分布式锁的主流实现方案与技术选型指南

分布式锁是分布式系统中保证资源互斥访问的核心组件,本文将深入分析四种主流实现方案的技术细节与适用场景。

一、基于数据库的实现方案

1. 唯一索引/主键冲突

实现原理:利用数据库唯一键约束实现互斥

CREATE TABLE distributed_lock (
  lock_name VARCHAR(64) PRIMARY KEY,
  expire_time DATETIME NOT NULL
);

加锁操作

public boolean tryLock(String lockName, long expireSeconds) {
    try {
        return jdbcTemplate.update(
            "INSERT INTO distributed_lock VALUES (?, NOW() + INTERVAL ? SECOND)",
            lockName, expireSeconds) == 1;
    } catch (DuplicateKeyException e) {
        return false;
    }
}

实践建议

  • 适用于低频并发场景(QPS < 100)
  • 必须设置合理的过期时间避免死锁
  • 建议增加定时任务清理过期锁

2. 乐观锁(版本号机制)

CREATE TABLE resource_with_lock (
  id BIGINT PRIMARY KEY,
  version INT NOT NULL,
  data VARCHAR(1024)
);

更新逻辑

public boolean updateWithLock(Long id, String newData) {
    Resource resource = getById(id);
    int rows = jdbcTemplate.update(
        "UPDATE resource_with_lock SET data = ?, version = version + 1 " +
        "WHERE id = ? AND version = ?",
        newData, id, resource.getVersion());
    return rows > 0;
}

3. 悲观锁(SELECT FOR UPDATE)

// 必须在事务中执行
@Transactional
public void doWithLock(Long resourceId) {
    Resource r = jdbcTemplate.queryForObject(
        "SELECT * FROM resource WHERE id = ? FOR UPDATE", 
        rowMapper, resourceId);
    // 业务处理
}

性能对比

图1

二、基于Redis的实现方案

1. SETNX + 过期时间

public boolean tryLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
    String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
    return "OK".equals(result);
}

关键问题

  • 非原子性操作风险(SETNX + EXPIRE)
  • 误删其他客户端锁(需校验requestId)

2. RedLock算法

图2

实践建议

  • 部署至少5个独立Redis节点
  • 时钟同步问题可能导致锁失效
  • 性能开销较大(适合CP场景)

3. Lua脚本保证原子性

-- 加锁脚本
if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then
    return redis.call('pexpire', KEYS[1], ARGV[2])
else
    return 0
end

-- 解锁脚本
if redis.call('get', KEYS[1]) == ARGV[1] then
    return redis.call('del', KEYS[1])
else
    return 0
end

4. Redisson实现

// 自动续期+可重入锁
RLock lock = redisson.getLock("myLock");
try {
    lock.lock();
    // 业务逻辑
} finally {
    lock.unlock();
}

功能特性

  • Watchdog机制(默认30秒续期)
  • 支持公平锁、联锁、红锁
  • 提供异步执行方式

三、基于ZooKeeper的实现方案

1. 临时顺序节点

图3

获取锁流程

  1. 创建临时顺序节点
  2. 检查自己是否是最小节点
  3. 如果不是,监听前一个节点
  4. 前一个节点删除后重新检查

2. Curator实现示例

InterProcessMutex lock = new InterProcessMutex(client, "/my-lock");
try {
    if (lock.acquire(10, TimeUnit.SECONDS)) {
        // 业务逻辑
    }
} finally {
    lock.release();
}

优势对比

特性RedisZooKeeper
性能高(10k+ QPS)中(1k-5k QPS)
可靠性依赖持久化强一致
实现复杂度中等
适用场景AP系统CP系统

四、其他中间件方案

1. Etcd实现

// 使用租约机制
resp, err := client.Grant(context.TODO(), 10)
_, err = client.Put(context.TODO(), "lock-key", "value", clientv3.WithLease(resp.ID))

2. Consul实现

# 创建Session
consul kv put -acquire -session=<session_id> lock-key "value"

五、技术选型建议

  1. 性能优先:Redis > Etcd > ZooKeeper > 数据库
  2. 可靠性优先:ZooKeeper > Etcd > Redis > 数据库
  3. 简单易用:Redis > 数据库 > ZooKeeper > Etcd

典型场景匹配

  • 秒杀系统:Redis RedLock
  • 分布式事务:ZooKeeper
  • 低频后台任务:数据库实现
  • 云原生环境:Etcd/Consul

六、最佳实践

  1. 必须设置合理的锁超时时间
  2. 实现锁的可重入性避免死锁
  3. 添加锁持有者标识防止误删
  4. 监控锁等待时间和获取次数
  5. 考虑锁降级策略提升性能
// 锁监控示例
@Aspect
public class LockMonitorAspect {
    @Around("@annotation(distributedLock)")
    public Object monitor(ProceedingJoinPoint pjp, DistributedLock distributedLock) {
        long start = System.currentTimeMillis();
        try {
            return pjp.proceed();
        } finally {
            long cost = System.currentTimeMillis() - start;
            metrics.recordLockTime(distributedLock.value(), cost);
        }
    }
}

通过合理选择和正确实现分布式锁,可以有效解决分布式环境下的资源竞争问题,建议根据实际业务场景选择最适合的方案。

添加新评论