分布式锁死锁预防与脑裂解决方案实战指南
分布式锁核心挑战与解决方案实战指南
1. 死锁预防
锁自动过期(TTL)
概念解释:
锁自动过期机制通过为锁设置生存时间(Time To Live),确保即使锁持有者崩溃或网络分区,锁也能自动释放,避免系统永久阻塞。
// Redis锁实现示例(Redisson)
RLock lock = redisson.getLock("orderLock");
try {
// 尝试加锁,最多等待10秒,锁自动释放时间30秒
boolean res = lock.tryLock(10, 30, TimeUnit.SECONDS);
if (res) {
// 业务处理
}
} finally {
lock.unlock();
}
实践建议:
- 过期时间应大于业务平均执行时间(建议2-3倍)
- 实现Watchdog机制自动续期(如Redisson的
lockWatchdogTimeout
) - 避免设置过长TTL导致系统恢复时间延长
可重入锁设计
概念解释:
可重入锁允许同一个线程多次获取同一把锁,防止线程自己造成死锁。
实现要点:
- 记录持有线程标识
- 维护重入计数器
- 只有计数器归零时才真正释放锁
// 自定义可重入锁示例
public class MyReentrantLock {
private Thread owner;
private int count = 0;
public synchronized void lock() throws InterruptedException {
Thread current = Thread.currentThread();
while (owner != null && owner != current) {
wait();
}
owner = current;
count++;
}
public synchronized void unlock() {
if (Thread.currentThread() != owner) {
throw new IllegalMonitorStateException();
}
if (--count == 0) {
owner = null;
notify();
}
}
}
2. 脑裂问题
多数派投票(RedLock)
算法流程:
- 获取当前毫秒级时间戳
- 依次向N个Redis节点请求加锁
- 当获得大多数(N/2+1)节点认可时视为成功
- 锁有效时间 = 初始有效时间 - 获取锁耗时
实践建议:
- 部署至少5个Redis节点(容忍2个故障)
- 网络延迟应远小于锁过期时间
- 官方建议仅在无法使用单实例Redis时才考虑RedLock
Fencing Token机制
解决方案:
- 锁服务在发放锁时返回单调递增的token
- 客户端操作资源时需携带token
- 资源服务验证token时序性
// 伪代码示例
public void updateResource(int newValue, long token) {
if (token <= lastProcessedToken) {
throw new StaleRequestException();
}
// 处理请求
lastProcessedToken = token;
resource.setValue(newValue);
}
优势:
- 解决因GC停顿导致的锁失效问题
- 防止延迟的网络包造成资源冲突
3. 性能优化
锁分段(ConcurrentHashMap思想)
实现方案:
- 将大资源拆分为多个小段
- 每个段独立加锁
- 细粒度控制并发度
// 订单服务分段锁示例
public class OrderService {
private final Striped<Lock> locks = Striped.lock(16);
public void processOrder(long orderId) {
Lock lock = locks.get(orderId % 16);
lock.lock();
try {
// 处理订单
} finally {
lock.unlock();
}
}
}
最佳实践:
- 分段数应大于等于并发线程数
- 热点数据仍可能成为瓶颈,需配合其他策略
- JDK8+推荐使用
ConcurrentHashMap
的computeIfAbsent
本地缓存+分布式锁结合
多级缓存架构:
实现代码:
public class CacheService {
private LoadingCache<String, Object> localCache =
Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(this::loadFromRedis);
private Object getData(String key) {
try {
return localCache.get(key);
} catch (Exception e) {
RLock lock = redisson.getLock(key + "_lock");
try {
lock.lock();
// 双重检查
Object value = localCache.getIfPresent(key);
if (value == null) {
value = loadFromDB(key);
localCache.put(key, value);
redis.set(key, value);
}
return value;
} finally {
lock.unlock();
}
}
}
}
性能对比:
方案 | QPS | 平均延迟 | 适用场景 |
---|---|---|---|
纯分布式锁 | 1,200 | 15ms | 强一致性要求高 |
本地缓存+锁 | 45,000 | 2ms | 读多写少场景 |
总结与选型建议
- 基础场景:单Redis节点 + TTL + 可重入锁
- 高可用场景:RedLock + Fencing Token
- 高性能场景:锁分段 + 多级缓存
- 特殊要求:Zookeeper/Etcd实现(CP系统)
避坑指南:
- 避免在锁代码块中执行远程调用(RPC/HTTP)
- 锁命名应具备业务语义(如"order_123_pay")
- 生产环境必须实现锁监控(获取次数、持有时间等)
通过合理组合这些技术方案,可以在分布式系统中实现安全、高效、可靠的并发控制。