[分布式设计] 没有 Redis 分布式锁如何保证操作的一致性? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
Charlie17Li
V2EX    程序员

[分布式设计] 没有 Redis 分布式锁如何保证操作的一致性?

  •  1
     
  •   Charlie17Li 2024-09-25 00:44:42 +08:00 3305 次点击
    这是一个创建于 389 天前的主题,其中的信息可能已经有所发展或是发生改变。

    背景

    应用实例部署在多个机房,不同机房的应用实例使用同一个 DB ,但是受 xxx 风险规则,不同机房不能够使用同一个 Redis:

    4871727195768_.pic.jpg

    然而应用中有一个定时任务会按时扫描 DB 中的某个表的记录执行相应的动作 Action ,并将结果回写到这条记录中。

    一个非常直觉的想法是,使用分布式锁实现某个记录只有一个应用实例在操作。但受限于上述规则而无法实现。

    因此,想问下该怎么实现才能保证一个记录不会被多个实例同时操作呢?

    思考

    目前几个非常直觉的想法是:

    1.应用在扫表后会得到一批记录,每次在执行动作 Action 之前,给这个记录加个行锁?理论上是可行的,但不知道这种加锁的方式是否合适以及代价/风险。

    2.每次只让一个机房的应用实例扫表,这样子同机房就可以使用 Redis

    3.给 DB 加个字段让每个机房只搞自己的,当某个机房发生故障的时候手动改配置,让另外一个健康机房可以扫故障机房的单子。

    感觉方法都有点野蛮... 另外这需求其实用消息队列应该是比较优质的解法,但目前规模不大,都不太想用到 Q...

    27 条回复    2024-09-26 14:37:36 +08:00
    mooyo
        1
    mooyo  
       2024-09-25 00:54:45 +08:00   1
    我感觉怎么设计都会有问题,一个最核心的问题是 你的 action 是可以重放的吗。如果不是的话,即使有 redis 分布式锁,你怎么解决如果扫到一个 action ,执行了一半挂了的情况呢。
    zdking08135
        2
    zdking08135  
       2024-09-25 01:09:05 +08:00
    尽量设计成一个实例操作一部分数据,没必要用锁竞争。
    做好监控的话,实例挂了的情况其实很少见,直接处理就行了。
    Maboroshii
        3
    Maboroshii  
       2024-09-25 01:16:32 +08:00   1
    使用 db 实现分布式锁是一样的啊,乐观锁 update
    R4rvZ6agNVWr56V0
        4
    R4rvZ6agNVWr56V0  
       2024-09-25 05:53:58 +08:00
    同意楼上观点
    R4rvZ6agNVWr56V0
        5
    R4rvZ6agNVWr56V0  
       2024-09-25 05:57:48 +08:00
    Aruforce
        6
    Aruforce  
       2024-09-25 08:41:33 +08:00 via Android
    @Maboroshii 这个挺好的
    Goooooos
        7
    Goooooos  
       2024-09-25 08:42:45 +08:00
    db 建一个 lock 表,里面只有一个主键和一个 version ,就能实现乐观锁
    yangtianming
        8
    yangtianming  
       2024-09-25 08:46:26 +08:00   1
    楼主想要实现的就是分布式锁,感觉学的有点死了,分布式锁可以通过 redis 实现,当然也可以通过 mysql 实现
    yangtianming
        9
    yangtianming  
       2024-09-25 08:49:24 +08:00
    @yangtianming #8
    不好意思,惯性思维 mysql 了 ,其他 db 也是一样
    dddd1919
        10
    dddd1919  
       2024-09-25 08:52:49 +08:00
    既然是数据库,那直接行锁啊,如果没有大量并发,用 redis 加锁也属实倒反天罡
    sagaxu
        11
    sagaxu  
       2024-09-25 09:03:38 +08:00   1
    1. 行锁可能会跟其它业务逻辑中的行锁打架,锁时间长了影响业务,加多了还容易死锁,
    2. 如果扫表负载不高,直接指定某个机房负责扫表也行,不用分配也不用轮流
    3. 不用加字段,直接按 ID 分配机房就行,出问题了手动切

    2 或 3 都行,把切机房的开关做方便点
    ys1992
        12
    ys1992  
       2024-09-25 09:08:54 +08:00   1
    "不同机房的应用实例使用同一个 DB" 这不就是天然实现分布式锁的基础设施么,没必要纠结非要使用 redis ,把 redis 分布式锁 setnx 和 expire ,用数据库的方式实现一遍就可以了呀,
    举个例子,来个 lock 表,lock_name 作为唯一索引,加一个 lock_status 状态,expire_time 过期时间,
    获取锁的时候 update lock set lock_status = 1 where lock_name = xxx and (lock_status = 0 or expire_time<now())
    释放锁的时候 update lock set lock_status = 0 where lock_name = xxx and lock_status = 1
    vishun
        13
    vishun  
       2024-09-25 09:17:43 +08:00
    直接最简单的,数据库悲观锁 for update ,隔离级别设置为 RC ,貌似 RR 会有间隙锁导致的死锁。
    roidinev
        14
    roidinev  
       2024-09-25 09:18:26 +08:00
    扫表看起来也是弱一致性动作。也要考虑协调问题
    billbur
        15
    billbur  
       2024-09-25 09:18:50 +08:00
    我们的做法是第一个做好幂等,也就是任务可以被重复执行但不会出现错误,另一个是根据某个字段做分片,取余或者一致型哈希算法去做,一个机房只处理一部分数据
    jorneyr
        16
    jorneyr  
       2024-09-25 09:36:11 +08:00   1
    select for update 语句可以实现分布式锁。
    yh7gdiaYW
        17
    yh7gdiaYW  
       2024-09-25 10:22:58 +08:00   1
    @ys1992 MySQL 在高并发下更新同一条数据可能会出现死锁,不如 Redis 用的省事儿。不过我也只是看过些案例没自己用 mysql 这么加过锁,也不清楚别的数据库是否也有这类问题
    pangdundun996
        18
    pangdundun996  
       2024-09-25 10:23:25 +08:00
    1 ) DB 实现分布式锁
    2 )如果可访问多机房的话,引入调度节点
    dododada
        19
    dododada  
       2024-09-25 10:47:17 +08:00
    7 楼的方案没啥问题,你也没有数据同步什么的,顶天就是个缓存数据不一致
    Charlie17Li
        20
    Charlie17Li  
    OP
       2024-09-2510:55:41 +08:00
    @mooyo 实际上是可以重放的,被扫的这个表实际包含新建的任务和之前执行失败的任务的记录。
    Charlie17Li
        21
    Charlie17Li  
    OP
       2024-09-25 10:56:47 +08:00
    @zdking08135 每个实例操作一部分数据这个有什么最佳实践吗?
    wangliran1121
        22
    wangliran1121  
       2024-09-25 11:22:03 +08:00
    select for update
    8355
        23
    8355  
       2024-09-25 11:46:39 +08:00
    啊?这不是最传统的简单方案就可以解决的吗
    mysql 抢占啊
    增加一个状态字段 假设 active_status = 0 未启动
    按照不同机房你最多有 2 个实例参与抢占
    update table set active_status = 1 where id = xxx
    看影响行数 = 1 视为抢占成功,开始执行就行了。。 其他的 return
    为啥还需要分布式锁之类的,mysql update 一定是原子的啊
    bingNew
        24
    bingNew  
       2024-09-25 17:00:31 +08:00
    -- 创建锁表
    CREATE TABLE distributed_lock (
    lock_name VARCHAR(255) PRIMARY KEY,
    locked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    );

    -- 尝试获取锁
    INSERT INTO distributed_lock (lock_name) VALUES ('my_lock')
    ON DUPLICATE KEY UPDATE locked_at = CURRENT_TIMESTAMP;

    -- 检查是否获取成功
    SELECT COUNT(*) FROM distributed_lock WHERE lock_name = 'my_lock';-- 创建锁表
    CREATE TABLE distributed_lock (
    lock_name VARCHAR(255) PRIMARY KEY,
    locked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    );

    -- 尝试获取锁
    INSERT INTO distributed_lock (lock_name) VALUES ('my_lock')
    ON DUPLICATE KEY UPDATE locked_at = CURRENT_TIMESTAMP;

    -- 检查是否获取成功
    SELECT COUNT(*) FROM distributed_lock WHERE lock_name = 'my_lock';
    happyxhw101
        25
    happyxhw101  
       2024-09-25 19:54:44 +08:00
    @Goooooos 不应该是悲观锁吗?
    desolekk
        26
    desolekk  
       2024-09-25 20:04:32 +08:00
    学习一下
    hapeman
        27
    hapeman  
       2024-09-26 14:37:36 +08:00
    乐观锁版本号
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2581 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 07:53 PVG 15:53 LAX 00:53 JFK 03:53
    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