1
renmu 2019-11-27 22:50:50 +08:00 via Android
一律返回失败,几秒后返回假数据到前端(狗头保命)
|
2
lhx2008 2019-11-27 22:53:24 +08:00 via Android 1
读已提交肯定出问题啊,因为他其实是基于 MVCC 的,比如说现在两个事务都看到只有一个库存,他就直接都做减库存操作了。不过你说了加锁,可能加的位置不对吧。
|
3
MeteorCat 2019-11-27 22:53:44 +08:00 via Android 1
不要假设并发不高,我今年也是假设并发不高,哪知道不知道哪里天杀直接爆破公司项目接口
|
4
lhx2008 2019-11-27 22:55:31 +08:00 via Android 1
最简单的方法肯定是 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 2
跟客户关系好的话...让她多准备几份商品.
|
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 1
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 1
秒杀系统设计应该涵盖两个要点,即限流和同步。限流可以采用 controller 层 CAS 结合分布式锁例如 Redis 或 Zookeeper。同步可以采用 service 层锁或 MySQL 乐观锁。你提到了数据库的事务,你确定只将逻辑写入事务就能保证系统的正确性吗?
|
13
yejianmail OP @jeffh 默认是可重复读级别,但是要开 binlog 才支持
|
14
mrdemonson 2019-11-27 23:38:26 +08:00 via Android 2
一直觉得奇怪,秒杀应该是锁内存数据吧,直接操作内存好了,为啥都要去搞数据库,和锁数据库
|
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 1
我记得在网上看过,淘宝的秒杀是异步的,先在内存中设置一个总量 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 1
@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,只是提供一个思路) |