求助一个高并发的数据校验与保存问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不在回答技术问题时复制粘贴 AI 生成的内容
mercurius
V2EX    程序员

求助一个高并发的数据校验与保存问题

  •  
      mercurius 2023 年 3 月 11 日 2754 次点击
    这是一个创建于 1040 天前的主题,其中的信息可能已经有所发展或是发生改变。
    背景:
    1 、一个商品对应着多个 SKU 数据,它们是一个整体
    2 、SKU 是自定义输入的,但相同店铺下 SKU 不能重复
    3 、因为其他业务逻辑,不能从数据库层面加唯一索引

    流程:
    商品数据在保存前,会先校验数据,查询数据库,确定相同店铺不存在重复 SKU 后才给通过

    伪代码:
    {
    // 数据校验,需查询数据库进行判断
    check();

    // 数据保存
    save();
    }

    问题:
    校验与保存之间存在极短的时间差,高并发下,存在相同 SKU 在该时间差内通过校验,最终导致插入重复 SKU 数据。

    例子:
    商品 A ,其 SKU 有 S1 、S2 、S3
    商品 B ,其 SKU 有 S3 、S4 、S5
    它们恰好在同一时间进行校验,并且通过了校验,到时候该店铺就会出现 S2 的重复 SKU

    自己想的解决方案:
    使用 Redisson 的 RSet.add 方法可以在添加时就判断是否重复,如果添加失败就说明存在上面描述的情况,直接校验失败。
    在查询数据库后,使用 RSet 去缓存这些 SKU ,等到保存成功再删掉缓存,同时为防止服务出问题,设置下 set 的过期时间。

    但这种解决方案,会在短时间内连续调用 RSet.add ,单个商品的次数可能是 1~200 ,考虑到网络开销,感觉性能应该会很差吧……
    大佬们有没有支持在短时间内大量去重并且支持过期 /删除的东西? orz
    布隆过滤器已经被 pass 了,它不支持元素的删除
    21 条回复    2023-03-12 15:07:15 +08:00
    546L5LiK6ZOt
        1
    546L5LiK6ZOt  
       2023 年 3 月 11 日
    Redisson 分布式锁不可以吗,对商品进行加锁
    hhjswf
        2
    hhjswf  
       2023 年 3 月 11 日 via Android
    搞不懂,就特么一个 sku 管理也要高并发吗。。很多个店铺管理员在疯狂添加 sku 吗。。
    ktqFDx9m2Bvfq3y4
        3
    ktqFDx9m2Bvfq3y4  
       2023 年 3 月 11 日 via iPhone
    你都高并发时检测数据库了,那么就算 redis 挂也是在数据库之后。

    所以多虑了。就用 redis 没什么的。
    alexleee
        4
    alexleee  
       2023 年 3 月 11 日
    用分布式缓存,以店铺 ID+SKU 为 key 弄个分布式锁,插入的时候先拿到锁,拿到锁的线程去 check()+save()?
    kwh
        5
    kwh  
       2023 年 3 月 11 日
    redis 自增,通过校验就自增,首次自增会是 1 ,非首次自增就会大于 1 了。
    而且商户保存操作,MySQL 行锁应该也行吧?
    mercurius
        6
    mercurius  
    OP
       2023 年 3 月 11 日
    @546L5LiK6ZOt 有考虑过,但要解决该问题,锁只能加在店铺维度,粒度有点大

    @hhjswf 用爬虫爬别人商品,然后批量创建到自己店铺,那个内部服务直接用多线程打过来时发现的问题……

    @Chad0000 但检测数据库那里只是一条 SQL ,而这里校验连续调用 200 次 RSet.add ,这个性能会不会有点夸张?我对这块没啥概念……

    @alexleee 有考虑过,但这个锁是 SKU 维度,那加锁次数是跟 SKU 数量一致的,而商品与 SKU 是一对多的整体关系,不能说一个个 SKU 单独校验和保存,所以单个商品可能加上几十甚至上百个锁……

    @kwh 自增是商品通过校验,还是 SKU 通过校验?它们是一个整体的,并且是一对多的关系……MySQL 行锁应该不行吧,问题不是保存操作,而是校验操作那里的高并发
    alexleee
        7
    alexleee  
       2023 年 3 月 11 日
    @mercurius 你的外键约束是商品 id+SKU 这两个的组合呀,我理解你说的怕竞争条件下出问题的资源不就是店铺 id+sku 的创建权吗,你又怕不同线程创建太多锁...那你牺牲一点并发性能锁 sku 或者锁店铺吧...
    leonshaw
        8
    leonshaw  
       2023 年 3 月 11 日
    要强一致就要加锁(或者等效于加锁的操作)。考虑到 IO RT ,可以按店铺分区,实现一个批量加锁的接口。不知道有没有现成的轮子。
    mercurius
        9
    mercurius  
    OP
       2023 年 3 月 11 日
    @alexleee 锁店铺粒度大但只锁一个,锁 sku 粒度小但需要连续锁多个(假设按最差的情况为 200 ),我还没实际遇过连续锁这么多次只为单个商品的创建,总觉得不太对劲,这性能正常吗……

    @leonshaw 对,我那个方案其实就相当于等效的加锁操作,但不确定这种单次操作中,包含了几十甚至上百次 redis 请求的性能如何
    546L5LiK6ZOt
        10
    546L5LiK6ZOt  
       2023 年 3 月 11 日
    几十、上百次 redis 请求不算高,腾讯云最低配的 redis 也能支持上万 qps
    546L5LiK6ZOt
        11
    546L5LiK6ZOt  
       2023 年 3 月 11 日
    我觉得严谨的做法还是得在数据库用唯一键约束来做。原先的表不能加唯一键,那就新增一个表,商品和 sku 作为唯一键,在一个事务里插入。用分布式锁不能保证 100% 一致的。
    k9982874
        12
    k9982874  
       2023 年 3 月 11 日 via Android
    sku 生成规则制定好,根本不需要去考虑冲突问题。
    例如商品 sku 由商铺 uid 为前缀+品类 uid+时间戳+用户自定义输入组成,基本上就很难重复
    xwayway
        13
    xwayway  
       2023 年 3 月 11 日 via iPhone
    那要不你扔 mq ?慢慢去保存就是咯
    mercurius
        14
    mercurius  
    OP
       2023 年 3 月 11 日
    @546L5LiK6ZOt 是的,最纯粹的做法就是唯一索引,只是原先表的业务逻辑不支持……不过确实新增一个中间表就能解决,并且比起各种加锁要简单得多,操作只需要批量插入和删除,性能也只是多一两次 SQL 操作。非常感谢,这是新思路!

    @k9982874 背景就是 sku 只能是商家自定义的,要原模原样,不能再在上面改东西……

    @xwayway 这种操作是会同步返回结果的,从同步改成异步,这逻辑变化有点大了
    k9982874
        15
    k9982874  
       2023 年 3 月 11 日 via Android
    @mercurius 非要用锁,11 楼已经给答案了,建一张外键表,sku 加唯一约束,先插入这个表,成功后再插入商品表。
    mercurius
        16
    mercurius  
    OP
       2023 年 3 月 11 日
    @k9982874 是的,11 楼的答案是之前没想到的新思路,完全可行且不复杂,正是我想要的那种

    感谢上述各位的讨论与回复 _(:з」∠)_
    potatowish
        17
    potatowish  
       2023 年 3 月 11 日 via iPhone
    加一个映射表,包含自增 ID 、商铺 UID 、商品品类 ID 、用户自定义 SKU 。自增 ID 作为实际的 SKU ,在业务中使用到 SKU 时只需要查询它对应的实际 SKU 。
    xiaop1ng
        18
    xiaop1ng  
       2023 年 3 月 12 日 via iPhone
    @546L5LiK6ZOt 这个方法算是基于数据库的一种分布式锁吧,我认为和基于 redis 对商品 id+sku 上分布式锁的逻辑是一样的,流程变为 lock->check->save->unlock ,不明白为什么分布式锁不能保证 100%一致,想讨论一下
    546L5LiK6ZOt
        19
    546L5LiK6ZOt  
       2023 年 3 月 12 日
    @xiaop1ng

    参考这里 https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html

    GC 的 STW 可能会导致锁过期了,但是进程还认为占用锁。即使用没有 GC 的语言,操作系统的进程调度也可能会出现这种情况。理论上来说,分布式锁不可能保证完全一致的。还是得靠底层数据库
    bushenx
        20
    bushenx  
       2023 年 3 月 12 日 via Android
    如果 check 只是一条 sql 且场景需要强一致,不如直接上事务吧,select 语句直接上锁。
    xiaop1ng
        21
    xiaop1ng  
       2023 年 3 月 12 日
    @546L5LiK6ZOt 了解,thanks~
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     3138 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 14:05 PVG 22:05 LAX 06:05 JFK 09:05
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86