关于高并发秒杀超出库存数目的问题。

某程序员 2017-05-05 02:11:06
最近看了不少关于网站秒杀活动时,大数目的用户并发抢购,如何避免库存出现负数之类的情况。
我的想法是,类似mysql数据库,多个线程并发更新同条数剧时,会自动并发串行化,也就是给同时到达这里的是三个用户排个序,一个一个执行,并生成排他锁。那么我只要设计这个库存字段unsigned不让它可能为负数。不就好了吗?当更新结果为负数的时候,mysql会自动抛出异常,这时候我服务端catch住异常,所有数据库操作回滚,提示无库存,从而避免库存超卖的情况。
当然我没有考虑性能方面,不知道我这个想法是否正确?
...全文
7507 22 打赏 收藏 转发到动态 举报
写回复
用AI写文章
22 条回复
切换为时间正序
请发表友善的回复…
发表回复
weixin_45464387 2020-11-28
  • 打赏
  • 举报
回复
https://forum.csdn.net/PointForum/ui/scripts/csdn/Plugin/003/monkey/8.gif
HumanFactory 2020-06-22
  • 打赏
  • 举报
回复
引用 6 楼 执笔记忆的空白 的回复:
[quote=引用 4 楼 caoyiweidashuaibi 的回复:] [quote=引用 2 楼 shijing266 的回复:] 秒杀活动,如果是更新和删除操作:设置乐观锁即可解决(满足分布式和集群的情况) 如果是新增操作:数据库设置某个字段的唯一 约束即可(满足分布式和集群的情况)
乐观锁:是在表中加个版本字段,事务启动,更新某条数据之前,去获取它的版本,更新数据。之后再次获取版本,比对两个版本值,一样则提交事务,不一致则事务回滚?我理解的意思对吗。如果这样,那不是很耗性能?[/quote] 含义确实是的。但是体现上没那么复杂, 就是在更新和删除操作时,判断下版本就行了。 例如: update user set status=2,version=version+1 where id=#{id} and version=#{version}; 这样,最新版本的能更新成功,其他的没成功的,更新行数就是0 ,然后你就通知事务回滚咯。 至于性能的影响。我们公司现在就是用的这种方案,并未出现性能问题 [/quote]只能说明你们的并发量不大,并发量大的时候一个版本被1000个人读到了,结果999个人都操作失败重新下单?
zhangxiaomin19921 2017-07-13
  • 打赏
  • 举报
回复
针对100个请求过来减库存的情况, 1.可以申明一个固定大小的阻塞队列,用户请求的时候,并不实际减库存,而是把请求商品id,用户id,商品id,等信息放到阻塞队列,超过阻塞队列大小就返回用户秒杀结束,在秒杀时间结束后或者请求结束后,就遍历阻塞队列,对库存做减操作。 2.使用悲观锁,请注意会死锁 3.使用内存锁,当某个用户要修改某个id的数据时,把要修改的id存入memcache,若其他用户触发修改此id的数据时,读到memcache有这个id的值时,就阻止那个用户修改
110成成 2017-05-05
  • 打赏
  • 举报
回复
我觉得这并不是超卖问题,可以理解位秒杀 如果通过记录加锁 但是在高并发情况下,性能会有所欠缺
帝落少天 2017-05-05
  • 打赏
  • 举报
回复
直接update table set a-1 where a>0,更新行数为1说明扣减成功,否则失败
HinanaiTenshi 2017-05-05
  • 打赏
  • 举报
回复
引用 16 楼 caoyiweidashuaibi 的回复:
两位大神,我说了暂不考虑性能,我就想问问我这样处理可不可以解决高并发库存超卖的问题。。
1. 所谓秒杀问题,就是性能的问题。如果不考虑性能,压根就不存在秒杀场景,就是一个普通的销售场景。 2. 超卖是一个业务问题,而不是一个技术问题,出现超卖智能说业务设计出现了漏洞。 3. 技术上有的是手段能完全杜绝超卖:严格事务控制,高一致性方案。 4. 业务架构上有很多机制能防止超卖,只要最后结果没有超卖就行了。
某程序员 2017-05-05
  • 打赏
  • 举报
回复
两位大神,我说了暂不考虑性能,我就想问问我这样处理可不可以解决高并发库存超卖的问题。。
  • 打赏
  • 举报
