Redis 分布式锁
[TOC]
本地加锁原理
解决并发写竞争的方法:
- 原子操作
- 加锁
本地加锁实现:
- 加锁:使用 CAS 尝试将锁变量由 0 改成 1
- 释放锁:将锁变量由 1 改成 0
todo: AQS/ReentrantLock 源码分析
分布式锁实现就是将锁变量放到共享存储系统(Redis)上,使不同的客户端可以操作该锁变量。
单节点 Redis 分布式锁
实现分布式锁需要考虑的问题:
- 加锁/释放锁操作的原子性(单命令或 Lua 脚本)
- 连接超时(和远程调用一样,需要考虑调用超时情况)
- 加锁后客户端发生异常,锁能够自动超时释放,避免死锁
- 释放锁时,只有持有锁的客户端才能释放锁,避免锁被意外释放
- 可靠性,需要考虑实例宕机故障的情况,通过多 Redis 节点和算法保证锁的可靠性
Redis 单节点分布式锁实现:
# 加锁(Redis 命令)
# id 可以唯一标识客户端的值
# NX 当键不存在时,才会进行设置(通过设置是否成功隐式表示锁变量)
# PX 过期时间(毫秒),EX 过去时间(秒)
SET lock_key id NX PX 10000
# 释放锁(Lua 脚本)
# KEYS[1]: lock_key
# ARGV[1]: id
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0end
todo: 了解 Redisson 分布式锁实现
多节点 Redis 分布式锁
为了避免单节点 Redis 故障,提高锁的可靠性,就需要使用多节点 + 分布式锁算法实现。
Redis RedLock 算法:
# 加锁
1. 获取开始时间
2. 尝试对每个节点进行加锁(需要设置连接超时,并且需要远远小于锁的有效时间,一般是几十毫秒)
3. 判断是否对超过半数的节点(N/2 + 1)加锁成功,否则加锁失败
4. 重新计算锁的有效剩余时间(最初有效时间-获取锁的总耗时),如果判断剩余时间能完成操作,加锁成功;否则加锁失败。
(第四步是为了防止业务还没执行完,锁就过期自动释放的情况)
# 释放锁
1. 依次对所有节点执行释放锁操作
RedLock 保证了只要半数以上的节点是正常的,锁就能正常工作。
todo:
- 了解 Redis RedLock 算法
- 评论区提示 RedLock 已被不官方推荐使用
参考
- 《Redis 核心技术与实战》