当前位置: 首页 > 科技观察

秒杀系统必须考虑的3个技术问题!

时间:2023-03-21 15:39:56 科技观察

1。并发队列的选择Java的concurrent包提供了三种常用的并发队列实现,分别是:ArrayBlockingQueue、ConcurrentLinkedQueue和LinkedBlockingQueue。ArrayBlockingQueue是一个初始容量固定的阻塞队列。我们可以将其作为数据库模块竞价成功的队列。比如有10个商品,那么我们设置一个大小为10的数组队列。ConcurrentLinkedQueue是使用CAS原语无锁队列实现的。它是一个异步队列。入队速度很快,出队时队列是锁死的,所以性能稍慢。LinkedBlockingQueue也是一个阻塞队列。锁定用于进入和退出队列。当队列为空时,线程会暂时阻塞。在请求预处理阶段,由于我们系统的入队需求远大于出队需求,一般不会出现空队列,所以我们可以选择ConcurrentLinkedQueue作为我们的请求队列实现。2、请求接口的合理设计一个秒杀或抢购页面通常分为两部分,一是静态HTML等内容,二是参与秒杀的Web后台请求接口。通常静态HTML等内容都是通过CDN部署的,一般压力不大,核心瓶颈其实在后台请求接口上。这个后端接口必须能够支持高并发请求。同时,必须尽可能“快”,在最短的时间内返回用户的请求结果,这一点非常重要。为了尽可能快地实现这一点,最好对接口的后端存储使用内存级操作。直接针对MySQL等存储还是不合适的。如果有这么复杂的业务需求,建议使用异步写法。当然,也有一些秒杀、抢购使用了“滞后反馈”,也就是说秒杀不知道此刻的结果,需要一段时间才能看到用户秒杀成功与否。不是来自页面。但这种行为属于“偷懒”行为,同时用户体验不佳,容易被用户视为“暗箱操作”。推荐:秒杀系统设计的5个要点。3.高并发下的数据安全我们知道,当多个线程写入同一个文件时,会存在“线程安全”问题(多个线程同时运行相同的代码,如果每次运行的结果都不同单线程结果是一样的,结果和预期的一样,是线程安全的)。如果是MySQL数据库,可以利用其内置的锁机制很好的解决问题。但是在大规模并发场景下,不推荐使用MySQL。在闪购、抢购场景中,还有一个问题,就是“超发”。如果这方面控制不慎,就会出现超发。我们也听说过一些电商搞抢购活动,买家拍照成功后,商家不承认订单有效,拒绝发货。这里的问题不一定是商家背信弃义,而是系统技术层面的超发风险。供过于求的原因假设在抢购场景中,我们总共只有100件商品,而在最后一刻,我们已经消耗了99件商品,只剩下最后一件。这时系统发送了多个并发请求,这些请求读取到的商品余额都是99,都通过了这个余额判断,最终导致超发。(同文前面提到的场景)上图中,并发用户B也“抢购成功”,让多了一个人拿到了商品。这种场景在高并发的情况下非常容易出现。悲观锁思路解决线程安全的思路有很多,讨论可以从“悲观锁”的方向开始。悲观锁,即修改数据时,采用锁定状态,排除外部请求修改。当遇到锁定状态时,必须等待。虽然上面的方案确实解决了线程安全的问题,但是别忘了我们的场景是“高并发”的。也就是说,这样的修改请求会很多,每个请求都需要等待一个“锁”。有些线程可能永远没有机会抢到这个“锁”,这样的请求就会死在那里。同时,这样的请求会很多,会瞬间增加系统的平均响应时间。结果,可用连接数将被耗尽,系统将陷入异常。推荐:并发控制——悲观锁和乐观锁详解。FIFO队列的思路不错,所以我们稍微修改一下上面的场景,我们直接把请求放到队列中,使用FIFO(FirstInputFirstOutput,先进先出),这样就不会造成一些永远处理的请求无法获取锁。看到这里,是不是觉得把多线程变成单线程有点勉强呢?那么,我们现在已经解决了锁的问题,所有的请求都在一个“先进先出”的队列中处理。那么一个新的问题出现了。在高并发场景下,由于请求量大,极有可能瞬间“爆”队列内存,进而导致系统陷入异常状态。或者设计一个巨大的内存队列也是一种解决方案。但是,系统在队列中处理请求的速度无法与疯狂涌入队列的数量相提并论。也就是说队列中的请求会越积越多,最终web系统的平均响应时间还是会大幅度下降,系统还是会陷入异常。乐观锁思想这个时候,我们就可以讨论一下“乐观锁”的思想了。乐观锁采用了比“悲观锁”更宽松的锁机制,大多更新一个版本号(Version)。实现是所有对该数据的请求都可以修改,但将获得数据的版本号。只有版本号匹配的才能更新成功,其他返回抢购失败。推荐:并发控制——悲观锁和乐观锁详解。这种情况下我们可以不用考虑队列的问题,但是会增加CPU的计算开销。但是,总的来说,这是一个更好的解决方案。支持“乐观锁”功能的软件和服务有很多,例如Redis中的watch就是其中之一。通过这个实现,我们保证了数据的安全。