回复
引用 14 楼 pany1209 的回复:
[quote=引用 9 楼 shijing266 的回复:] [quote=引用 7 楼 pany1209 的回复:] 秒杀啊、。。。。用redis的List数据类型吧,把所有秒杀请求插入队列,当请求达到库存阀值后停止插入。。。。然后进行后续的处理。。例如LPOP key得到秒杀成功的用户的id进行后续处理。。。这种方式可以解决超卖问题
秒杀方案当然用队列。 只是他的问题是说数据库怎么设计[/quote] 数据库设计???超发就是读取库存和减少库存不是原子性的问题吧,,,在redis里面用一个hash结构存储库存,用hincrby命令。。该命令的自增自减都是原子性的。。估计可以解决[/quote] 别说估计,一般线上的控制都有几层的。 一层的控制谁也没法保证。 比如我们新增操作还有分布式锁。 这些都是处理方案 队列是减缓 对数据库的压力, 乐观锁和唯一索引等是防止意外。
李德胜1995 2017-05-05
  • 打赏
  • 举报
回复
引用 9 楼 shijing266 的回复:
[quote=引用 7 楼 pany1209 的回复:] 秒杀啊、。。。。用redis的List数据类型吧,把所有秒杀请求插入队列,当请求达到库存阀值后停止插入。。。。然后进行后续的处理。。例如LPOP key得到秒杀成功的用户的id进行后续处理。。。这种方式可以解决超卖问题
秒杀方案当然用队列。 只是他的问题是说数据库怎么设计[/quote] 数据库设计???超发就是读取库存和减少库存不是原子性的问题吧,,,在redis里面用一个hash结构存储库存,用hincrby命令。。该命令的自增自减都是原子性的。。估计可以解决
X元素 2017-05-05
  • 打赏
  • 举报
回复
引用 3 楼 caoyiweidashuaibi 的回复:
[quote=引用 1 楼 u011619071 的回复:] 给你举个例子 A 读取到P 当前 有100 个。 扣减一个库存的动作完成之前 ,B 读取了P 数据, 这时候P 的库存仍然是100 , 而这时候A 扣减动作完成,那么当前P 还有99个, 而B 拿到的却是100个, 如果依靠数据库的隔离级别的话,大大的降低了数据处理能力,楼主说的排它锁在抢购的业务场景下,极容易发生死锁。 而抢购对用户体验有很高的要求,需要根据不同业务场景设计出合理的流程。
我为什么要拿库存,我只管减库存不就好了吗?。。 排它锁是mysql更新操作是自己加的,那一般项目高并发更新数据时,那不是都容易死锁了?总不能说是mysql的性能问题把。[/quote] 我建议楼主再好好看看排它锁。
HinanaiTenshi 2017-05-05
  • 打赏
  • 举报
回复
稍微有点流量的秒杀场景都不适合把请求直接压到数据库上,或者说不适合直接依赖磁盘的读写能力,用户稍微发力一下就把数据库连接打满了。 如果要详细了解秒杀的细节,可以翻看网上有很多经典的文章,比如阿里那篇 秒杀系统架构优化思路。 回到这个问题本身,业务上秒杀一般都有一个预栈的设置,是大于实际库存的虚拟库存,而这个库存内的商品真正是真正可秒杀商品。在秒杀活动中就算因为总总原因约束被一枪捅穿,活动商品的发货依然是可控的。另外这个预栈设计也是缓存设计的关键,围绕它可以做业务和技术两方面的优化。
  • 打赏
  • 举报
回复
引用 8 楼 caoyiweidashuaibi 的回复:
[quote=引用 5 楼 shijing266 的回复:] 你减是正常的,但是你页面要展示啊,既然要展示,你肯定要读的
我只要保证不超卖就行了,当所有的线程跑完了,页面不就好了吗,12306明明有票,真正去抢,就告诉你没票啊,刷新页面还是显示有票的。所以说 我这个想法,其实还是没问题,对不对?快告诉我对。[/quote] 这种方案我没试过,并且在线上环境没这样弄过。你可以去尝试下。
某程序员 2017-05-05
  • 打赏
  • 举报
回复
引用 7 楼 pany1209 的回复:
秒杀啊、。。。。用redis的List数据类型吧,把所有秒杀请求插入队列,当请求达到库存阀值后停止插入。。。。然后进行后续的处理。。例如LPOP key得到秒杀成功的用户的id进行后续处理。。。这种方式可以解决超卖问题
听不懂
  • 打赏
  • 举报
回复
引用 7 楼 pany1209 的回复:
秒杀啊、。。。。用redis的List数据类型吧,把所有秒杀请求插入队列,当请求达到库存阀值后停止插入。。。。然后进行后续的处理。。例如LPOP key得到秒杀成功的用户的id进行后续处理。。。这种方式可以解决超卖问题
秒杀方案当然用队列。 只是他的问题是说数据库怎么设计
某程序员 2017-05-05
  • 打赏
  • 举报
回复
引用 5 楼 shijing266 的回复:
你减是正常的,但是你页面要展示啊,既然要展示,你肯定要读的
我只要保证不超卖就行了,当所有的线程跑完了,页面不就好了吗,12306明明有票,真正去抢,就告诉你没票啊,刷新页面还是显示有票的。所以说 我这个想法,其实还是没问题,对不对?快告诉我对。
李德胜1995 2017-05-05
  • 打赏
  • 举报
