刚刚看了一篇比较好的文章,指导怎么构建秒杀系统,看完之后发现有几点可以改良,暂记下来。原文请移步: 点击打开链接
1.去掉乐观锁
由于秒杀活动并发量极高,乐观锁大几率失败,所以这里不考虑乐观锁
2.不使用消息队列
由于设置了限流,实际上系统流量有限,无须使用消息队列。
基于以上,我对秒杀流程进行了改良,思路如下
1.首次访问时,在redis上读取商品的库存,有两种可能: 1.没有读取到库存数量,那只有一种可能,就是秒杀活动刚刚开始。此时需加分布式锁,重新从redis读取库存数,然后把商品库存数缓存到redis,解锁。这里加锁后需重新读取是因为可能有多线程重入读取商品的库存,如果第一个线程已经缓存了商品库存,第二个线程及以后不能重新缓存库存!2.读取到库存为0,秒杀结束,不为0,即还有库存,执行下一步
2.调用redis decr命令使商品库存-1,然后检查命令返回值,返回小于0,即已经没有库存,马上返回失败,返回M (此时M>0),下一步。(由于redis单线程的特点。此处redis decr命令可以保证原子性)
3.增加秒杀订单
4.redis get重新获取商品库存数N,如果最新商品库存数小于M,则不更新数据库(N<M表示刚刚已经有人下单了,redis库存已经减少了,此时更新数据库中商品库存没有意义,比如总库存为10,库存M=9,最新库存值N=8,此时更新商品库存为9没有意义,因为最后始终会更新为8)。如果M=N,加写锁,更新数据库中库存为N,解锁。
伪代码
//加锁 /*在这里加锁是因为:如果在判断M==N之后加锁,另一线程改变了N的值为X(如X=N-1),并且数据库更新最新值为X,之后当前线程继续执行sql更新最新值为N的话会覆盖掉最新的更新!这样会导致修改丢失了,虽然几率极小,但是不可忽略,且此处逻辑简单,加锁对性能影响不大*/
//获取最新库存N
if (M==N){
//执行sql更新
} else{//M>N,执行第四步之前,库存数减少了,此时不更新数据库,什么也不执行
}
//解锁