Distributed Locks with Redis
分布式锁原则
- 安全性:任意时刻,只有一个客户端能持有锁,保证临界资源的安全访问。
- 可用性:无死锁,锁总是能够在某一时刻被释放,即使持有锁的客户端未释放锁便意外结束。
- 容错性:只要集群中大多数节点存活,客户端就能正常加锁释放锁。
Redis-based 分布式锁
到 Redlock 之前都基于单实例 redis 实现。
SETNX + EXPIRE
1 | lock_key = "xxx" |
setnx
和 expire
不是原子操作,如果 setnx
后正要 expire
时客户端进程 crash 或者 Redis 重启,锁将无法得到释放。
SETNX + value=datetime
1 | lock_key = "xxx" |
现在获取锁操作成为了一个原子操作,但是这种方式存在以下问题:
- 各个客户端时钟必须保持同步
- 锁过期时多个客户端同时请求锁,可能出现一个客户端获取成功,但是过期时间被其它客户端覆盖
SET EX PX NX
1 | lock_key = "xxx" |
这种方式问题在于,锁可能被其它客户端释放。
SET EX PX NX + unique value
1 | lock_key = "xxx" |
锁的释放不是原子操作,在并发环境下还是可能被其它客户端释放锁。
Lua script
1 | if redis.call('get', KEYS[1]) == ARGV[1] then |
通过 Lua 脚本组合多个操作,实现原子化操作。
Extending lock
当获取锁成功后,为锁持有线程开启一个守护线程,定时检测锁并延长锁的 TTL。
到这里基本上解决了安全性和可用性。
Redlock
以上实现方式在单实例情况下够用了,如果想实现更高的容错性,就需要引入多实例的分布式实现。
N 个独立的实例(无 replica)。
客户端按照如下步骤获取锁:
- 获取当前时间戳(initia timestamp)。
- 使用相同的 key 和 random value 顺序地向 N 个 Redis 实例获取锁,获取锁的超时时间比锁的生存时间小很多,比如超时时间是 5~50ms,锁生存时间是 10s。
- 只有当成功获取到 N/2 + 1 个锁,并且总 elapsed time 小于锁的生存时间才认为成功获取到了锁。
- 成功获取到锁后,锁的有效时间 = initial validity time - elapsed time。
- 如果客户端未能成功获取到锁,它需要释放已经获取到的部分锁。
Key takeaways
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 爱吃胡萝卜!