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

Reference:秒杀系统必须考虑的 3 个技术问题数据库

一、并发队列的选择后端

Java的并发包提供了三个经常使用的并发队列实现,分别是:ArrayBlockingQueue、ConcurrentLinkedQueue 和 LinkedBlockingQueue 。数组

ArrayBlockingQueue是初始容量固定的阻塞队列,咱们能够用来做为数据库模块成功竞拍的队列,好比有10个商品,那么咱们就设定一个10大小的数组队列。安全

ConcurrentLinkedQueue使用的是CAS原语无锁队列实现,是一个异步队列,入队的速度很快,出队进行了加锁,性能稍慢。多线程

LinkedBlockingQueue也是阻塞的队列,入队和出队都用了加锁,当队空的时候线程会暂时阻塞。并发

在请求预处理阶段,因为咱们的系统入队需求要远大于出队需求,通常不会出现队空的状况,因此咱们能够选择ConcurrentLinkedQueue来做为咱们的请求队列实现。异步

二、请求接口的合理设计高并发

一个秒杀或者抢购页面,一般分为2个部分,一个是静态的HTML等内容,另外一个就是参与秒杀的Web后台请求接口。性能

一般静态HTML等内容,是经过CDN的部署,通常压力不大,核心瓶颈实际上在后台请求接口上。这个后端接口,必须可以支持高并发请求,同时,很是重要的一点,必须尽量“快”,在最短的时间里返回用户的请求结果。线程

为了实现尽量快这一点,接口的后端存储使用内存级别的操做会更好一点。仍然直接面向MySQL之类的存储是不合适的,若是有这种复杂业务的需求,都建议采用异步写入。

 

固然,也有一些秒杀和抢购采用“滞后反馈”,就是说秒杀当下不知道结果,一段时间后才能够从页面中看到用户是否秒杀成功。

可是,这种属于“偷懒”行为,同时给用户的体验也很差,容易被用户认为是“暗箱操做”。推荐:秒杀系统设计的 5 个要点。

三、高并发下的数据安全

咱们知道在多线程写入同一个文件的时候,会存现“线程安全”的问题(多个线程同时运行同一段代码,若是每次运行结果和单线程运行的结果是同样的,结果和预期相同,就是线程安全的)。

若是是MySQL数据库,可使用它自带的锁机制很好的解决问题,可是,在大规模并发的场景中,是不推荐使用MySQL的。秒杀和抢购的场景中,还有另一个问题,就是“超发”,若是在这方面控制不慎,会产生发送过多的状况。

咱们也曾经据说过,某些电商搞抢购活动,买家成功拍下后,商家却不认可订单有效,拒绝发货。这里的问题,也许并不必定是商家奸诈,而是系统技术层面存在超发风险致使的。

超发的缘由

假设某个抢购场景中,咱们一共只有100个商品,在最后一刻,咱们已经消耗了99个商品,仅剩最后一个。

这个时候,系统发来多个并发请求,这批请求读取到的商品余量都是99个,而后都经过了这一个余量判断,最终致使超发。(同文章前面说的场景)

 

在上面的这个图中,就致使了并发用户B也“抢购成功”,多让一我的得到了商品。这种场景,在高并发的状况下很是容易出现。

悲观锁思路

解决线程安全的思路不少,能够从“悲观锁”的方向开始讨论。

悲观锁,也就是在修改数据的时候,采用锁定状态,排斥外部请求的修改。遇到加锁的状态,就必须等待。

 

虽然上述的方案的确解决了线程安全的问题,可是,别忘记,咱们的场景是“高并发”。也就是说,会不少这样的修改请求,每一个请求都须要等待“锁”,某些线程可能永远都没有机会抢到这个“锁”,这种请求就会死在那里。

同时,这种请求会不少,瞬间增大系统的平均响应时间,结果是可用链接数被耗尽,系统陷入异常。推荐:并发控制--悲观锁和乐观锁详解

FIFO队列思路

那好,那么咱们稍微修改一下上面的场景,咱们直接将请求放入队列中的,采用FIFO(First Input First Output,先进先出),这样的话,咱们就不会致使某些请求永远获取不到锁。看到这里,是否是有点强行将多线程变成单线程的感受哈。

 

而后,咱们如今解决了锁的问题,所有请求采用“先进先出”的队列方式来处理。那么新的问题来了,高并发的场景下,由于请求不少,极可能一瞬间将队列内存“撑爆”,而后系统又陷入到了异常状态。

或者设计一个极大的内存队列,也是一种方案,可是,系统处理完一个队列内请求的速度根本没法和疯狂涌入队列中的数目相比。

也就是说,队列内的请求会越积累越多,最终Web系统平均响应时候仍是会大幅降低,系统仍是陷入异常。

乐观锁思路

这个时候,咱们就能够讨论一下“乐观锁”的思路了。乐观锁,是相对于“悲观锁”采用更为宽松的加锁机制,大都是采用带版本号(Version)更新。

实现就是,这个数据全部请求都有资格去修改,但会得到一个该数据的版本号,只有版本号符合的才能更新成功,其余的返回抢购失败。推荐:并发控制--悲观锁和乐观锁详解

这样的话,咱们就不须要考虑队列的问题,不过,它会增大CPU的计算开销。可是,综合来讲,这是一个比较好的解决方案。

 

有不少软件和服务都“乐观锁”功能的支持,例如Redis中的watch就是其中之一。经过这个实现,咱们保证了数据的安全。