业务背景:公司采购了一批爱奇艺会员卡密,导入到了 mysql 现在的做法是有用户购买就获取最后一张未使用的, 更新时判断状态(可能已被使用 会失败),如果失败就自旋重试 redis 的方案已经启弃用了 感觉难以保证一致性... 各大 v 友还有啥方案 麻了
1
lipaa OP 失败就自旋重试 感觉也不好 所以..
|
2
Rwing 2022-01-05 15:00:12 +08:00
加锁。。。。
|
3
murmur 2022-01-05 15:00:54 +08:00
并发有多大,你们那点销量需要很严格的锁机制么
|
4
murmur 2022-01-05 15:01:45 +08:00
最后一张未使用的是什么意思,我感觉这需求就有问题,连 ATM 都知道取钱和存钱是两个箱子
用户买了这卡,那就死活不退,这口子开不得 |
6
yushiro 2022-01-05 15:07:05 +08:00 via iPhone
这种需求能有多大的并发量啊?取卡密的时候上个锁不行?
|
7
murmur 2022-01-05 15:07:47 +08:00
|
8
abcbuzhiming 2022-01-05 15:09:49 +08:00
技术上保证不重发,那就只能加锁变成序列化。除此之外,看看有没有人能在业务模型上提出个新方案来
|
9
Canon1014 2022-01-05 15:11:12 +08:00
并发不是特别大加个乐观锁就够了吧
|
10
WildCat 2022-01-05 15:12:05 +08:00
|
11
h82258652 2022-01-05 15:13:44 +08:00
加锁
数据库获取一张未使用的 将这张标记为已使用 释放锁 并发不高就这么搞吧 并发高那也只能排队了,这业务就是个库存问题 |
12
murmur 2022-01-05 15:14:12 +08:00
@abcbuzhiming 模型就更简单了,直接排队领卡号,锁这部分更好设计,就跟淘宝一样,你买了卡,不是立刻收货,过段时间别人会把卡号通过消息给你发过来,或者直接代充
那后面的模型更简单了,不需要确认收货,撕逼都省了 |
13
wei745359223 2022-01-05 15:16:09 +08:00
两张表
卡密表 ID,CODE 发卡表 ID,USER_ID 两个表 ID 都是自增,往发卡表里插入数据,返回的 ID 在卡密表里找到就占用。 |
14
jadec0der 2022-01-05 15:17:04 +08:00
更新时判断状态会失败,是指…乐观锁?
|
15
EileenJ 2022-01-05 15:43:06 +08:00
pg 里用 skip locked 应该可以实现,mysql 不知道有没有类似的功能
|
18
Chinsung 2022-01-05 16:13:37 +08:00
上个分布式锁就行了吧,觉得 redis 不可靠就上 zk 。
你自旋的方案也没什么问题,但是锁肯定更好一些。 自旋的方案如果想吞吐高点,可以给卡密表分下区,然后再建张表记录表记录每个区里剩几张,在一个事务里更新。 比如卡密表 10 条记录分区,那记录表就记录 10 (范围)-9 (剩余数量)。然后请求进来就先找一个剩余数量大于 0 的分区去尝试更新。 如果你卡密数量无限,不怕超发,其实楼上兄弟那个双表自增主键的方案也挺好的 |
19
abigeater 2022-01-05 16:14:36 +08:00
用户点击领取向一个表写入一条记录 并返回 ID 给用户
然后提供一个接口给前端 通过 Id 获取卡密 让前端轮询调用直到有卡密返回 再起一个定时任务之类的 队列方式从卡密表拿一条就给领取表填一条 是不是就解决了。。 |
20
philchang1995 2022-01-05 16:26:13 +08:00
@abigeater 通过 id 获取卡密这个操作是如何实现的?类似 13 楼说的那个双表自增主键的方式么?
|
21
philchang1995 2022-01-05 16:29:56 +08:00
@abigeater 还有你的那个给领取表填数据的定时任务是不是不太合理,领取表的数据来源只有用户触发领取操作这一个途径才对吧
|
22
Rache1 2022-01-05 16:32:26 +08:00
如果觉得单独引入 redis 做锁成本太高的话,又想用类似可铐的方案,可以用 flock
|
23
boozer 2022-01-05 16:37:09 +08:00
说个题外话, 有时候业务问题不该让技术来买单
|
24
hgc81538 2022-01-05 16:55:08 +08:00
我想到這個方法:
UPDATE key_table set user_id="123" where id = (Select min(id) from key_table where user_id=null); 如果 affected rows == 0 就重試一, 兩次, 都不成功就報錯, 叫用戶稍後再試 如果 affected rows == 1, 則成功. 這方法不用鎖, 請問可行嗎? |
25
abigeater 2022-01-05 17:04:25 +08:00
@philchang1995 主要目标是将领取这个过程变成异步填充
和 13 的有区别吧 毕竟我更希望是有 ID 关联 有日志可查 假设一共有 2 张表卡密表 和 领取表 卡密表 id,code 领取表 id,user_id,code 通过 ID 获取是指,领取表的 ID 找到该条记录 (如果怕被穷举 可以用 hash 或者鉴权 user_id ) 轮询到 code 不为空为止。 正如你所说领取表数据来源是用户触发,用户写入了 user_id 到领取表后,一个队列(假设是定时任务或者 mq 消费队列)只需要取出 code 等于空的数据 从卡密表获取数据后 update 即可。 主要目的把这个领取过程交给后台异步队列填充,避免并发。 |
26
ZXCDFGTYU 2022-01-05 17:28:39 +08:00
之前遇到过类似的问题,用 for update 结合 order by 使用次数 asc 解决的
|
27
ganbuliao 2022-01-05 17:33:49 +08:00
redis list 不是挺好的吗
|
28
gam2046 2022-01-05 17:45:45 +08:00
这样可以嘛?
cursor = select ... for update from ... where status = unused limit 1 update set cursor.status = used return cursor |
29
Pythoner666666 2022-01-05 17:46:52 +08:00
@EileenJ 没有·
|
30
dzdh 2022-01-05 17:49:52 +08:00
难道不是给 code +个 order_id 的字段么
|
31
Pythoner666666 2022-01-05 17:54:00 +08:00
归根结底就是个并发问题。下发的时候加锁,可以解决 99%的下发。剩下 1%的锁冲突就丢到队列,然后消费队列即可。
|
34
lipaa OP 感谢 大家 还是自旋把 一次性取一大批 再去乐观锁更新 也基本可以保证不出问题
|
35
philchang1995 2022-01-05 17:56:40 +08:00
@abigeater 那是我一开始理解错你的队列的意思了,按这样的逻辑来的话确实可以避免发卡的并发操作 只是领卡并发高的时候用户轮询等待卡密这一块可能会等的久一点 不过我个人感觉这个方案可行👍
|
36
whcoding 2022-01-05 17:58:27 +08:00
我公司给你这差不多的一个业务
|
37
whcoding 2022-01-05 18:00:24 +08:00
我公司给你这差不多的一个业务 是这么处理的 导入的时候 同时导入 mysql 和 redis list, 用户来取码直接取 list 中的 然后去走个队列去修改数据库. 完事 ~
|
38
Building 2022-01-05 18:00:35 +08:00
分成几张表放,再加锁,并发不就好了吗。
|
39
Chad0000 2022-01-05 18:05:06 +08:00 via iPhone
这种并发应该不高吧,可能使用队列,然后只有一个消费者,外加乐观锁,外加先写日志,就差不多了吧。
|
40
philchang1995 2022-01-05 18:08:47 +08:00
@whcoding 这种方案 redis 的集群和数据保持做的好的话还不错 如果不好的话数据一致性和数据丢失确实是个问题
|
41
lu5je0 2022-01-05 18:13:08 +08:00
update tb set order=xxx limit 1
|
42
lipaa OP @philchang1995 我们一开始就这么做的
|
43
whcoding 2022-01-05 18:24:59 +08:00
@philchang1995 怕 redis 里的数据丢失?
|
44
ETiV 2022-01-05 18:26:22 +08:00 via iPhone 8
做成队列,改发放机制成申请制,申请后提示用户“稍后将卡片以短信、邮件发送”
然后慢慢消费队列就好了 话说你们不怕灰产、羊毛党吗? |
45
philchang1995 2022-01-05 18:39:56 +08:00
@whcoding 对啊😂
|
46
philchang1995 2022-01-05 18:41:59 +08:00
@lipaa 25 楼那种方案么?
|
47
whcoding 2022-01-05 18:43:59 +08:00
@philchang1995 reids 挂掉? 丢数据 可能性不大吧 再说了还有持久化呢 不怕.....
|
48
git00ll 2022-01-05 18:55:35 +08:00
for update 。简单 暴力。性能也不差
|
49
zhoujinjing09 2022-01-06 00:19:46 +08:00
就正常分布式自增 ID 的解法,每个 ID 对应一个卡密不就行了吗……可能会有跳过但是无所谓吧,你们每天定时任务重新归档一下?
|
50
ryd994 2022-01-06 03:41:23 +08:00 via Android 1
比起纠结这个,你更需要销售的日志。对库存表的每一个操作都应当有审计记录。这样就算碰到发错货或者无赖买家也有据可查。
|
51
v2orz 2022-01-06 08:33:27 +08:00
单线程 消费队列下发
|
52
Hug125 2022-01-06 09:05:16 +08:00 via iPhone
@ETiV #44 我司核心业务也是用消息队列异步处理保证数据准确性,就是需要注意消费者多实例部署时加分布式锁,避免两个实例同时消费同一条消息。OP 的业务场景做到准实时也够用了。
|
53
NeezerGu 2022-01-06 12:25:22 +08:00
非技术人员好奇问问。
能不能简单粗暴的吧所有卡密分成 10 分,放在 10 个 redis 分库里,对应 10 个取卡密进程(取卡时加锁排队)。 取卡的时候做个负载均衡,保证高并发场景能扛得住就行 印象里 redis 消耗资源不大,一台机开 n 个也行?当然如果内存不够 redis 可以换成 pika ? |