Redis 分布式锁实现原理 + Java 完整实现教程
大约 4 分钟
Redis 分布式锁实现原理 + Java 完整实现教程
背景介绍:为什么需要分布式锁?
先看常见场景:
| 场景 | 问题 |
|---|---|
| 多服务部署的库存扣减 | 多个服务节点并发更新库存,导致库存为负 |
| 定时任务 | 多个实例执行重复任务 |
| 防止重复提交 | 用户疯狂点击按钮,创建多个重复订单 |
传统锁(如 ReentrantLock)仅在当前进程内有效,无法跨 JVM 实例、跨服务节点协调。
因此就需要分布式锁,确保多个应用、服务、线程间在高并发下对某一资源的互斥访问。
Redis 实现分布式锁的基本原理
SET 命令 + 参数组合的说明
SET lockKey lockValue NX PX 30000含义解释:
lockKey:锁的唯一标识,例如order:lock:123lockValue:客户端生成的唯一值(如 UUID),防止误删NX:key 不存在才设置,保证原子性加锁PX:过期时间(毫秒),防止死锁
这条命令具备:加锁 + 原子 + 有效期 三大属性。
Java 手撸完整实现(基于 Jedis)
public class RedisDistributedLock {
private final Jedis jedis;
private static final String LOCK_SUCCESS = "OK";
private static final Long RELEASE_SUCCESS = 1L;
public RedisDistributedLock(Jedis jedis) {
this.jedis = jedis;
}
// 加锁
public boolean tryLock(String key, String requestId, int expireTimeMillis) {
String result = jedis.set(key, requestId, "NX", "PX", expireTimeMillis);
return LOCK_SUCCESS.equals(result);
}
// 解锁(用 Lua 脚本确保原子性)
public boolean releaseLock(String key, String requestId) {
String lua = """
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
""";
Object result = jedis.eval(lua, Collections.singletonList(key), Collections.singletonList(requestId));
return RELEASE_SUCCESS.equals(result);
}
}使用方式
String lockKey = "order:lock:789";
String uuid = UUID.randomUUID().toString();
// 加锁
if (lock.tryLock(lockKey, uuid, 10000)) {
try {
// 临界区代码
} finally {
// 解锁
lock.releaseLock(lockKey, uuid);
}
}锁的关键三件事(必须掌握)
| 点 | 说明 |
|---|---|
| 唯一性 | 每个客户端必须设置唯一值,如 UUID |
| 原子性 | 加锁用 SET NX PX,释放锁用 Lua |
| 可重入性 | Redis 本身不支持,需要业务层实现 |
常见问题与误区(务必避坑)
误区1:加锁后忘记设置过期时间
结果:
- 程序 crash 后锁永远不释放,造成死锁
正确做法
- 设置合理
PX,确保 Redis 自动删除
误区2:unlock 使用 del lockKey
风险:
- 若锁超时释放后被别的线程抢到,这时误删别人的锁
正确做法
- 使用 Lua 脚本判断
requestId是否匹配再删
高可用 RedLock 简析(适合 Redis 多节点)
Redlock 设计目标:
- 多个 Redis 实例部署在不同物理机
- 同一客户端需在半数以上节点获取锁才算成功
- 若有节点挂掉也能容错
原理:
客户端并发发起 SET 命令到 N 台 Redis
成功获得 M 台,M >= (N/2 + 1)
在有效时间内拿到,就算成功缺点
- 代码复杂
- 多 Redis 网络延迟会影响吞吐
- 强一致性依然无法完全保障
适用于对可用性 > 一致性的业务
实战建议
| 建议 | 说明 |
|---|---|
| 锁粒度控制 | key 带上业务 ID,避免全局锁 |
| 设置过期时间 | 推荐 10-30s |
| 锁失败降级 | 锁失败后可使用 MQ、异步队列重试 |
| 使用线程隔离 | 限定线程获取锁的超时时间 |
| 保证幂等 | 即使锁失效,操作也能重复执行不出错 |
总结一句话
Redis 实现分布式锁的核心在于:
- 加锁要原子 + 唯一标识 + 超时时间
- 解锁要保证是自己加的锁才释放
- 异常处理要健壮、防止死锁
工程里的关键补充点
续期与超时控制
- 业务执行可能超过过期时间,需要续期机制
- 续期要有上限,避免异常时无限持锁
锁的粒度与热点
- key 设计要细粒度,避免全局锁导致吞吐下降
- 热点资源可拆分为多把锁,结合一致性哈希
释放安全与幂等
- 解锁必须校验持有者,避免误删
- 业务要有幂等保护,防止锁超时带来的重复执行
可观测与告警
- 监控锁等待时间、持锁时长、失败率
- 持锁过久要告警并触发人工排查
