分布式锁主流实现方案深度解析

分布式锁是分布式系统中保证资源互斥访问的核心机制,本文将系统性地介绍当前主流的分布式锁实现方案,包括技术原理、实现细节和最佳实践。

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

1. 唯一索引/主键冲突

实现原理:利用数据库唯一键约束特性,通过插入相同键值实现互斥。

CREATE TABLE distributed_lock (
    lock_name VARCHAR(64) PRIMARY KEY,
    owner_id VARCHAR(255),
    expire_time DATETIME
);

加锁操作

public boolean tryLock(String lockName, String ownerId, long expireSeconds) {
    try {
        return jdbcTemplate.update(
            "INSERT INTO distributed_lock VALUES (?, ?, DATE_ADD(NOW(), INTERVAL ? SECOND))",
            lockName, ownerId, expireSeconds) == 1;
    } catch (DuplicateKeyException e) {
        // 检查是否为过期锁
        int updated = jdbcTemplate.update(
            "UPDATE distributed_lock SET owner_id=?, expire_time=DATE_ADD(NOW(), INTERVAL ? SECOND) " +
            "WHERE lock_name=? AND expire_time <= NOW()",
            ownerId, expireSeconds, lockName);
        return updated == 1;
    }
}

实践建议

  • 适合并发量不高的简单场景
  • 必须设置合理的过期时间避免死锁
  • 需要定时任务清理过期锁

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

实现原理:通过版本号或时间戳实现CAS操作

CREATE TABLE optimistic_lock (
    resource_id BIGINT PRIMARY KEY,
    version INT DEFAULT 0,
    data VARCHAR(1000)
);

-- 更新时检查版本
UPDATE optimistic_lock 
SET data = 'new_value', version = version + 1 
WHERE resource_id = ? AND version = ?;

实践建议

  • 适合读多写少场景
  • 需要处理更新失败的重试逻辑
  • 长时间冲突会导致大量重试

3. 悲观锁(SELECT FOR UPDATE)

实现原理:利用数据库行锁机制阻塞其他事务

@Transactional
public void doWithLock(Long resourceId) {
    // 获取行锁
    Resource res = jdbcTemplate.queryForObject(
        "SELECT * FROM resources WHERE id = ? FOR UPDATE", 
        rowMapper, resourceId);
    
    // 业务处理
    process(res);
}

实践建议

  • 事务要保持短小精悍
  • 需要合理设置事务超时时间
  • 注意锁升级问题(如MySQL的间隙锁)

二、基于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);
}

解锁脚本(避免误删):

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

2. RedLock算法

算法步骤

  1. 获取当前毫秒级时间戳
  2. 依次尝试从N个独立Redis实例获取锁
  3. 计算获取锁总耗时,当且仅当大多数节点获取成功且耗时小于锁有效期时认为成功
  4. 如果失败,向所有节点发起释放锁请求

图1

实践建议

  • 官方推荐5个独立实例
  • 时钟同步问题可能导致锁失效
  • 性能开销较大,适合关键业务

3. Redisson实现

特性

  • Watchdog自动续期机制
  • 可重入锁支持
  • 多种锁类型(公平锁、联锁等)
// 获取锁
RLock lock = redisson.getLock("myLock");
try {
    // 尝试加锁,最多等待100秒,上锁后30秒自动解锁
    boolean res = lock.tryLock(100, 30, TimeUnit.SECONDS);
    if (res) {
        // 业务逻辑
    }
} finally {
    lock.unlock();
}

三、基于ZooKeeper的实现

1. 临时顺序节点实现

核心流程

  1. 在锁节点下创建临时顺序节点
  2. 获取所有子节点并排序
  3. 判断自己是否为最小节点,是则获取锁
  4. 否则监听前一个节点的删除事件

图2

Curator实现示例

InterProcessMutex lock = new InterProcessMutex(client, "/my_lock");
try {
    if (lock.acquire(10, TimeUnit.SECONDS)) {
        // 业务处理
    }
} finally {
    lock.release();
}

2. 监听机制(Watcher)

特性

  • 事件一次性触发
  • 会话失效自动释放锁
  • 避免羊群效应(只监听前驱节点)

实践建议

  • 适合对可靠性要求高的场景
  • 注意ZooKeeper的watch数量限制
  • 会话超时时间要合理设置

四、其他中间件方案

1. Etcd实现

核心机制

# 获取锁(租约10秒)
etcdctl lease grant 10
# 使用租约PUT键值
etcdctl put --lease=1234abcd /lock/resource1 owner1

特性

  • 基于租约的自动过期
  • 事务比较支持
  • 线性一致性保证

2. Consul实现

Session机制

// 创建Session
NewSession session = NewSession.builder()
    .name("service-lock")
    .ttl("10s")
    .lockDelay("0s")
    .build();

// 获取锁
KVOperation operation = KVOperation.builder()
    .verb(KVVerb.LOCK)
    .key("locks/resource1")
    .session(sessionId)
    .build();

五、方案对比与选型建议

方案性能可靠性实现复杂度适用场景
数据库简单低并发、已有数据库依赖
Redis中高中等高并发、允许偶尔失效
ZooKeeper复杂强一致性要求、可靠性优先
Etcd中高中等云原生环境、需要线性一致性

选型建议

  1. 单数据中心首选Redis(Redisson实现)
  2. 金融级场景考虑ZooKeeper
  3. Kubernetes环境可优先考虑Etcd
  4. 简单场景可用数据库方案

六、最佳实践

  1. 锁粒度控制:按资源ID分片,避免全局锁

    String lockKey = "order_lock:" + orderId;
  2. 超时设置:业务操作超时应小于锁超时

    // 业务超时5秒,锁超时10秒
    lock.tryLock(5, 10, TimeUnit.SECONDS);
  3. 容错处理

    int maxRetries = 3;
    while (maxRetries-- > 0) {
        if (tryLock()) {
            try {
                doBusiness();
                return;
            } finally {
                unlock();
            }
        }
        Thread.sleep(100);
    }
    throw new LockAcquireFailedException();
  4. 监控指标

    • 锁等待时间
    • 锁持有时间
    • 锁获取失败率

通过合理选择和正确实现分布式锁,可以确保分布式系统中的数据一致性和业务正确性。建议根据实际业务场景和技术栈选择最适合的方案。

添加新评论