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

分布式锁是分布式系统中保证资源互斥访问的关键技术。本文将详细介绍四种主流实现方案及其核心机制,帮助开发者根据业务场景选择最适合的方案。

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

1. 唯一索引/主键冲突

实现原理:利用数据库唯一键约束特性,通过插入相同数据触发唯一键冲突来实现锁竞争。

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

加锁操作

INSERT INTO distributed_lock(lock_name, owner_id, expire_time) 
VALUES ('order_lock', 'server1', NOW() + INTERVAL 30 SECOND);

解锁操作

DELETE FROM distributed_lock WHERE lock_name = 'order_lock' AND owner_id = 'server1';

实践建议

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

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

实现原理:通过版本号或时间戳实现CAS(Compare-And-Swap)操作。

-- 初始化数据
CREATE TABLE resource_with_lock (
  id BIGINT PRIMARY KEY,
  version INT DEFAULT 0,
  data VARCHAR(255)
);

-- 更新时检查版本
UPDATE resource_with_lock 
SET data = 'new_value', version = version + 1 
WHERE id = 123 AND version = 5;

实践建议

  • 适合读多写少场景
  • 需要处理更新失败的重试逻辑
  • 不保证严格的互斥性

3. 悲观锁(SELECT FOR UPDATE)

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

// Spring事务中使用
@Transactional
public void updateWithLock(Long id) {
    // 获取行锁
    Resource res = entityManager.createQuery(
        "SELECT r FROM Resource r WHERE r.id = :id", Resource.class)
        .setParameter("id", id)
        .setLockMode(LockModeType.PESSIMISTIC_WRITE)
        .getSingleResult();
    
    // 业务处理
    updateResource(res);
}

实践建议

  • 事务结束后会自动释放锁
  • 注意锁等待超时设置
  • 可能引起死锁,需要合理设计获取顺序

二、基于Redis的实现方案

1. SETNX + 过期时间

基本命令

SET lock_key unique_value NX PX 30000

Java实现

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

public boolean unlock(Jedis jedis, String lockKey, String requestId) {
    String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                    "return redis.call('del', KEYS[1]) " +
                    "else return 0 end";
    Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
    return result.equals(1L);
}

实践建议

  • 必须设置过期时间防止死锁
  • 解锁操作需要Lua脚本保证原子性
  • 考虑锁续期问题(看门狗机制)

2. RedLock算法

算法步骤

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

Redisson实现

Config config = new Config();
config.useClusterServers()
    .addNodeAddress("redis://127.0.0.1:7000")
    .addNodeAddress("redis://127.0.0.1:7001");

RedissonClient redisson = Redisson.create(config);
RLock lock = redisson.getLock("myLock");

try {
    // 尝试加锁,最多等待100秒,上锁后30秒自动解锁
    boolean res = lock.tryLock(100, 30, TimeUnit.SECONDS);
    if (res) {
        // 业务逻辑
    }
} finally {
    lock.unlock();
}

实践建议

  • 至少部署5个Redis主节点提高可靠性
  • 需要考虑时钟同步问题
  • 性能比单节点方案低,适合高可靠性要求场景

3. Redisson高级特性

看门狗机制

图1

实践建议

  • 默认看门狗检查间隔是锁过期时间的1/3
  • 业务执行时间不可预测时特别有用
  • 注意正确处理锁释放避免资源泄漏

三、基于ZooKeeper的实现方案

1. 临时顺序节点实现

核心原理

  1. 在锁节点下创建临时顺序子节点
  2. 判断自己是否是最小序号节点
  3. 如果不是,则监听前一个节点的删除事件
  4. 前一个节点删除后重新检查顺序

Curator框架实现

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

节点结构

/locks
  /order
    /_c_8623f016-634b-4a85-b905-5d8a61f885d6-0000000000
    /_c_8623f016-634b-4a85-b905-5d8a61f885d6-0000000001

2. 监听机制(Watcher)

工作原理

图2

实践建议

  • 适合对可靠性要求极高的场景
  • 性能比Redis方案低
  • 需要处理"羊群效应"(Curator已优化)

四、其他中间件方案

1. Etcd实现

核心机制

// 获取锁(租约机制)
resp, err := client.Grant(context.Background(), 10) // 10秒租约
_, err = client.Put(context.Background(), "lock_key", "value", clientv3.WithLease(resp.ID))

// 保持心跳保持锁
ka, err := client.KeepAlive(context.Background(), resp.ID)

实践建议

  • 基于Raft协议,强一致性保证
  • 适合需要严格一致性的场景
  • 提供事务支持,可实现更复杂锁逻辑

2. Consul实现

Session机制

# 创建Session(TTL 15秒)
SESSION_ID=$(curl -X PUT 'http://localhost:8500/v1/session/create' \
  -d '{"Name": "order_lock", "TTL": "15s"}')

# 获取锁
curl -X PUT "http://localhost:8500/v1/kv/lock/order?acquire=${SESSION_ID}" \
  -d "server1"

实践建议

  • Session失效时自动释放关联的锁
  • 支持健康检查集成
  • 适合服务发现与锁结合的场景

五、方案选型对比

特性数据库RedisZooKeeperEtcd
性能
可靠性依赖部署方式
实现复杂度简单
自动续期不支持支持(Redisson)支持支持
适用场景低并发简单场景高并发场景高可靠场景强一致性场景

终极选型建议

  1. 简单场景:数据库方案足够
  2. 高并发:Redis + Redisson
  3. 高可靠:ZooKeeper/Etcd
  4. 已有中间件:优先使用现有基础设施

希望本文能帮助您在分布式系统开发中做出合理的锁方案选择。每种方案都有其适用场景,理解其核心原理才能发挥最大价值。

添加新评论