V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
Charlie17Li
V2EX  ›  程序员

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

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

    背景

    应用实例部署在多个机房,不同机房的应用实例使用同一个 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  
       83 天前   ❤️ 1
    我感觉怎么设计都会有问题,一个最核心的问题是 你的 action 是可以重放的吗。如果不是的话,即使有 redis 分布式锁,你怎么解决如果扫到一个 action ,执行了一半挂了的情况呢。
    zdking08135
        2
    zdking08135  
       83 天前
    尽量设计成一个实例操作一部分数据,没必要用锁竞争。
    做好监控的话,实例挂了的情况其实很少见,直接处理就行了。
    Maboroshii
        3
    Maboroshii  
       83 天前   ❤️ 1
    使用 db 实现分布式锁是一样的啊,乐观锁 update
    GeekGao
        4
    GeekGao  
       83 天前
    同意楼上观点
    GeekGao
        5
    GeekGao  
       83 天前
    Aruforce
        6
    Aruforce  
       83 天前 via Android
    @Maboroshii 这个挺好的
    Goooooos
        7
    Goooooos  
       83 天前
    db 建一个 lock 表,里面只有一个主键和一个 version ,就能实现乐观锁
    yangtianming
        8
    yangtianming  
       83 天前   ❤️ 1
    楼主想要实现的就是分布式锁,感觉学的有点死了,分布式锁可以通过 redis 实现,当然也可以通过 mysql 实现
    yangtianming
        9
    yangtianming  
       83 天前
    @yangtianming #8
    不好意思,惯性思维 mysql 了 ,其他 db 也是一样
    dddd1919
        10
    dddd1919  
       83 天前
    既然是数据库,那直接行锁啊😂,如果没有大量并发,用 redis 加锁也属实倒反天罡
    sagaxu
        11
    sagaxu  
       83 天前   ❤️ 1
    1. 行锁可能会跟其它业务逻辑中的行锁打架,锁时间长了影响业务,加多了还容易死锁,
    2. 如果扫表负载不高,直接指定某个机房负责扫表也行,不用分配也不用轮流
    3. 不用加字段,直接按 ID 分配机房就行,出问题了手动切

    2 或 3 都行,把切机房的开关做方便点
    ys1992
        12
    ys1992  
       83 天前   ❤️ 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  
       83 天前
    直接最简单的,数据库悲观锁 for update ,隔离级别设置为 RC ,貌似 RR 会有间隙锁导致的死锁。
    roidinev
        14
    roidinev  
       83 天前
    扫表看起来也是弱一致性动作。也要考虑协调问题
    billbur
        15
    billbur  
       83 天前
    我们的做法是第一个做好幂等,也就是任务可以被重复执行但不会出现错误,另一个是根据某个字段做分片,取余或者一致型哈希算法去做,一个机房只处理一部分数据
    jorneyr
        16
    jorneyr  
       83 天前   ❤️ 1
    select for update 语句可以实现分布式锁。
    yh7gdiaYW
        17
    yh7gdiaYW  
       83 天前   ❤️ 1
    @ys1992 MySQL 在高并发下更新同一条数据可能会出现死锁,不如 Redis 用的省事儿。不过我也只是看过些案例没自己用 mysql 这么加过锁,也不清楚别的数据库是否也有这类问题
    pangdundun996
        18
    pangdundun996  
       83 天前
    1 ) DB 实现分布式锁
    2 )如果可访问多机房的话,引入调度节点
    dododada
        19
    dododada  
       83 天前
    7 楼的方案没啥问题,你也没有数据同步什么的,顶天就是个缓存数据不一致
    Charlie17Li
        20
    Charlie17Li  
    OP
       83 天前
    @mooyo 实际上是可以重放的,被扫的这个表实际包含新建的任务和之前执行失败的任务的记录。
    Charlie17Li
        21
    Charlie17Li  
    OP
       83 天前
    @zdking08135 每个实例操作一部分数据这个有什么最佳实践吗?
    wangliran1121
        22
    wangliran1121  
       83 天前
    select for update
    8355
        23
    8355  
       83 天前
    啊?这不是最传统的简单方案就可以解决的吗
    mysql 抢占啊
    增加一个状态字段 假设 active_status = 0 未启动
    按照不同机房你最多有 2 个实例参与抢占
    update table set active_status = 1 where id = xxx
    看影响行数 = 1 视为抢占成功,开始执行就行了。。 其他的 return
    为啥还需要分布式锁之类的,mysql update 一定是原子的啊
    bingNew
        24
    bingNew  
       83 天前
    -- 创建锁表
    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  
       82 天前
    @Goooooos 不应该是悲观锁吗?
    desolekk
        26
    desolekk  
       82 天前
    学习一下
    hapeman
        27
    hapeman  
       82 天前
    乐观锁版本号
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5116 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 09:43 · PVG 17:43 · LAX 01:43 · JFK 04:43
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.