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

有个关于并发的问题想请教下

  •  1
     
  •   jtping · 2021-01-05 18:04:12 +08:00 · 2136 次点击
    这是一个创建于 1208 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近在做一个电商项目,现在到了扣库存这一块,扣库存时有什么好的上锁方案吗 (要有一定的并发能力)

    具体是这样的:

    最开始用 redisson 分布式锁把整个生成订单事务都锁了(扣库存操作在里面),但这样效率不高。

    现在改用悲观锁(用的 MySQL ),可感觉悲观锁的效率还是不够高,我也去网上找过,都不太建议库用悲观锁。

    想过把库存信息都放到 reids 中,把所有库存信息都放到 reids 中可行吗?有没有什么弊端

    之前这方面接触的也不多,有没有什么好的解决方案,望大神赐教

    25 条回复    2021-01-06 08:52:42 +08:00
    jtping
        1
    jtping  
    OP
       2021-01-05 18:11:06 +08:00
    自定
    xiaomu8
        2
    xiaomu8  
       2021-01-05 18:17:19 +08:00
    开发日活百万
    上线日活一百
    jtping
        3
    jtping  
    OP
       2021-01-05 18:19:43 +08:00
    @xiaomu8 哈哈哈 还是要稍微做点准备的 万一火了呢
    YouLMAO
        4
    YouLMAO  
       2021-01-05 19:39:26 +08:00 via Android
    一般都是这样的,为什么效率不高? 有没有分析过生成订单最慢的 top 5 操作
    jtping
        5
    jtping  
    OP
       2021-01-05 19:48:50 +08:00
    @YouLMAO 这个还真不了解 望指点
    simonlu9
        6
    simonlu9  
       2021-01-05 19:48:57 +08:00
    库存采用分段锁,可以提高效率
    jtping
        7
    jtping  
    OP
       2021-01-05 19:51:23 +08:00
    @simonlu9 应该还不到用分段锁的地步
    opengps
        8
    opengps  
       2021-01-05 19:52:45 +08:00   ❤️ 2
    库存放在 redis 很常见,弊端就是对逻辑要求比较严谨,需要综合考虑各种极端情况,比如:
    断电怎么办
    锁是否彻底能够做到库存不超额
    数据库变更时候缓存刷新是否有残留变更
    等等
    YouLMAO
        9
    YouLMAO  
       2021-01-05 20:00:05 +08:00 via Android   ❤️ 1
    我觉得网民和我讨论场景不同,他们是考虑秒杀? 我是考虑有 5 千万商品在售,每件商品 50 库存,一分钟有 1 万人下单但下的是不同商品呀
    jtping
        10
    jtping  
    OP
       2021-01-05 20:00:51 +08:00
    @opengps 学习了 感谢
    securityCoding
        11
    securityCoding  
       2021-01-05 20:02:34 +08:00
    独立库存组件出来 , 配合 redis 用 write through(这玩意不知道咋翻译)方式管理库存 ,后面挂个队列异步通知 db 扣减库存(最终一致性)
    YouLMAO
        12
    YouLMAO  
       2021-01-05 20:03:55 +08:00 via Android
    不是秒杀场景用 Redis,可以准备跑路
    securityCoding
        13
    securityCoding  
       2021-01-05 20:04:31 +08:00
    @YouLMAO 5000w sku 你真敢想...
    jtping
        14
    jtping  
    OP
       2021-01-05 20:04:49 +08:00
    @YouLMAO 我就是这个意思!用悲观锁 万一有其他请求进来就要等了 用户体验不太好
    jtping
        15
    jtping  
    OP
       2021-01-05 20:06:03 +08:00
    @YouLMAO 没呢 项目还没上线
    YouLMAO
        16
    YouLMAO  
       2021-01-05 20:06:28 +08:00 via Android
    @jtping 那你是秒杀还是非秒杀???下单的是不同商品,不会互相锁库存喔为什么要等
    jtping
        17
    jtping  
    OP
       2021-01-05 20:07:02 +08:00
    @securityCoding 方便给个关键词吗 我去网上找找
    YouLMAO
        18
    YouLMAO  
       2021-01-05 20:08:17 +08:00 via Android
    @jtping 分段锁也是秒杀场景的,直接百度
    securityCoding
        19
    securityCoding  
       2021-01-05 20:08:26 +08:00   ❤️ 1
    @jtping 小伙子 ,你是不是分布式锁把整个下单接口都锁住了? 锁下单商品那个 sku 就行了
    jtping
        20
    jtping  
    OP
       2021-01-05 20:08:55 +08:00
    @YouLMAO 就是普通的购买 比如 1 秒内对同一个商品下单 10 次 这个就需要等了(这种情况应该会出现吧)
    jtping
        21
    jtping  
    OP
       2021-01-05 20:10:00 +08:00
    @securityCoding 哈哈刚开始不懂事 就是这么锁的 后来改成行锁了
    securityCoding
        22
    securityCoding  
       2021-01-05 20:18:46 +08:00
    @jtping 相关操作都先通过库存组件 ,DB 保持最终一致性,缓存策略看看这篇文章吧
    https://coolshell.cn/articles/17416.html
    jtping
        23
    jtping  
    OP
       2021-01-05 20:25:55 +08:00
    @securityCoding 感谢!
    crclz
        24
    crclz  
       2021-01-05 21:21:37 +08:00   ❤️ 4
    @opengps #8 楼说的非常正确,要考虑的情况很多,逻辑要求应当非常严谨。

    现在市面上的文章很多都没考虑到数据的一致性。(例如 redis 扣减库存,然后 DB 再慢慢处理订单)


    经过分析,不难发现,redis+DB 有以下两个主要 ACID 方面的问题:

    1. "ACID.Consistency"(一致性):因为 redis 和 DB 是两个数据储存,所以涉及到分布式事务。
    分布式事务一般有如下两种选择:A. 采用某种协议,例如 Paxos,保证强一致性。B. 采用消息队列+补偿来保证最终一致性。

    2. "ACID.Duration"(持久性):redis 断电后如何恢复?
    一般可以采用的是:A. 副本集 B. 平行系统( Parallel Model - Martin Fowler )

    对于“一致性”问题,A 方案(分布式事务协议)是不现实的,因为 redis 本身的事务支持就不完备。如果采用 B 方案,用消息队列,也是无法实现的,因为 outbox pattern (自己去查英文资料)依赖 ACID 的单个数据库的事务,而 redis 本身的事务支持就不完备。

    现在,我们发现,万恶之源在于 —— redis 对事务的支持不完备。

    -------

    但是,我们可以转换一下思路,把 redis 的角色从更偏向 DB 的角色转换成更偏向 cache 的角色。
    这时候就应当引入 [软状态] :状态定期过期并刷新。

    简单来说,就是定期把剩余库存定期更新到 redis 。

    接下来详细说一下:

    假设我们的步骤如下:
    1. 连接 redis,库存-1,如果成功(可以使用 lua 脚本来保证原子性),那么就进行下一步;如果失败,就提示已售罄。
    2. 连接数据库,创建订单(或者为了削峰,写消息队列,事后慢慢在 DB 创建订单)

    但是,这两步不是原子性的,会造成 redis 和 DB 的状态不一致:
    如果 redis 扣减库存成功了,但是连接数据库失败了,那么就会存在少卖的情况。但是不会超卖。幸运的是,少卖比超卖好解决。

    这时候,只需要在消息队列里面的订单处理完成后,将 DB 里面真实的剩余库存同步到 redis 即可。
    jtping
        25
    jtping  
    OP
       2021-01-06 08:52:42 +08:00
    @crclz 多谢赐教
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2678 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 10:39 · PVG 18:39 · LAX 03:39 · JFK 06:39
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.