分布式锁4大实现方案详解与最佳实践
分布式锁的主流实现方案深度解析
分布式锁是构建分布式系统时确保数据一致性的关键组件。本文将深入分析四种主流实现方案的技术细节、适用场景及最佳实践。
一、基于数据库的实现方案
1. 唯一索引/主键冲突
实现原理:利用数据库唯一约束特性,通过插入相同键值实现互斥。
CREATE TABLE distributed_lock (
lock_name VARCHAR(64) PRIMARY KEY,
expire_time DATETIME
);
加锁操作:
public boolean tryLock(String lockName, long expireSeconds) {
try {
return jdbcTemplate.update(
"INSERT INTO distributed_lock VALUES (?, DATE_ADD(NOW(), INTERVAL ? SECOND))",
lockName, expireSeconds) > 0;
} catch (DuplicateKeyException e) {
return false;
}
}
实践建议:
- 必须设置过期时间避免死锁
- 建议添加定时任务清理过期锁
- 适合并发量较低的场景(QPS < 500)
2. 乐观锁(版本号机制)
实现模式:
典型代码:
public boolean optimisticLock(String resourceId) {
int version = getCurrentVersion(resourceId);
return jdbcTemplate.update(
"UPDATE resources SET version = version + 1 WHERE id = ? AND version = ?",
resourceId, version) > 0;
}
3. 悲观锁(SELECT FOR UPDATE)
事务中使用方式:
@Transactional
public void processWithPessimisticLock(Long id) {
// 获取行锁
Resource res = jdbcTemplate.queryForObject(
"SELECT * FROM resources WHERE id = ? FOR UPDATE",
rowMapper, id);
// 业务处理
updateResource(res);
} // 事务提交自动释放锁
注意事项:
- MySQL需要InnoDB引擎支持
- 可能引发死锁,需设置事务超时
- 高并发下性能较差
二、基于Redis的实现方案
1. SETNX + 过期时间
基础实现:
public boolean tryLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
String result = jedis.set(lockKey, requestId, "NX", "EX", expireTime);
return "OK".equals(result);
}
关键问题:
- 非原子性操作风险(SETNX+EXPIRE需用Lua脚本)
- 误删其他客户端锁(需验证requestId)
2. RedLock算法
实现步骤:
- 获取当前毫秒级时间戳
- 依次尝试从N个独立Redis实例获取锁
- 计算获取锁总耗时,小于锁过期时间且成功获取多数节点锁时视为成功
- 若失败,向所有节点发起释放锁请求
3. Redisson最佳实践
典型用法:
RLock lock = redisson.getLock("orderLock");
try {
// 支持看门狗自动续期
if (lock.tryLock(10, 60, TimeUnit.SECONDS)) {
// 业务处理
}
} finally {
lock.unlock();
}
核心特性:
- 默认30秒过期,每10秒自动续期(看门狗机制)
- 提供可重入锁、公平锁等多种锁类型
- 支持联锁(MultiLock)和红锁(RedLock)
三、基于ZooKeeper的实现方案
1. 临时顺序节点实现
Curator框架实现:
InterProcessMutex lock = new InterProcessMutex(client, "/locks/order");
public void process() throws Exception {
if (lock.acquire(10, TimeUnit.SECONDS)) {
try {
// 临界区代码
} finally {
lock.release();
}
}
}
ZK节点结构:
/locks
/order
/_c_8623f816-634c-45d1-8a8f-27f9c4e8e314-lock-0000000000
/_c_9237h291-283x-56k2-9d3f-38f7d5c2e1f6-lock-0000000001
2. Watcher监听机制
工作原理:
- 客户端创建临时顺序节点
- 检查自己是否是最小序号节点
- 如果不是,则监听前一个节点的删除事件
- 前驱节点删除后重新检查序号
四、其他中间件方案
1. Etcd实现方案
基于租约的锁:
// 创建租约(TTL 30秒)
resp, err := client.Grant(context.TODO(), 30)
// 创建事务比较
cmp := v3.Compare(v3.CreateRevision(key), "=", 0)
put := v3.OpPut(key, "", v3.WithLease(resp.ID))
get := v3.OpGet(key)
// 执行事务
txnResp, err := client.Txn(context.TODO()).If(cmp).Then(put, get).Else(get).Commit()
2. Consul实现方案
Session机制关键步骤:
# 创建Session(TTL 30s)
SESSION_ID=$(curl -X PUT -d '{"Name": "order_lock", "TTL": "30s"}' \
http://localhost:8500/v1/session/create | jq -r '.ID')
# 获取锁(KV存储)
curl -X PUT -d "locked" \
http://localhost:8500/v1/kv/locks/order?acquire=$SESSION_ID
五、方案选型对比
特性 | 数据库 | Redis | ZooKeeper | Etcd |
---|---|---|---|---|
性能 | 低(100-500QPS) | 高(10,000+QPS) | 中(1000-5000QPS) | 高(5000+QPS) |
一致性保证 | 强一致 | 最终一致 | 强一致 | 强一致 |
实现复杂度 | 简单 | 中等 | 复杂 | 中等 |
典型适用场景 | 传统数据库系统 | 高并发缓存系统 | 配置中心 | 服务发现 |
实践建议:
- 已有Redis集群时优先考虑Redisson方案
- 强一致性要求场景选择ZooKeeper或Etcd
- 数据库方案仅作为兜底方案使用
- 跨机房部署需特别注意网络分区问题
六、常见问题解决方案
锁续期问题:
实现模式:
private void scheduleExpirationRenewal(final String threadId) { if (expirationRenewalMap.containsKey(getEntryName())) { return; } Timeout task = commandExecutor.getConnectionManager() .newTimeout(new TimerTask() { public void run(Timeout timeout) { // 执行Lua脚本续期 renewExpirationAsync(threadId); } }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS); }
锁重入实现:
-- Redisson重入锁Lua脚本
if (redis.call('exists', KEYS[1]) == 0) then
redis.call('hset', KEYS[1], ARGV[2], 1);
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil;
end;
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
redis.call('hincrby', KEYS[1], ARGV[2], 1);
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil;
end;
return redis.call('pttl', KEYS[1]);
通过深入理解各种分布式锁的实现原理和适用场景,开发者可以根据实际业务需求选择最适合的技术方案,确保分布式系统的数据一致性和高可用性。