本文目录导读:

使用Redis实现分布式锁的核心是通过原子操作和过期时间来避免死锁,以下是完整的技术方案和最佳实践。
基础实现(SET NX + 过期时间)
加锁命令
SET lock_key random_value NX PX 30000
NX:键不存在时才能设置成功PX 30000:设置30秒自动过期,防止死锁random_value:唯一随机值,用于安全释放锁
Java代码示例
import redis.clients.jedis.Jedis;
import java.util.UUID;
public class RedisDistributedLock {
private static final String LOCK_SUCCESS = "OK";
private static final Long RELEASE_SUCCESS = 1L;
private Jedis jedis;
private String lockKey;
private String requestId;
private int expireTime = 30000; // 30秒过期
public boolean tryLock(String lockKey) {
requestId = UUID.randomUUID().toString();
String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
return LOCK_SUCCESS.equals(result);
}
public boolean unlock(String lockKey) {
// Lua脚本保证原子性
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 RELEASE_SUCCESS.equals(result);
}
}
完整避免死锁的策略
设置合理的过期时间
// 根据业务执行时间动态调整 int estimatedTime = 5000; // 预估执行时间 int expireTime = estimatedTime * 3; // 设置3倍缓冲 jedis.set(key, value, "NX", "PX", expireTime);
自动续期(看门狗模式)
public class LockWithWatchdog {
private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
private volatile boolean running = true;
public boolean tryLock(String key, int expireTime) {
if (acquireLock(key, expireTime)) {
startWatchdog(key, expireTime);
return true;
}
return false;
}
private void startWatchdog(String key, int expireTime) {
scheduler.scheduleAtFixedRate(() -> {
if (running) {
// 续期操作
jedis.expire(key, expireTime / 1000);
}
}, expireTime / 3, expireTime / 3, TimeUnit.MILLISECONDS);
}
public void unlock(String key) {
running = false;
scheduler.shutdown();
// 释放锁的Lua脚本
}
}
Redlock算法(高可用场景)
public class Redlock {
private List<Jedis> redisNodes; // 多个独立的Redis节点
public boolean tryLock(String key, int ttl) {
int n = redisNodes.size();
int quorum = n / 2 + 1;
int startTime = System.currentTimeMillis();
int lockCount = 0;
for (Jedis node : redisNodes) {
String result = node.set(key, value, "NX", "PX", ttl);
if ("OK".equals(result)) {
lockCount++;
}
}
int elapsedTime = System.currentTimeMillis() - startTime;
if (lockCount >= quorum && elapsedTime < ttl) {
// 获取锁成功
return true;
} else {
// 释放部分获取的锁
for (Jedis node : redisNodes) {
unlock(node, key, value);
}
return false;
}
}
}
最佳实践模板
完整的生产级实现
import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
public class RedisLockTemplate {
private Jedis jedis;
public <T> T executeWithLock(String lockKey,
int expireTimeMs,
int waitTimeoutMs,
Supplier<T> action) {
String requestId = UUID.randomUUID().toString();
long startTime = System.currentTimeMillis();
try {
// 自旋获取锁
while (System.currentTimeMillis() - startTime < waitTimeoutMs) {
String result = jedis.set(lockKey, requestId,
SetParams.setParams().nx().px(expireTimeMs));
if ("OK".equals(result)) {
try {
// 执行业务逻辑
return action.get();
} finally {
// 安全释放锁
releaseLock(lockKey, requestId);
}
}
// 避免忙等,稍作休眠
Thread.sleep(100);
}
throw new RuntimeException("获取锁超时");
} catch (Exception e) {
throw new RuntimeException("执行失败", e);
}
}
private boolean releaseLock(String key, 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(key),
Collections.singletonList(requestId));
return Long.valueOf(1).equals(result);
}
}
// 使用示例
RedisLockTemplate lockTemplate = new RedisLockTemplate();
Integer result = lockTemplate.executeWithLock(
"order:123",
30000, // 锁过期时间
5000, // 等待超时时间
() -> {
// 业务逻辑
return processOrder(123);
}
);
注意事项与常见陷阱
❌ 常见错误
- 不用原子操作:先检查再删除,非原子操作可能导致误删
- 过期时间设置不合理:太短业务没完成,太长影响并发
- 使用相同的value:无法区分不同客户端,可能释放别人的锁
✅ 正确做法
- 使用Lua脚本保证原子性
- 生成唯一随机值(UUID)
- 设置合理的过期时间 + 续期机制
- 考虑时钟漂移问题(Redlock中特别重要)
性能优化建议
| 场景 | 推荐方案 |
|---|---|
| 低并发、简单需求 | SET NX + 过期时间 |
| 高并发、需要自动续期 | 看门狗模式 |
| 分布式、高可靠性 | Redlock算法 |
| 已使用Redisson | 直接使用其内置Lock |
最简可靠性方案:
// 加锁
SET key uuid NX PX 30000
// 释放锁(Lua脚本保证原子性)
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
这个方案通过过期时间防止死锁,通过Lua脚本保证原子释放,是生产环境最常用的实现方式,对于高可用场景,建议使用Redlock或直接采用Redisson等成熟框架。