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

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导致系统恢复时间延长

可重入锁设计

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

图1

实现要点

  1. 记录持有线程标识
  2. 维护重入计数器
  3. 只有计数器归零时才真正释放锁
// 自定义可重入锁示例
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)

算法流程

  1. 获取当前毫秒级时间戳
  2. 依次向N个Redis节点请求加锁
  3. 当获得大多数(N/2+1)节点认可时视为成功
  4. 锁有效时间 = 初始有效时间 - 获取锁耗时

图2

实践建议

  • 部署至少5个Redis节点(容忍2个故障)
  • 网络延迟应远小于锁过期时间
  • 官方建议仅在无法使用单实例Redis时才考虑RedLock

Fencing Token机制

解决方案

  1. 锁服务在发放锁时返回单调递增的token
  2. 客户端操作资源时需携带token
  3. 资源服务验证token时序性
// 伪代码示例
public void updateResource(int newValue, long token) {
    if (token <= lastProcessedToken) {
        throw new StaleRequestException();
    }
    // 处理请求
    lastProcessedToken = token;
    resource.setValue(newValue);
}

优势

  • 解决因GC停顿导致的锁失效问题
  • 防止延迟的网络包造成资源冲突

3. 性能优化

锁分段(ConcurrentHashMap思想)

实现方案

  1. 将大资源拆分为多个小段
  2. 每个段独立加锁
  3. 细粒度控制并发度
// 订单服务分段锁示例
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+推荐使用ConcurrentHashMapcomputeIfAbsent

本地缓存+分布式锁结合

多级缓存架构

图3

实现代码

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,20015ms强一致性要求高
本地缓存+锁45,0002ms读多写少场景

总结与选型建议

  1. 基础场景:单Redis节点 + TTL + 可重入锁
  2. 高可用场景:RedLock + Fencing Token
  3. 高性能场景:锁分段 + 多级缓存
  4. 特殊要求:Zookeeper/Etcd实现(CP系统)

避坑指南

  • 避免在锁代码块中执行远程调用(RPC/HTTP)
  • 锁命名应具备业务语义(如"order_123_pay")
  • 生产环境必须实现锁监控(获取次数、持有时间等)

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

添加新评论