分布式锁核心挑战与解决方案

1. 死锁预防

锁自动过期(TTL)

概念解释
锁自动过期机制通过为分布式锁设置生存时间(Time To Live),确保即使锁持有者崩溃或网络分区,锁也能自动释放,避免永久死锁。

// Redis锁设置TTL示例
String result = jedis.set(lockKey, requestId, "NX", "PX", 30000);
if ("OK".equals(result)) {
    // 获取锁成功
}

实践建议

  • 设置合理的TTL时间(通常10-30秒)
  • 避免业务处理时间超过TTL(可通过心跳续期解决)
  • 确保设置值和过期时间是原子操作(Redis的SET NX PX命令)

可重入锁设计

概念解释
可重入锁允许同一个线程多次获取同一把锁,防止线程自己造成死锁。

图1

代码实现

public class RedisReentrantLock {
    private ThreadLocal<Map<String, Integer>> lockers = new ThreadLocal<>();
    
    public boolean lock(String key) {
        Map<String, Integer> current = lockers.get();
        if (current != null && current.containsKey(key)) {
            current.put(key, current.get(key) + 1);
            return true;
        }
        // 实际获取锁逻辑...
    }
}

实践建议

  • 使用ThreadLocal维护重入状态
  • 每次重入必须对应相同次数的释放
  • 客户端ID应唯一标识锁持有者

2. 脑裂问题

多数派投票(RedLock)

概念解释
RedLock算法通过在多个独立的Redis节点上获取锁,当获取多数节点(N/2+1)锁成功时才认为获取成功。

图2

实践建议

  • 部署至少5个Redis节点提高容错性
  • 时钟同步问题可能导致锁失效
  • 性能较低,适合对一致性要求高的场景

Fencing Token机制

概念解释
通过单调递增的token确保后获取锁的客户端请求能拒绝之前持有锁的延迟请求。

// 伪代码示例
long token = zk.getLock().getFencingToken();
storage.write(data, token); // 存储系统验证token

实践建议

  • 存储系统需要支持token验证
  • 适用于数据库、文件系统等共享存储
  • 与锁服务解耦,提高可靠性

3. 性能优化

锁分段(ConcurrentHashMap思想)

概念解释
将一个大锁拆分为多个小锁,减少锁竞争粒度。

// 分段锁示例
public class SegmentLock {
    private final int segments = 16;
    private final Object[] locks = new Object[segments];
    
    public Object getLock(String key) {
        return locks[key.hashCode() & (segments - 1)];
    }
}

实践建议

  • 根据并发量选择合适的分段数(通常2的幂次)
  • 避免热点key导致分段失效
  • 适用于商品库存等可分割资源

本地缓存+分布式锁结合

概念解释
本地缓存处理大部分读请求,分布式锁只保护关键写操作。

图3

实践建议

  • 本地缓存设置合理过期时间
  • 使用Pub/Sub机制同步缓存失效
  • 适合读多写少场景

最佳实践总结

  1. 锁选择原则

    • 单Redis节点:SET NX PX + 唯一ID + Lua释放
    • 高可用:RedLock或Zookeeper
    • 高性能:锁分段+本地缓存
  2. 监控指标

    # Redis锁监控
    redis-cli info stats | grep lock
  3. 故障处理

    • 设置合理的锁等待超时
    • 实现锁获取的重试退避策略
    • 记录锁竞争日志用于分析

通过合理组合这些技术方案,可以在分布式系统中实现安全、高效的并发控制。

添加新评论