问题域一定要把握好。今天有人问到我“秒杀场景下超卖问题怎么解决”,我立马把问题域扩大到“秒杀系统要怎么建设”,方向已经偏到十万八千里了,而我也不知道如何回答了。
秒杀超卖现象:在高并发下,多个线程并发更新库存,导致库存为负的情况。
我搜集了一些资料,整理了一下,秒杀可选方案主要有以下三种:
1. 悲观锁
悲观锁方案最容易理解:在更新库存期间加锁,不允许其它线程修改。
1.1 select xxx for update
优点:确保了线程安全。
缺点:高并发场景下会导致多个请求一直等待,数据库性能下降,系统的链接数上升,负载飙升,影响系统的平均响应时间,甚至会瘫痪。
1.2 文件锁
优点与 1.1 类似,缺点是磁盘 IO 开销会变大。
1.3 缓存锁
当用户 A 要修改某个 id 的数据时,把要修改的 id 存入缓存,若其他用户触发修改此 id 的数据时,读到 memcache 有这个 id 的值时,就阻止其它用户修改。
优点与缺点与 1.2,但总体效果要好于以上两种方案。如果缓存是独立的集群,还可以解决跨进程乐观锁处理不了的问题。
1.4 分布式锁
与 1.3 类似。
2. 乐观锁
使用带版本号的更新。每个线程都可以并发修改,但在并发时,只有一个线程会修改成功,其它会返回失败。
Redis 的 watch
3. FIFO 队列
通过 FIFO 队列,使修改库存的操作串行化。
Redis 的队列
优点:不需要在单独加锁(无论是悲观锁还是乐观锁)。
缺点:队列的长度是有限的,必须控制好,不然请求会越积越多。
4. 结论
总的来说,不能把压力放在数据库上,所以使用"select xxx for update"的方式在高并发的场景下是不可行的。FIFO 同步队列的方式,可以结合库存限制队列长,但是在库存较多的场景下,又不太适用。所以相对来说,我会倾向于选择:乐观锁/缓存锁/分布式锁的方式。
5. 参考文献
https://blog.csdn.net/maikelsong/article/details/53322942
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于