• 请不要在回答技术问题时复制粘贴 AI 生成的内容
lipaa
V2EX  ›  程序员

讨论下 卡密下发 保证不重发 除了不用 redis 还有啥方案

  •  
  •   lipaa · Jan 5, 2022 · 7011 views
    This topic created in 1768 days ago, the information mentioned may be changed or developed.

    业务背景:公司采购了一批爱奇艺会员卡密,导入到了 mysql 现在的做法是有用户购买就获取最后一张未使用的, 更新时判断状态(可能已被使用 会失败),如果失败就自旋重试 redis 的方案已经启弃用了 感觉难以保证一致性... 各大 v 友还有啥方案 麻了

    Supplement 1  ·  Jan 6, 2022
    以解决 列队异步消费确实有也不错
    54 replies  •  2022-01-07 00:01:58 +08:00
    lipaa
    lipaa
    OP
    Jan 5, 2022
    1
    失败就自旋重试 感觉也不好 所以..
    Rwing
    Rwing
    Jan 5, 2022
    2
    加锁。。。。
    murmur
    murmur
    Jan 5, 2022
    3
    并发有多大,你们那点销量需要很严格的锁机制么
    murmur
    murmur
    Jan 5, 2022
    4
    最后一张未使用的是什么意思,我感觉这需求就有问题,连 ATM 都知道取钱和存钱是两个箱子

    用户买了这卡,那就死活不退,这口子开不得
    lipaa
    lipaa
    OP
    Jan 5, 2022
    5
    @murmur 按主键排序取第一条未被使用的啊 需求没问题 很正常的需求
    yushiro
    yushiro
    Jan 5, 2022 via iPhone
    6
    这种需求能有多大的并发量啊?取卡密的时候上个锁不行?
    murmur
    murmur
    Jan 5, 2022
    7
    @lipaa 哦,那我理解错了,我还是想问并发,因为你再严格的锁也会遇到用户撕逼,我就跟你死磕你卖我的卡不能用,你不处理么

    所以是不是可以考虑宽松锁剩下的客服处理
    abcbuzhiming
    abcbuzhiming
    Jan 5, 2022
    8
    技术上保证不重发,那就只能加锁变成序列化。除此之外,看看有没有人能在业务模型上提出个新方案来
    Canon1014
    Canon1014
    Jan 5, 2022
    9
    并发不是特别大加个乐观锁就够了吧
    WildCat
    WildCat
    Jan 5, 2022
    10
    很经典的系统设计问题?

    分段加锁?

    https://zhuanlan.zhihu.com/p/104515829
    h82258652
    h82258652
    Jan 5, 2022
    11
    加锁
    数据库获取一张未使用的
    将这张标记为已使用
    释放锁

    并发不高就这么搞吧

    并发高那也只能排队了,这业务就是个库存问题
    murmur
    murmur
    Jan 5, 2022
    12
    @abcbuzhiming 模型就更简单了,直接排队领卡号,锁这部分更好设计,就跟淘宝一样,你买了卡,不是立刻收货,过段时间别人会把卡号通过消息给你发过来,或者直接代充

    那后面的模型更简单了,不需要确认收货,撕逼都省了
    wei745359223
    wei745359223
    Jan 5, 2022
    13
    两张表
    卡密表
    ID,CODE

    发卡表
    ID,USER_ID

    两个表 ID 都是自增,往发卡表里插入数据,返回的 ID 在卡密表里找到就占用。
    jadec0der
    jadec0der
    Jan 5, 2022
    14
    更新时判断状态会失败,是指…乐观锁?
    EileenJ
    EileenJ
    Jan 5, 2022
    15
    pg 里用 skip locked 应该可以实现,mysql 不知道有没有类似的功能
    lipaa
    lipaa
    OP
    Jan 5, 2022
    16
    @Rwing 是的
    lipaa
    lipaa
    OP
    Jan 5, 2022
    17
    @jadec0der 是的
    Chinsung
    Chinsung
    Jan 5, 2022
    18
    上个分布式锁就行了吧,觉得 redis 不可靠就上 zk 。
    你自旋的方案也没什么问题,但是锁肯定更好一些。
    自旋的方案如果想吞吐高点,可以给卡密表分下区,然后再建张表记录表记录每个区里剩几张,在一个事务里更新。
    比如卡密表 10 条记录分区,那记录表就记录 10 (范围)-9 (剩余数量)。然后请求进来就先找一个剩余数量大于 0 的分区去尝试更新。
    如果你卡密数量无限,不怕超发,其实楼上兄弟那个双表自增主键的方案也挺好的
    abigeater
    abigeater
    Jan 5, 2022
    19
    用户点击领取向一个表写入一条记录 并返回 ID 给用户
    然后提供一个接口给前端 通过 Id 获取卡密 让前端轮询调用直到有卡密返回
    再起一个定时任务之类的 队列方式从卡密表拿一条就给领取表填一条

    是不是就解决了。。
    zzvgtiger
    zzvgtiger
    Jan 5, 2022
    20
    @abigeater 通过 id 获取卡密这个操作是如何实现的?类似 13 楼说的那个双表自增主键的方式么?
    zzvgtiger
    zzvgtiger
    Jan 5, 2022
    21
    @abigeater 还有你的那个给领取表填数据的定时任务是不是不太合理,领取表的数据来源只有用户触发领取操作这一个途径才对吧
    Rache1
    Rache1
    Jan 5, 2022
    22
    如果觉得单独引入 redis 做锁成本太高的话,又想用类似可铐的方案,可以用 flock
    boozer
    boozer
    Jan 5, 2022
    23
    说个题外话, 有时候业务问题不该让技术来买单
    hgc81538
    hgc81538
    Jan 5, 2022
    24
    我想到這個方法:

    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, 則成功.
    這方法不用鎖, 請問可行嗎?
    abigeater
    abigeater
    Jan 5, 2022
    25
    @philchang1995 主要目标是将领取这个过程变成异步填充
    和 13 的有区别吧 毕竟我更希望是有 ID 关联 有日志可查
    假设一共有 2 张表卡密表 和 领取表
    卡密表 id,code
    领取表 id,user_id,code
    通过 ID 获取是指,领取表的 ID 找到该条记录 (如果怕被穷举 可以用 hash 或者鉴权 user_id ) 轮询到 code 不为空为止。

    正如你所说领取表数据来源是用户触发,用户写入了 user_id 到领取表后,一个队列(假设是定时任务或者 mq 消费队列)只需要取出 code 等于空的数据 从卡密表获取数据后 update 即可。

    主要目的把这个领取过程交给后台异步队列填充,避免并发。
    ZXCDFGTYU
    ZXCDFGTYU
    Jan 5, 2022
    26
    之前遇到过类似的问题,用 for update 结合 order by 使用次数 asc 解决的
    ganbuliao
    ganbuliao
    Jan 5, 2022
    27
    redis list 不是挺好的吗
    ntdll
    ntdll
    Jan 5, 2022
    28
    这样可以嘛?

    cursor = select ... for update from ... where status = unused limit 1

    update set cursor.status = used

    return cursor
    Pythoner666666
    Pythoner666666
    Jan 5, 2022
    29
    @EileenJ 没有·
    dzdh
    dzdh
    Jan 5, 2022
    30
    难道不是给 code +个 order_id 的字段么
    Pythoner666666
    Pythoner666666
    Jan 5, 2022
    31
    归根结底就是个并发问题。下发的时候加锁,可以解决 99%的下发。剩下 1%的锁冲突就丢到队列,然后消费队列即可。
    lipaa
    lipaa
    OP
    Jan 5, 2022
    32
    @ZXCDFGTYU for update 只是锁住了 b 如果 a 事务释放 b 之后还是可以拿到锁的
    lipaa
    lipaa
    OP
    Jan 5, 2022
    33
    @ganbuliao 领导觉得一致性难以保证
    lipaa
    lipaa
    OP
    Jan 5, 2022
    34
    感谢 大家 还是自旋把 一次性取一大批 再去乐观锁更新 也基本可以保证不出问题
    zzvgtiger
    zzvgtiger
    Jan 5, 2022
    35
    @abigeater 那是我一开始理解错你的队列的意思了,按这样的逻辑来的话确实可以避免发卡的并发操作 只是领卡并发高的时候用户轮询等待卡密这一块可能会等的久一点 不过我个人感觉这个方案可行👍
    whcoding
    whcoding
    Jan 5, 2022
    36
    我公司给你这差不多的一个业务
    whcoding
    whcoding
    Jan 5, 2022
    37
    我公司给你这差不多的一个业务 是这么处理的 导入的时候 同时导入 mysql 和 redis list, 用户来取码直接取 list 中的 然后去走个队列去修改数据库. 完事 ~
    Building
    Building
    Jan 5, 2022
    38
    分成几张表放,再加锁,并发不就好了吗。
    ktqFDx9m2Bvfq3y4
    ktqFDx9m2Bvfq3y4
    Jan 5, 2022 via iPhone
    39
    这种并发应该不高吧,可能使用队列,然后只有一个消费者,外加乐观锁,外加先写日志,就差不多了吧。
    zzvgtiger
    zzvgtiger
    Jan 5, 2022
    40
    @whcoding 这种方案 redis 的集群和数据保持做的好的话还不错 如果不好的话数据一致性和数据丢失确实是个问题
    lu5je0
    lu5je0
    Jan 5, 2022
    41
    update tb set order=xxx limit 1
    lipaa
    lipaa
    OP
    Jan 5, 2022
    42
    @philchang1995 我们一开始就这么做的
    whcoding
    whcoding
    Jan 5, 2022
    43
    @philchang1995 怕 redis 里的数据丢失?
    ETiV
    ETiV
    Jan 5, 2022 via iPhone❤️8
    44
    做成队列,改发放机制成申请制,申请后提示用户“稍后将卡片以短信、邮件发送”

    然后慢慢消费队列就好了

    话说你们不怕灰产、羊毛党吗?
    zzvgtiger
    zzvgtiger
    Jan 5, 2022
    45
    @whcoding 对啊😂
    zzvgtiger
    zzvgtiger
    Jan 5, 2022
    46
    @lipaa 25 楼那种方案么?
    whcoding
    whcoding
    Jan 5, 2022
    47
    @philchang1995 reids 挂掉? 丢数据 可能性不大吧 再说了还有持久化呢 不怕.....
    git00ll
    git00ll
    Jan 5, 2022
    48
    for update 。简单 暴力。性能也不差
    zhoujinjing09
    zhoujinjing09
    Jan 6, 2022
    49
    就正常分布式自增 ID 的解法,每个 ID 对应一个卡密不就行了吗……可能会有跳过但是无所谓吧,你们每天定时任务重新归档一下?
    ryd994
    ryd994
    Jan 6, 2022 via Android❤️1
    50
    比起纠结这个,你更需要销售的日志。对库存表的每一个操作都应当有审计记录。这样就算碰到发错货或者无赖买家也有据可查。
    v2orz
    v2orz
    Jan 6, 2022
    51
    单线程 消费队列下发
    Hug125
    Hug125
    Jan 6, 2022 via iPhone
    52
    @ETiV #44 我司核心业务也是用消息队列异步处理保证数据准确性,就是需要注意消费者多实例部署时加分布式锁,避免两个实例同时消费同一条消息。OP 的业务场景做到准实时也够用了。
    NeezerGu
    NeezerGu
    Jan 6, 2022
    53
    非技术人员好奇问问。
    能不能简单粗暴的吧所有卡密分成 10 分,放在 10 个 redis 分库里,对应 10 个取卡密进程(取卡时加锁排队)。
    取卡的时候做个负载均衡,保证高并发场景能扛得住就行
    印象里 redis 消耗资源不大,一台机开 n 个也行?当然如果内存不够 redis 可以换成 pika ?
    rekulas
    rekulas
    Jan 7, 2022
    54
    @NeezerGu 单机的话 redis 主要瓶颈在内存,多开不能提高什么性能(我怀疑还会降低)
    如果是多机,那不如直接 redis 集群了,还不用考虑分配系统逻辑更简单
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Privacy   ·   Solana   ·   556 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 72ms · UTC 19:44 · PVG 03:44 · LAX 11:44 · JFK 14:44
    ♥ Do have faith in what you're doing.