V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
lipaa
V2EX  ›  程序员

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

  •  
  •   lipaa · 173 天前 · 4419 次点击
    这是一个创建于 173 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

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

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

    分段加锁?

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

    并发不高就这么搞吧

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

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

    发卡表
    ID,USER_ID

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

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

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

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

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

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

    update set cursor.status = used

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

    然后慢慢消费队列就好了

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