分布式锁的实现

满足的条件

互斥性

1
任意时刻,只有一个客户端持有锁

不会发生死锁

1
即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。

解铃还须系铃人

1
加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

原子性

1
加锁和解锁必须具有原子性

步骤

一、多个客户端申请获取锁

1
即执行多个setnx语句

二、获取成功

1
即执行setnx语句成功后的客户端,其他客户端等待重试

三、执行业务逻辑

1
db获取数据,放入缓存

四、释放锁

1
执行del语句

总结

1
以上步骤解决了互斥性

问题一

1
如果业务逻辑出现异常,导致锁无法释放

解决方案

1
设置锁的过期时间,解决死锁问题

问题二

1
2
3
4
5
6
7
8
在解决问题一后,可能存在释放了其他客户端上的锁,现象如下:

1. index1业务逻辑没执行完,3秒后锁被自动释放。
2. index2获取到锁,执行业务逻辑,3秒后锁被自动释放。
3. index3获取到锁,执行业务逻辑
4. index1业务逻辑执行完成,开始调用del释放锁,这时释放的是index3的锁,导致index3的业务只执行1s就被别人释放。

最终等于没锁的情况。

解决方案

1
2
3
setnx获取锁时,设置一个指定的唯一值(例如:uuid);释放前获取这个值,判断是否自己的锁

解决了上锁和释放锁是同一个客户端

问题三

1
2
3
在解决问题二后,删除操作缺乏原子性,现象如下:

可能存在有一个客户端刚好执行到删除del语句,而且已经完成了uuid的判断,但已经过期,此时第二个客户端开始上锁,可恰好第一个客户端开始执行del语句,导致第二个客户端的lock被删除

解决方案

1
2
3
LUA脚本保证删除的原子性

解决了原子性

与Zookeeper分布式锁的差异

1
2
Redis性能高,但是需要多重优化,而且客户端需要不断去尝试获取锁,比较耗性能
Zookeeper可靠性强,不需要不断主动尝试获取锁,只需要负责监听前一个节点即可,性能开销较小

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!