重拾分布式锁

分布式锁解决的是同一个资源在分布式系统中竞争的问题。同一时刻一个资源只能被一个线程占用。比如修改同一条数据,这条数据背后对应的实际业务含义可能是商品、账户等。 说到分布式说,老生常谈的是redis和zookeeper。本文就按从这两个角度展开讨论。 

先给出分布式锁的基本流程:

分布式锁的基本流程是:客户端获取锁 → 执行业务 → 释放锁(DEL)。

redis的实现

redis的set命令有个NX参数,能实现如果key不存在就插入,如果key存在就无法插入的能力。因此,redis就用setNX来实现分布式锁:

1、key存在,数据插入失败:上锁失败。

2、key不存在,数据插入成果:上锁成果。

命令如下:

SET lock_key value NX //版本1

到这里,我们已经用redis实现了简单版本的distributed lock。这么做会有哪些问题呢?首先,如果获取锁的进程挂了,其他线程会一直等,就是deadlock。为了防止这种情况发生,我们引入了expired time。

SET lock_key value NX PX 1000 //版本2,上锁是能保证原子性的

版本2是不是万无一失了呢?也不一定。做分布式的都知道一旦引入超时这个变量就会有另一个问题:执行业务真的会在lease time内完成吗?gc了咋办?业务异常咋办?网络抖动咋办?

此时A进程拿到了锁,陷入了full gc。在lease time内没有醒来。锁过期被释放了。B拿到了这把锁,对同一条数据进行修改了。A恢复后对同一条数据进行了修改,把锁给释放了!

这里有两个问题:同一条数据A和B都改了。A把B的给覆盖了,而且A释放了别人的锁!此时C有可能拿这把锁修改同一条数据。这可咋整啊!

于是,出现了第三个版本的distributed lock:

SET lock_key unique_value NX PN 10000 //版本3, 上锁时的value是唯一的,比如trace id等

在释放锁时对value进行判断,如果是自己的锁,才能释放:

// 释放锁时,先比较 unique_value 是否相等,避免锁的误释放
if redis.call("get",KEYS[1]) == "my_unique_value" then
    return redis.call("del",KEYS[1])
else
    return 0
end

由于上面的代码先要get value和自己的my_unique_value做比较,然后决定是否删除。这是两步操作,因此要用Lua脚本确保操作的原子性。这样基本上就不会有误删其他线程锁的操作了。

版本三的实现和Redission已经非常接近了,redission多了一个watch dog。watch dog解决的还是lease time内业务不一定能跑完的问题。如果没有watch dog,在lease time内还没完成业务执行,锁被释放被别人拿到,会引入并发问题。如果lease time设置太长行不行,如果A业务逻辑正常之行没问题,但如果跑到一半挂了,由于lease time过长,白白占用了很多时间。综合以上原因,watch dog的引入解决了这个问题。每隔Xms去续期一次,最多浪费短短的lease time的时间。

缺陷:主从模式下客户端拿到锁后,master还没备份到slave就挂了。导致其他进程上锁成功。因此引入了集群模式的redLock。

RedLock

设计理念是集群模式下,client必须拿到过半数的锁才算成功。执行成功后释放所有锁。

如果上锁失败,必须把此轮set NX成功的节点数据释放,否则影响他人上锁。

Zookeeper的实现

//TODO

 

posted @ 2026-11-01 23:52  ACityWalker  阅读(3)  评论(0)    收藏  举报