原生redis实现分布式锁
用 原生 Redis(Jedis、Lettuce) 实现分布式锁,可以参考 Redisson 的原理,但需要自己处理锁的自动续期、故障恢复等细节。核心思路是使用 Redis 的 SET NX EX
或 SET PX NX
命令来实现互斥锁,并利用 Lua 脚本 保障原子性。
实现思路
- 获取锁
SET key value NX PX expiration
,确保锁只能被一个线程获取,并设置过期时间。
- 自动续期
- 通过 后台线程 定时续期,防止业务执行时间过长导致锁超时释放。
- 释放锁
- 通过 Lua 脚本 保证原子性,只有 当前线程 持有锁时才能释放。
完整代码
1. 依赖引入
使用 Jedis 作为 Redis 客户端:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.3.1</version>
</dependency>
2. 分布式锁实现
获取锁
import redis.clients.jedis.Jedis;
import java.util.UUID;
public class RedisLock {
private static final String LOCK_SUCCESS = "OK";
private static final String LOCK_KEY = "my_lock";
private static final int EXPIRE_TIME = 10_000; // 10秒过期
private final Jedis jedis;
private final String lockValue; // 唯一标识锁的持有者
public RedisLock(Jedis jedis) {
this.jedis = jedis;
this.lockValue = UUID.randomUUID().toString(); // 防止误删
}
public boolean tryLock() {
String result = jedis.set(LOCK_KEY, lockValue, "NX", "PX", EXPIRE_TIME);
return LOCK_SUCCESS.equals(result); // 返回 true 代表加锁成功
}
}
自动续期
如果业务执行时间超过 10s,锁会自动释放,所以需要定时续期:
import java.util.concurrent.*;
public class LockRenewal {
private static final int RENEWAL_INTERVAL = 5000; // 每5秒续期
private final Jedis jedis;
private final String lockValue;
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
public LockRenewal(Jedis jedis, String lockValue) {
this.jedis = jedis;
this.lockValue = lockValue;
}
public void startRenewal() {
scheduler.scheduleAtFixedRate(() -> {
if (lockValue.equals(jedis.get("my_lock"))) {
jedis.pexpire("my_lock", 10_000);
}
}, 0, RENEWAL_INTERVAL, TimeUnit.MILLISECONDS);
}
public void stopRenewal() {
scheduler.shutdown();
}
}
释放锁
使用 Lua 脚本,确保只有 持有锁的线程 才能删除:
public void unlock() {
String luaScript =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
jedis.eval(luaScript, 1, "my_lock", lockValue);
}
3. 使用示例
Jedis jedis = new Jedis("localhost", 6379);
RedisLock redisLock = new RedisLock(jedis);
LockRenewal renewal = new LockRenewal(jedis, redisLock.lockValue);
if (redisLock.tryLock()) {
renewal.startRenewal(); // 开启自动续期
try {
// 执行业务逻辑
System.out.println("获取到锁,执行业务...");
Thread.sleep(15000); // 模拟业务执行时间超过锁的原始超时时间
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
redisLock.unlock();
renewal.stopRenewal(); // 停止续期
}
} else {
System.out.println("获取锁失败");
}
总结
- 互斥性:
SET NX PX
确保只有一个线程能获取锁。 - 自动续期:定期
PEXPIRE
延长锁的存活时间。 - 安全释放:Lua 脚本保证只有锁的持有者才能删除。