分布式锁实现方案对比与选型指南
分布式锁的主流实现方案与技术选型指南
分布式锁是分布式系统中保证资源互斥访问的核心组件,本文将深入分析四种主流实现方案的技术细节与适用场景。
一、基于数据库的实现方案
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);
// 业务处理
}
性能对比:
二、基于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算法
实践建议:
- 部署至少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. 临时顺序节点
获取锁流程:
- 创建临时顺序节点
- 检查自己是否是最小节点
- 如果不是,监听前一个节点
- 前一个节点删除后重新检查
2. Curator实现示例
InterProcessMutex lock = new InterProcessMutex(client, "/my-lock");
try {
if (lock.acquire(10, TimeUnit.SECONDS)) {
// 业务逻辑
}
} finally {
lock.release();
}
优势对比:
特性 | Redis | ZooKeeper |
---|---|---|
性能 | 高(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"
五、技术选型建议
- 性能优先:Redis > Etcd > ZooKeeper > 数据库
- 可靠性优先:ZooKeeper > Etcd > Redis > 数据库
- 简单易用:Redis > 数据库 > ZooKeeper > Etcd
典型场景匹配:
- 秒杀系统:Redis RedLock
- 分布式事务:ZooKeeper
- 低频后台任务:数据库实现
- 云原生环境:Etcd/Consul
六、最佳实践
- 必须设置合理的锁超时时间
- 实现锁的可重入性避免死锁
- 添加锁持有者标识防止误删
- 监控锁等待时间和获取次数
- 考虑锁降级策略提升性能
// 锁监控示例
@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);
}
}
}
通过合理选择和正确实现分布式锁,可以有效解决分布式环境下的资源竞争问题,建议根据实际业务场景选择最适合的方案。