回复
秒杀啊、。。。。用redis的List数据类型吧,把所有秒杀请求插入队列,当请求达到库存阀值后停止插入。。。。然后进行后续的处理。。例如LPOP key得到秒杀成功的用户的id进行后续处理。。。这种方式可以解决超卖问题
  • 打赏
  • 举报
回复
引用 4 楼 caoyiweidashuaibi 的回复:
[quote=引用 2 楼 shijing266 的回复:] 秒杀活动,如果是更新和删除操作:设置乐观锁即可解决(满足分布式和集群的情况) 如果是新增操作:数据库设置某个字段的唯一 约束即可(满足分布式和集群的情况)
乐观锁:是在表中加个版本字段,事务启动,更新某条数据之前,去获取它的版本,更新数据。之后再次获取版本,比对两个版本值,一样则提交事务,不一致则事务回滚?我理解的意思对吗。如果这样,那不是很耗性能?[/quote] 含义确实是的。但是体现上没那么复杂, 就是在更新和删除操作时,判断下版本就行了。 例如: update user set status=2,version=version+1 where id=#{id} and version=#{version}; 这样,最新版本的能更新成功,其他的没成功的,更新行数就是0 ,然后你就通知事务回滚咯。 至于性能的影响。我们公司现在就是用的这种方案,并未出现性能问题
  • 打赏
  • 举报
回复
引用 3 楼 caoyiweidashuaibi 的回复:
[quote=引用 1 楼 u011619071 的回复:] 给你举个例子 A 读取到P 当前 有100 个。 扣减一个库存的动作完成之前 ,B 读取了P 数据, 这时候P 的库存仍然是100 , 而这时候A 扣减动作完成,那么当前P 还有99个, 而B 拿到的却是100个, 如果依靠数据库的隔离级别的话,大大的降低了数据处理能力,楼主说的排它锁在抢购的业务场景下,极容易发生死锁。 而抢购对用户体验有很高的要求,需要根据不同业务场景设计出合理的流程。
我为什么要拿库存,我只管减库存不就好了吗?。。 排它锁是mysql更新操作是自己加的,那一般项目高并发更新数据时,那不是都容易死锁了?总不能说是mysql的性能问题把。[/quote] 你减是正常的,但是你页面要展示啊,既然要展示,你肯定要读的
某程序员 2017-05-05
  • 打赏
  • 举报
回复
引用 2 楼 shijing266 的回复:
[quote=引用 楼主 caoyiweidashuaibi 的回复:] 最近看了不少关于网站秒杀活动时,大数目的用户并发抢购,如何避免库存出现负数之类的情况。 我的想法是,类似mysql数据库,多个线程并发更新同条数剧时,会自动并发串行化,也就是给同时到达这里的是三个用户排个序,一个一个执行,并生成排他锁。那么我只要设计这个库存字段unsigned不让它可能为负数。不就好了吗?当更新结果为负数的时候,mysql会自动抛出异常,这时候我服务端catch住异常,所有数据库操作回滚,提示无库存,从而避免库存超卖的情况。 当然我没有考虑性能方面,不知道我这个想法是否正确?
秒杀活动,如果是更新和删除操作:设置乐观锁即可解决(满足分布式和集群的情况) 如果是新增操作:数据库设置某个字段的唯一 约束即可(满足分布式和集群的情况)[/quote] 乐观锁:是在表中加个版本字段,事务启动,更新某条数据之前,去获取它的版本,更新数据。之后再次获取版本,比对两个版本值,一样则提交事务,不一致则事务回滚?我理解的意思对吗。如果这样,那不是很耗性能?
某程序员 2017-05-05
  • 打赏
  • 举报
回复
引用 1 楼 u011619071 的回复:
给你举个例子 A 读取到P 当前 有100 个。 扣减一个库存的动作完成之前 ,B 读取了P 数据, 这时候P 的库存仍然是100 , 而这时候A 扣减动作完成,那么当前P 还有99个, 而B 拿到的却是100个, 如果依靠数据库的隔离级别的话,大大的降低了数据处理能力,楼主说的排它锁在抢购的业务场景下,极容易发生死锁。 而抢购对用户体验有很高的要求,需要根据不同业务场景设计出合理的流程。
我为什么要拿库存,我只管减库存不就好了吗?。。 排它锁是mysql更新操作是自己加的,那一般项目高并发更新数据时,那不是都容易死锁了?总不能说是mysql的性能问题把。
加载更多回复(2)

67,513

社区成员

发帖
与我相关
我的任务
社区描述
J2EE只是Java企业应用。我们需要一个跨J2SE/WEB/EJB的微容器,保护我们的业务核心组件(中间件),以延续它的生命力,而不是依赖J2SE/J2EE版本。
社区管理员
  • Java EE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