1 renmu 2019-11-27 22:50:50 +08:00 via Android 一律返回失败,几秒后返回假数据到前端(狗头保命) |
![]() | 2 lhx2008 2019-11-27 22:53:24 +08:00 via Android ![]() 读已提交肯定出问题啊,因为他其实是基于 MVCC 的,比如说现在两个事务都看到只有一个库存,他就直接都做减库存操作了。不过你说了加锁,可能加的位置不对吧。 |
3 MeteorCat 2019-11-27 22:53:44 +08:00 via Android ![]() 不要假设并发不高,我今年也是假设并发不高,哪知道不知道哪里天杀直接爆破公司项目接口 |
![]() | 4 lhx2008 2019-11-27 22:55:31 +08:00 via Android ![]() 最简单的方法肯定是 redis 做一个 lua 递减,或者一个分布式锁,程序内的锁,多副本运行就死了 |
![]() | 5 lhx2008 2019-11-27 22:56:46 +08:00 via Android 如果是纯 mysql 的话,也可以用版本乐观锁的,但是读已提交还是不一定能生效。 |
![]() | 6 shoaly 2019-11-27 22:58:48 +08:00 ![]() 跟客户关系好的话...让她多准备几份商品. |
7 yejianmail OP @lhx2008 涉及两个方法一个是加减,一个是查看剩余库存,锁是同一个对象,事务应该是默认的 require |
8 yejianmail OP @renmu 最后一结算交易额为 0 |
9 yejianmail OP @lhx2008 我看到网上的一些实现就是用的一个 guava 的工具类来限流,然后用 redis 的递增或者递减来保证库存操作原子性,没看明白退还库存为什么一定要用 lua 脚本 |
![]() | 10 des 2019-11-27 23:29:22 +08:00 via Android 这种不是有很多讨论的么? 提前把数据在库里生成好也行,每一个商品算一条记录,删除成功进行后续操作 用 redis 也行,不过还是建议用 redis 这种东西不适合直接上锁 |
![]() | 11 jeffh 2019-11-27 23:30:40 +08:00 ![]() mariadb 默认不是不重复读级别吗?更新库存的时候可以 update tab set value=value-1 where id=? and value>0;这相当于变相的乐观锁了吧。根据 sql 返回值可以知道是否 sql 执行成功 |
12 hhx 2019-11-27 23:36:15 +08:00 via Android ![]() 秒杀系统设计应该涵盖两个要点,即限流和同步。限流可以采用 controller 层 CAS 结合分布式锁例如 Redis 或 Zookeeper。同步可以采用 service 层锁或 MySQL 乐观锁。你提到了数据库的事务,你确定只将逻辑写入事务就能保证系统的正确性吗? |
13 yejianmail OP @jeffh 默认是可重复读级别,但是要开 binlog 才支持 |
14 mrdemonson 2019-11-27 23:38:26 +08:00 via Android ![]() 一直觉得奇怪,秒杀应该是锁内存数据吧,直接操作内存好了,为啥都要去搞数据库,和锁数据库 |
15 yejianmail OP @jeffh 根据 sql 返回值这是个好办法,类似于 ignore into 看插入成功没 |
16 yejianmail OP @mrdemonson 最终数据库需要和内存同步吧,这样才有办法结算 |
17 mxT52CRuqR6o5 2019-11-27 23:44:23 +08:00 via Android 据说淘宝的双十一秒杀是会超售的,不知道是真是假 |
18 yejianmail OP @hhx 如果读取的数据没有脏读和幻读,可以保证业务的正确性 |
19 yejianmail OP @mxT52CRuqR6o5 真的么,那我这就不算 bug 了呀,手动滑稽.jpg |
![]() | 20 petelin 2019-11-27 23:50:41 +08:00 via iPhone 为啥楼上的都不考虑可靠性和稳定性 内存数据库万一挂了呢?实时同步不就退化成...了吗 我觉得限流加锁完全没问题 比如你用 select for update 一个人一个人的弄 怎么会有问题 |
![]() | 21 jeffh 2019-11-27 23:57:09 +08:00 ![]() 我记得在网上看过,淘宝的秒杀是异步的,先在内存中设置一个总量 v,秒杀到的显示排队中 mq 削峰异步处理,同时 v-1,如果 v 小于 0 了,直接返回秒杀结束。 |
22 hhx 2019-11-28 00:04:43 +08:00 via Android @yejianmail 我想知道你是怎么用的 |
23 h123123h 2019-11-28 00:07:34 +08:00 对速度不敏感的话事务+乐观锁就可以了吧 |
24 ljpCN 2019-11-28 00:51:42 +08:00 消费者队列 |
![]() | 25 wangyzj 2019-11-28 00:58:29 +08:00 select for update redis nx queue 同时保存状态+轮询获取新状态 |
![]() | 26 imcj 2019-11-28 00:59:48 +08:00 如果没有范围读取,read commit 足够,要么设置为 repeatable-read,要么修改代码,避免范围读取。 当然,事务的开启和提交是否都正确?嵌套是否是否存在? |
![]() | 27 imcj 2019-11-28 01:00:07 +08:00 如果没有范围读取,read commit 足够,要么设置为 repeatable-read,要么修改代码,避免范围读取。 当然,事务的开启和提交是否都正确?嵌套事务是否存在? |
28 yc8332 2019-11-28 08:09:16 +08:00 用户不多直接数据库加锁,或者更新的时候校验数据库的库存,和你判断时的值不一样就失败 |
![]() | 30 markgor 2019-11-28 12:14:48 +08:00 一个用户不太多的秒杀 能出-1 的情.... 我通常偷的做法 start transaction; select 1 from item where id = 123 and less > 1;就返回失。 update item set less = less -1 where less > 1 and id = 123;影=0 返回失 commit; 未出超售。了 item 的 less 是不允的。 另外也 redis , 把品加去 redis, 然後成功 pop 出再去 mysql 扣。 |
![]() | 31 markgor 2019-11-28 12:15:42 +08:00 ![]() @markgor #30 select 1 from item where id = 123 and less > 0;就返回失 update item set less = less -1 where less > 0 and id = 123;影=0 返回失 的,上面了,更改回。 |
![]() | 32 shenyuzhi 2019-11-28 14:45:17 +08:00 via iPhone 可以先收集请求,然后抽奖。 比如你预计 3 秒钟秒完,就收集前 5 秒的请求,只记录不处理,然后抽奖。秒杀本来就看运气,用户又不知道你怎么实现的。 |
33 yejianmail OP @markgor 我觉得你这个方法可行,毕竟项目用户不多,不用整得太复杂 |
![]() | 34 markgor 2019-11-28 18:36:22 +08:00 @yejianmail 你可以做面,一有求就存,按照我上面那方法,然後 ab 一下,看看不出超售。 反正超售是肯定可以保,但是由於行流程是一行,所以後面的卡在那,此前端 ajax 置超重。 另外流量真的大,你直接後台按百分比放行,比方 1~100,只要是 50 以上的放行行,50 以下的直接返回失。 |
![]() | 35 markgor 2019-11-28 18:40:41 +08:00 你可以考下: 前端, CDN。 前端面,一是效求去百度,一是有效求到 java 然後有效求的那面,送 ajax 去 java 第一按百分比返回果, 50%以上行上面。50%的就直接返回失。 然,是很低的方法,但一定程度有效分流,也能保到超售情。 |
36 yejianmail OP @markgor 谢谢大佬,我测试下,ab 没用过,我用 jmeter 试试 |
37 crclz 2019-12-01 19:13:50 +08:00 库存为 -1,应该是没有在库存那条记录上面加锁,而不是幻读。 另外 ReadCommited 足够。 如果数据高争用,那么就应该用 redis。但是 redis 是内存数据库,Durability 没法保证,所以就 2 台以上 redis 吧。redis 写个 lua 脚本,最小化往返次数。 (没实际用过 redis,只是提供一个思路) |