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

来讨论一下用数据库实现简单分布式锁的问题

  •  
  •   zou8944 · 2023-06-27 16:24:48 +08:00 · 1728 次点击
    这是一个创建于 520 天前的主题,其中的信息可能已经有所发展或是发生改变。

    来讨论一下用数据库实现简单分布式锁的问题

    单纯做技术讨论,使用 PG 数据库实现一个分布式锁。仅考虑锁的正确性,不考虑可重入等功能。我的想法如下。

    create table distribute_locks
    (
        id         varchar     not null primary key,
        expire_at  timestamptz not null,
        created_at timestamptz not null default current_timestamp,
        updated_at timestamptz not null default current_timestamp
    );
    
    • id 作为加锁的 key
    • expire_at 作为锁的过期时间。锁不存在或过期都算锁已被释放,此时其它方可以获取到该锁

    用法

    • 加锁
    insert into distribute_locks (id, expire_at)
    values (:id, now() + interval '1 minute')
    on conflict (id) 
    do update set expire_at = now() + interval '1 minute'
    where distribute_locks.expire_at < current_timestamp
    returning id
    

    有内容返回时获取锁成功,否则获取锁失败

    • 锁续期
    update distribute_locks
    set expire_at = now() + interval '1 minute'
    where id = :id and expire_at > current_timestamp
    

    只有锁存在且过期才能续期,否则续期无效

    • 释放锁
    delete from distribute_locks
    where id = :id
    

    疑问点

    这样设计的锁能满足基本需求了,但还有一个问题没有解决,即如何稳定续期。

    问题点在于,如果我在获取到锁时启动一个线程去续期,那如果当前线程结束,没有主动释放锁。该续期线程要如何结束呢?

    我用的是 python 来做

    第 1 条附言  ·  2023-06-27 17:50:09 +08:00
    修正锁续期的说法:只有锁存在且在有效期内才能续期,否则续期无效
    15 条回复    2023-06-27 23:29:44 +08:00
    opengps
        1
    opengps  
       2023-06-27 16:35:25 +08:00
    我没看明白,这个 distribute_locks 表存在哪,因为我始终都想知道怎么实现的分布式锁。
    因为我关注点是:这到底是多个数据库的锁,还是分布式应用的共享一个库里的行数据作为锁
    zuisong
        2
    zuisong  
       2023-06-27 17:38:38 +08:00
    设置一个最大续期次数?
    liprais
        3
    liprais  
       2023-06-27 17:39:35 +08:00
    先想想隔离级别的事
    zou8944
        4
    zou8944  
    OP
       2023-06-27 17:51:01 +08:00
    @opengps 后者
    zou8944
        5
    zou8944  
    OP
       2023-06-27 17:51:47 +08:00
    @zuisong 不可行,这个和设置一个超长的锁有效期没有本质区别
    zou8944
        6
    zou8944  
    OP
       2023-06-27 17:52:02 +08:00
    @liprais 为什么要想隔离级别的事情?
    leonshaw
        7
    leonshaw  
       2023-06-27 18:04:55 +08:00
    续期的时候不看所有权?
    zou8944
        8
    zou8944  
    OP
       2023-06-27 18:06:40 +08:00
    @leonshaw 所有权也要看,这里漏掉了
    lolizeppelin
        9
    lolizeppelin  
       2023-06-27 18:14:24 +08:00
    直接 zk 或者 etcd 做不就行了....为什么折腾 pg
    数据库做锁没法支持连接断开后清理锁,用 expire_at 很别扭的

    字段里加个 lokcer 存放 uuid, 这个 uuid 由于获得上锁的客户端生成, 由于这个 uuid 只有上锁的客户端才知道,这样就可以做到过期前只有指定的 locker 才能释放

    上锁
    update lock set locker = 'fffffffffffffffffffffffffffffffffffff' where id = 'locker-id' and locker is null

    放锁
    update lock set locker = null where id = 'locker-id' and locker = 'fffffffffffffffffffffffffffffffff'
    shinyruo2020
        10
    shinyruo2020  
       2023-06-27 18:41:23 +08:00
    没看懂,为什么发现冲突时是去续期呢?不用判断当前线程是否持有锁的吗?
    shinyruo2020
        11
    shinyruo2020  
       2023-06-27 18:42:32 +08:00
    噢噢,看到你上面的回复了,sry
    voidmnwzp
        12
    voidmnwzp  
       2023-06-27 18:46:13 +08:00 via iPhone
    分布式锁不都是 zk 或者 redis 不会用硬盘数据库吧
    rrfeng
        13
    rrfeng  
       2023-06-27 19:00:14 +08:00
    只要逻辑没错,你用啥都行。

    只要是可靠的强一致性的支持事务的存储,都可以用来做分布式锁。很多时候没必要引入 redis/zk 。
    xiangyuecn
        14
    xiangyuecn  
       2023-06-27 19:22:16 +08:00
    删除也一样要校验所有权。简单加一个随机值,谁持有这个随机值就代表谁持有这个锁,可删除和更新(不管过期不过期都能操作,逻辑上简单粗暴)。
    wqtacc
        15
    wqtacc  
       2023-06-27 23:29:44 +08:00
    感觉现在的实现,如果是多个客户端,加锁时的 id 怎么分配的,冲突就展期,防止不了冲突,释放锁也是一样的
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2494 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 16:06 · PVG 00:06 · LAX 08:06 · JFK 11:06
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.