分布式锁深度实战:从 Redis 到 Zookeeper 深度解析
核心要点
分布式锁是分布式系统中保证资源互斥访问的核心技术:
- 需要理解分布式锁的三大必要条件(互斥、无死锁、高可用)
- 需要掌握 Redis 和 Zookeeper 分布式锁的实现原理
- 需要了解 Redis AP 系统和 Zookeeper CP 系统的本质区别
- 需要深入理解 Redisson 看门狗机制和 Curator 框架的羊群效应优化
本文将深入对比 Redis 和 Zookeeper 分布式锁的实现原理,帮助你在实际项目中做出正确的技术选型。
一、分布式锁基础
1.1 为什么需要分布式锁
在分布式系统中,多个服务实例可能同时访问共享资源,我们需要一种机制来保证资源在同一时刻只能被一个实例访问:
解决方案:分布式锁
分布式锁
Redis 实现
Zookeeper 实现
资源互斥访问
分布式场景:本地锁失效
用户请求
Tomcat 集群
多个实例同时竞争
synchronized 只在单实例有效
数据不一致!
单机场景:本地锁够用
用户请求
Tomcat 实例
synchronized 互斥
访问共享资源
1.2 分布式锁应满足的条件
一个合格的分布式锁必须满足以下条件:
分布式锁必要条件
互斥性
任意时刻只能有一个客户端持有锁
无死锁
锁在持有客户端崩溃时能自动释放
原子性
加锁和解锁操作必须是原子的
可重入
同一客户端可多次获取同一把锁
高性能
高并发下性能要足够好
| 条件 | 说明 | 实现方式 |
|---|---|---|
| 互斥性 | 任意时刻只能有一个客户端持有锁 | SETNX、临时顺序节点 |
| 无死锁 | 持有锁的客户端崩溃后,锁能自动释放 | TTL 过期、临时节点自动删除 |
| 原子性 | 加锁和解锁必须是原子操作 | Lua 脚本、事务 |
| 可重入 | 同一客户端可多次获取同一把锁 | 锁计数器、线程 ID 记录 |
| 高性能 | 高并发下性能要足够好 | Redis QPS 10万+ |
1.3 本地锁 vs 分布式锁
| 特性 | 本地锁 | 分布式锁 |
|---|---|---|
| 作用范围 | 单 JVM 进程 | 多 JVM 进程/多机器 |
| 实现方式 | synchronized、ReentrantLock | Redis SETNX、Zookeeper 临时节点 |
| 性能 | 非常高(无网络开销) | 有网络开销 |
| 可靠性 | 单点故障 | 依赖外部系统可靠性 |
| 复杂度 | 简单 | 复杂 |
1.4 典型应用场景
应用场景
秒杀场景
库存扣减
保证库存不超卖
分布式任务
订单处理
保证幂等性
定时任务
资源分配
保证唯一执行
| 场景 | 说明 | 分布式锁作用 |
|---|---|---|
| 秒杀/抢购 | 同一商品同时只能被一个用户购买 | 库存扣减时加锁,防止超卖 |
| 订单处理 | 同一订单只能被处理一次 | 防止重复处理 |
| 定时任务 | 同一任务只能被执行一次 | 防止重复执行 |
| ID 生成器 | 全局唯一 ID | 防止 ID 重复 |
| 乐观锁更新 | 防止并发更新冲突 | CAS 操作的变体 |
二、Redis 分布式锁
2.1 Redis 分布式锁实现原理
Redis 分布式锁的核心是 SET key value NX EX seconds 命令,这条命令保证加锁操作的原子性:
Redis SETNX 加锁流程
成功
失败
SET lock_key unique_id NX EX 30
返回 OK?
获取锁成功
获取锁失败,返回 false
执行业务逻辑
释放锁(Lua 脚本)
2.2 Redis SETNX 基础实现
java
/**
* Redis 分布式锁基础实现
*/
public class RedisDistributedLock {
private RedisTemplate<String, String> redisTemplate;
/**
* 尝试获取锁
* @param lockKey 锁的 key
* @param requestId 客户端唯一标识(UUID)
* @param expireTime 过期时间(秒)
* @return 是否获取成功
*/
public boolean tryLock(String lockKey, String requestId, long expireTime) {
// SET key value NX EX seconds - 原子性操作
Boolean result = redisTemplate.opsForValue()
.setIfAbsent(lockKey, requestId,
Duration.ofSeconds(expireTime));
return Boolean.TRUE.equals(result);
}
/**
* 释放锁 - 使用 Lua 脚本保证原子性
* @param lockKey 锁的 key
* @param requestId 客户端唯一标识
* @return 是否释放成功
*/
public boolean releaseLock(String lockKey, String requestId) {
// Lua 脚本:只有锁的值等于请求 ID 时才删除
String luaScript =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
Long result = redisTemplate.execute(
new DefaultRedisScript<>(luaScript, Long.class),
Collections.singletonList(lockKey),
requestId
);
return result != null && result > 0;
}
}
2.3 Redisson 实现机制(看门狗、自动续期)
Redisson 是 Java 中最流行的 Redis 客户端之一,其分布式锁实现包含了完善的看门狗机制:
TTL 续期示例
初始 TTL = 30秒
10秒后:第一次续期 → TTL = 30秒
20秒后:第二次续期 → TTL = 30秒
...
Redisson 看门狗机制
是
否
获取锁成功
启动看门狗定时器
每 1/3 TTL 时间检查
锁仍被当前客户端持有?
续期 TTL
不做任何操作
java
/**
* Redisson 分布式锁完整使用示例
*/
public class RedissonLockExample {
private RedissonClient redissonClient;
/**
* 获取锁(可重入)
*/
public void executeWithLock(String lockKey, Runnable task) {
// 1. 获取锁(默认 TTL = 30秒,看门狗自动续期)
RLock lock = redissonClient.getLock(lockKey);
try {
// 2. 尝试获取锁,等待 10 秒,锁定 30 秒
// waitTime = 10s:等待获取锁的最大时间
// leaseTime = 30s:锁的自动释放时间
// 如果设置 leaseTime,看门狗不会生效
// 如果不设置 leaseTime,看门狗会自动续期
boolean locked = lock.tryLock(10, 30, TimeUnit.SECONDS);
if (locked) {
try {
// 3. 执行业务逻辑
task.run();
} finally {
// 4. 释放锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
/**
* 公平锁示例
*/
public void fairLockExample(String lockKey, Runnable task) {
// 获取公平锁(FIFO 顺序获取)
RLock fairLock = redissonClient.getFairLock(lockKey);
try {
fairLock.lock();
task.run();
} finally {
fairLock.unlock();
}
}
/**
* 读写锁示例
*/
public void readWriteLockExample(String lockKey) {
RReadWriteLock rwLock = redissonClient.getReadWriteLock(lockKey);
// 读锁:多个线程可以同时持有
RLock readLock = rwLock.readLock();
readLock.lock();
try {
// 读取操作
} finally {
readLock.unlock();
}
// 写锁:独占
RLock writeLock = rwLock.writeLock();
writeLock.lock();
try {
// 写入操作
} finally {
writeLock.unlock();
}
}
}
2.4 RedLock 红锁算法原理
RedLock 是 Redis 作者 Antirez 提出的分布式锁算法,用于提高分布式锁的可靠性:
RedLock 示意图
加锁成功
加锁成功
加锁成功
加锁失败
加锁失败
Redis 1
✓
Redis 2
✓
Redis 3
✓
Redis 4
✗
Redis 5
✗
成功 3/5 >= 3
RedLock 红锁算法
是
否
获取 N 个 Redis 实例
在 N 个实例上获取锁
成功获取 >= N/2+1 个锁?
获取锁成功
获取锁失败,释放所有锁
执行业务逻辑
释放所有实例的锁
2.5 Redis 分布式锁的坑
Redis 分布式锁常见问题
主从切换问题
Master 宕机,Slave 未同步锁
新 Master 没有锁数据
其他客户端获取同一把锁
时钟漂移问题
Redisson 使用定时器续期
如果时间回拨,可能提前释放
锁过期业务未完成
业务执行时间超过 TTL
业务还没执行完,锁就释放了
其他客户端进入,数据不一致
非原子性操作
check-then-act 模式
检查锁存在后再操作
中间可能被其他客户端篡改
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 主从切换导致锁丢失 | Redis 主从异步复制 | 使用 RedLock,需要多个独立 Redis 实例 |
| 时钟漂移 | 分布式系统时间不同步 | Redisson 使用单节点内时间,外部时间漂移不影响 |
| 锁提前释放 | 业务执行时间 > TTL | 看门狗自动续期,或设置合理 TTL + 业务异步补偿 |
| 非原子性 | check-then-act | 使用 Lua 脚本保证原子性 |
三、Zookeeper 分布式锁
3.1 Zookeeper 数据模型
Zookeeper 是一个分布式协调服务,其核心是树形数据结构和 Watch 机制:
节点类型
PERSISTENT
持久节点
EPHEMERAL
临时节点(客户端断开自动删除)
SEQUENCE
顺序节点(自动编号)
Zookeeper 数据模型
根节点 /
/locks
/locks/order-0000000001
/locks/order-0000000002
/locks/order-0000000003
临时顺序节点
临时顺序节点
临时顺序节点
3.2 临时顺序节点实现分布式锁原理
Zookeeper 释放锁流程
客户端 A 断开连接
临时节点自动删除
通知监听该节点的客户端
客户端 B 成为最小节点
获取锁成功
Zookeeper 获取锁流程
是
否
客户端 A 创建临时顺序节点
/locks/order-0000000001
获取 /locks 下所有子节点
判断自己是否是最小节点?
获取锁成功
Watcher 监听前一个节点
等待前一个节点删除
收到通知,再次检查
3.3 Curator 框架使用
java
/**
* Curator 分布式锁完整使用示例
*/
public class CuratorLockExample {
private CuratorFramework curatorClient;
/**
* 初始化 Curator 客户端
*/
@PostConstruct
public void init() {
curatorClient = CuratorFrameworkFactory.builder()
.connectString("localhost:2181")
.sessionTimeoutMs(5000)
.connectionTimeoutMs(3000)
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
.build();
curatorClient.start();
}
/**
* 获取分布式排他锁
*/
public void executeWithLock(String lockPath, Runnable task) throws Exception {
// 1. 创建排他锁
InterProcessMutex lock = new InterProcessMutex(curatorClient, lockPath);
try {
// 2. 获取锁(等待 10 秒)
if (lock.acquire(10, TimeUnit.SECONDS)) {
try {
// 3. 执行业务逻辑
task.run();
} finally {
// 4. 释放锁
lock.release();
}
}
} catch (Exception e) {
throw new RuntimeException("获取锁失败", e);
}
}
/**
* 可重入排他锁
*/
public void reentrantLockExample(String lockPath, Runnable task) throws Exception {
InterProcessMutex lock = new InterProcessMutex(curatorClient, lockPath);
// 同一线程可以多次获取锁
lock.acquire(10, TimeUnit.SECONDS);
lock.acquire(10, TimeUnit.SECONDS); // 可重入
try {
task.run();
} finally {
lock.release();
lock.release(); // 需要对应释放次数
}
}
/**
* 读写锁示例
*/
public void readWriteLockExample(String lockPath) throws Exception {
InterProcessReadWriteLock rwLock =
new InterProcessReadWriteLock(curatorClient, lockPath);
// 获取读锁(共享锁,多个线程可同时持有)
InterProcessMutex readLock = rwLock.readLock();
readLock.acquire(10, TimeUnit.SECONDS);
try {
// 读取操作
} finally {
readLock.release();
}
// 获取写锁(独占锁)
InterProcessMutex writeLock = rwLock.writeLock();
writeLock.acquire(10, TimeUnit.SECONDS);
try {
// 写入操作
} finally {
writeLock.release();
}
}
/**
* 多锁对象(同时获取多个锁)
*/
public void multiLockExample(String lockPath1, String lockPath2, Runnable task)
throws Exception {
InterProcessMutex lock1 = new InterProcessMutex(curatorClient, lockPath1);
InterProcessMutex lock2 = new InterProcessMutex(curatorClient, lockPath2);
// 同时获取多个锁
InterProcessMultiLock multiLock =
new InterProcessMultiLock(lock1, lock2);
if (multiLock.acquire(10, TimeUnit.SECONDS)) {
try {
task.run();
} finally {
multiLock.release();
}
}
}
}
3.4 Curator 分布式锁实现原理
羊群效应优化
无优化:所有客户端监听第一个节点
第一个节点删除,所有客户端都被通知
大量无效 Watch 事件
优化后:每个客户端只监听前一个节点
有序通知,精确唤醒
Watch 事件数量 O(n) → O(1)
Curator 分布式锁实现原理
是
否
创建临时顺序节点
获取锁路径下所有子节点
子节点按编号排序
自己是第一个?
获取锁成功
Watch 前一个节点
等待前一个节点删除
收到删除事件
重新检查排序
3.5 Zookeeper 分布式锁的优缺点
| 优点 | 说明 |
|---|---|
| 可靠性高 | Zookeeper 使用 ZAB 协议,保证分布式一致性 |
| 自动释放 | 临时节点在客户端断开时自动删除,无死锁 |
| 有序性 | 顺序节点天然保证锁的获取顺序 |
| 成熟方案 | Curator 框架封装完善,经过大量生产验证 |
| 缺点 | 说明 |
|---|---|
| 性能较低 | 每次获取锁都需要创建节点、Watcher 通知,有网络开销 |
| 吞吐量有限 | Zookeeper 每个写操作需要 Leader 广播,不适合高并发 |
| 运维复杂 | 需要维护 Zookeeper 集群,增加运维成本 |
| 锁粒度 | 不支持公平锁(社区版),需要额外实现 |
四、两种方案对比
4.1 Redis vs Zookeeper 分布式锁
对比维度
CAP 理论
Redis: AP 系统
高可用 + 分区容忍
Zookeeper: CP 系统
一致性 + 分区容忍
性能
Redis: QPS 10万+
纯内存操作
Zookeeper: QPS 1万
需要 Leader 协调
可靠性
Redis: 依赖 Redis 本身
需要 RedLock 提高可靠性
Zookeeper: ZAB 协议保证
一致性更强
功能特性
Redis: 看门狗自动续期
可重入、公平锁、读写锁
Zookeeper: 临时顺序节点
Watch 机制、天然有序
运维成本
Redis: 简单,Redis 本身需要维护
Zookeeper: 复杂,需要维护 ZK 集群
| 对比维度 | Redis 分布式锁 | Zookeeper 分布式锁 |
|---|---|---|
| CAP 模型 | AP(高可用、分区容忍) | CP(一致性、分区容忍) |
| 实现原理 | SETNX + TTL | 临时顺序节点 + Watch |
| 性能 | 非常高(10万+ QPS) | 较低(1万 QPS) |
| 可靠性 | 依赖 Redis 本身 | ZAB 协议保证 |
| 锁释放 | TTL 自动过期 | 临时节点自动删除 |
| 锁续期 | 看门狗自动续期 | 不需要(持有锁=连接存在) |
| 羊群效应 | 无 | 通过顺序节点优化 |
| 运维复杂度 | 低 | 高 |
| 适用场景 | 高并发、对性能要求高 | 对一致性要求极高 |
4.2 选型决策树
是
否
是
否
是
否
是
否
开始选型
对一致性要求极高?
选择 Zookeeper
并发量超过 5万/秒?
选择 Redis
需要自动续期?
选择 Redis + Redisson
已有 Zookeeper 集群?
选择 Zookeeper
选择 Redis
五、分布式锁升级策略
5.1 锁粒度设计
分段锁(折中方案)
将数据分成 N 段
每段一个锁
N = 线程数 × 2
细粒度锁
锁住单行数据
复杂,但并发高
按 ID 哈希分配锁
粗粒度锁
锁住整个表
简单,但并发低
所有操作串行化
5.2 锁升级策略
升级路径
是
否
读多写少场景
共享锁(读锁)
写入需求?
升级为排他锁
继续持有读锁
轻量级锁 → 重量级锁
自旋锁重试次数超限
阻塞等待
单实例锁 → 分布式锁
单点故障风险
引入 RedLock
六、最佳实践与避坑
6.1 常见问题案例
案例3:Redis 单点故障
Redis Master 宕机
哨兵自动切换
Slave 未同步锁数据
新 Master 没有锁
其他客户端获取同一把锁
解决方案:使用 RedLock
案例2:释放了别人的锁
客户端 A 获取锁
业务执行超时
锁自动释放
客户端 B 获取锁
客户端 A 执行完毕
错误释放了 B 的锁
解决方案:使用唯一 requestId
案例1:锁过期业务未完成
TTL = 5秒
业务执行 10 秒
5 秒后锁自动释放
其他客户端进入
数据不一致!
解决方案:看门狗自动续期
6.2 锁设计原则
| 原则 | 说明 | 示例 |
|---|---|---|
| 锁粒度要合理 | 锁太粗性能差,锁太细管理复杂 | 库存场景:按 SKU 分段锁 |
| TTL 要充足 | TTL = 业务执行时间 × 2 + buffer | 业务 5 秒 → TTL 至少 15 秒 |
| 异常必须释放锁 | finally 中释放,防止死锁 | try-finally-unlock |
| 使用唯一标识 | 防止误释放他人持有的锁 | UUID 作为 requestId |
| 考虑幂等性 | 解锁失败、锁续期失败怎么办 | 业务逻辑支持重试 |
| 监控告警 | 监控锁等待时间、获取成功率 | Prometheus + Grafana |
6.3 完整代码实现
java
/**
* 分布式锁最佳实践完整实现
*/
@Component
public class DistributedLockTemplate {
private RedissonClient redissonClient;
private PrometheusMeterRegistry meterRegistry;
/**
* 通用分布式锁执行模板
*/
public <T> T executeWithLock(String lockKey,
Supplier<T> supplier,
int waitTimeSeconds,
int leaseTimeSeconds) {
RLock lock = redissonClient.getLock(lockKey);
Timer.Sample sample = Timer.start(meterRegistry);
try {
// 1. 尝试获取锁,记录等待时间
boolean acquired = lock.tryLock(waitTimeSeconds,
leaseTimeSeconds,
TimeUnit.SECONDS);
if (!acquired) {
// 获取锁失败,记录指标
meterRegistry.counter("lock.failed",
Tags.of("key", lockKey)).increment();
throw new LockAcquisitionException(
"获取分布式锁失败: " + lockKey);
}
// 2. 获取成功,执行业务
meterRegistry.counter("lock.success",
Tags.of("key", lockKey)).increment();
return supplier.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new LockAcquisitionException("获取锁被中断", e);
} finally {
// 3. 释放锁(必须)
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
// 4. 记录执行时间
sample.stop(Timer.builder("lock.execution")
.tag("key", lockKey)
.register(meterRegistry));
}
}
/**
* 库存扣减示例
*/
public boolean deductStock(String productId, int quantity) {
String lockKey = "stock:lock:" + productId;
return executeWithLock(lockKey, () -> {
// 1. 查询当前库存
Integer stock = getStockFromDB(productId);
// 2. 检查库存是否充足
if (stock < quantity) {
return false; // 库存不足
}
// 3. 扣减库存
updateStockInDB(productId, stock - quantity);
return true;
}, 5, 30); // 等待 5 秒,TTL 30 秒
}
/**
* 分布式任务执行(幂等保证)
*/
public void executeTaskOnce(String taskId, Runnable task) {
String lockKey = "task:lock:" + taskId;
// 使用 tryLock 模式,避免阻塞
RLock lock = redissonClient.getLock(lockKey);
if (lock.tryLock()) {
try {
// 双重检查:Redis + 数据库
if (isTaskExecuted(taskId)) {
return; // 已执行,直接返回
}
// 执行业务
task.run();
// 标记任务完成
markTaskExecuted(taskId);
} finally {
lock.unlock();
}
} else {
// 未获取到锁,说明有其他实例在执行
log.info("任务 {} 正在被其他实例执行", taskId);
}
}
// 辅助方法
private Integer getStockFromDB(String productId) { /* ... */ return 0; }
private void updateStockInDB(String productId, int stock) { /* ... */ }
private boolean isTaskExecuted(String taskId) { return false; }
private void markTaskExecuted(String taskId) { /* ... */ }
}
七、面试高频问题
7.1 Redis 分布式锁问题
Q1:Redis 分布式锁如何保证原子性?
A1:Redis 分布式锁的原子性通过以下机制保证:
- 加锁原子性 :使用
SET key value NX EX seconds命令,这是 Redis 单命令操作,保证原子性 - 释放锁原子性:使用 Lua 脚本释放锁,先检查值是否匹配,再删除
lua
-- 释放锁的 Lua 脚本
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
Q2:Redis 分布式锁过期了业务还没执行完怎么办?
A2:解决方案有两种:
- 看门狗机制(推荐):Redisson 实现了看门狗,每 1/3 TTL 时间自动续期,只要客户端存活,锁就不会释放
- 设置合理 TTL + 异步补偿:预估业务执行时间,设置充足的 TTL + buffer,业务执行失败时通过消息队列重试
Q3:Redis 主从模式下锁丢失问题如何解决?
A3:Redis 主从异步复制可能导致以下问题:
- Master 宕机时,锁数据未同步到 Slave
- Sentinel/Cluster 自动切换后,新 Master 没有锁数据
解决方案是使用 RedLock 算法:
- 在 N 个独立的 Redis 实例上获取锁
- 只有成功获取 >= N/2+1 个锁,才认为获取成功
- 这样即使部分实例宕机,也不会丢失锁
7.2 Zookeeper 分布式锁问题
Q4:Zookeeper 分布式锁如何实现公平锁?
A4:Zookeeper 的顺序节点天然保证了公平性:
- 每个请求在 /locks 路径下创建临时顺序节点
- 最小序号节点获取锁,其他节点等待
- 由于是顺序节点,锁的获取顺序就是创建顺序(FIFO)
注意:Curator 的 InterProcessMutex 默认就是公平锁。
Q5:Zookeeper 的羊群效应是什么?如何优化?
A5:羊群效应是指大量客户端同时监听同一个节点变化。当这个节点被删除时,所有客户端都会收到通知,导致大量无效的 Watch 事件。
优化方案:使用临时顺序节点,每个客户端只监听前一个节点,而不是第一个节点。这样只有前一个节点被删除时,才会通知下一个客户端,实现了精确唤醒。
7.3 选型问题
Q6:Redis 和 Zookeeper 分布式锁如何选型?
A6:选型依据主要是业务场景:
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 高并发(10万+ QPS) | Redis | 性能高,延迟低 |
| 强一致性(金融、订单) | Zookeeper | CP 模型,ZAB 协议保证 |
| 简单场景 | Redis | 运维简单,Redisson 功能完善 |
| 已有 Zookeeper | Zookeeper | 复用现有基础设施 |
| 需要自动续期 | Redis + Redisson | 看门狗机制 |
| 跨数据中心 | Zookeeper | 更强的分布式一致性 |
八、总结
8.1 核心要点回顾
选型建议
高并发 → Redis
强一致 → Zookeeper
通用场景 → Redis + Redisson
Zookeeper分布式锁
临时顺序节点
Watcher 监听前一个
羊群效应优化
自动释放无死锁
Redis分布式锁
SETNX + TTL
看门狗自动续期
Lua 脚本释放锁
RedLock 提高可靠性
8.2 Redis vs Zookeeper 对比总结
| 维度 | Redis | Zookeeper |
|---|---|---|
| CAP | AP | CP |
| 性能 | 10万+ QPS | 1万 QPS |
| 可靠性 | 需要 RedLock | ZAB 协议保证 |
| 锁释放 | TTL 自动过期 | 临时节点删除 |
| 适用场景 | 高并发 | 强一致性 |
| 运维成本 | 低 | 高 |
8.3 实战建议
- 优先选择 Redis:大多数场景下 Redis 分布式锁足够使用,性能更好
- 使用成熟框架:Redisson 封装完善,Curator 功能丰富
- 注意异常处理:锁获取失败要有降级方案
- 做好监控:监控锁等待时间、获取成功率等指标
- 考虑幂等性:业务逻辑要支持锁相关的重试
参考资料
- Redis 官方文档:https://redis.io/docs/
- Redisson 官方文档:https://redisson.org/
- Zookeeper 官方文档:https://zookeeper.apache.org/
- Curator 官方文档:https://curator.apache.org/
- RedLock 论文:https://redis.io/topics/distlock
- 《Redis 设计与实现》
- 《从 Paxos 到 Zookeeper》