Redis分布式锁的几种实现方法
作者:今天多喝热水
本文主要介绍了Redis分布式锁的几种实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
Redis基本命令:
// 设置键myKey的值为myValue,并且该键在10秒后过期 SET myKey myValue EX 10 // 设置键myKey的值为myValue,并且该键在1000毫秒(1秒)后过期 SET myKey myValue PX 1000 // 指定key过期时间,单位是秒,过期后自动删除 EXPIRE key_name second_num // 指定key过期时间,单位是毫秒,过期后自动删除 PEXPIRE key_name millisecond_num // 返回key过期时间 TTL key_name SET key value //设置键key的值为value SETNX key value //只有在键key不存在的情况下,将key的值设置为value SETEX key seconds value //将键key的值设置为value,并且超时时间为seconds秒 PSETEX key milliseconds value //将键key的值设置为value,并且超时时间为milliseconds毫秒
一、基础方案:SETNX命令实现
public class SimpleRedisLock {
private Jedis jedis;
private String lockKey;
public SimpleRedisLock(Jedis jedis, String lockKey) {
this.jedis = jedis;
this.lockKey = lockKey;
}
public boolean tryLock() {
Long result = jedis.setnx(lockKey, "locked");
if (result == 1) {
jedis.expire(lockKey, 30); // 设置过期时间
return true;
}
return false;
}
public void unlock() {
jedis.del(lockKey);
}
}
// 使用示例
Jedis jedis = new Jedis("localhost");
SimpleRedisLock lock = new SimpleRedisLock(jedis, "order_lock");
try{
if(lock.tryLock()){
// 业务逻辑
}
} finally {
lock.unlock();
}
问题分析:
- 非原子操作:setnx和expire非原子操作,可能产生死锁
- 锁误删:任何客户端都可以删除锁
- 不可重入:同一线程重复获取会失败
二、改进方案:原子SET命令
public class AtomicRedisLock {
private Jedis jedis;
private String lockKey;
private String clientId;
public SimpleRedisLock(Jedis jedis, String lockKey) {
this.jedis = jedis;
this.lockKey = lockKey;
this.clientId = UUID.randomUUID().toString();
}
public boolean tryLock(int expireSeconds) {
String result = jedis.set(lockKey, clientId, SetParams.setParams().nx().ex(expireSeconds));
return "OK".equals(result);
}
public boolean unlock() {
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(clientId));
return result.equals(1L);
}
}
// 使用示例
Jedis jedis = new Jedis("localhost");
AtomicRedisLock lock = new AtomicRedisLock(jedis, "payment_lock");
try{
if(lock.tryLock(30)){
// 业务逻辑
}
} finally {
lock.unlock();
}核心改进:
- 使用原子SET命令:SET key value NX EX
- Lua脚本保证删除原子性
- 客户端唯一标识防止误删
仍然存在的问题:
- 锁续期困难
- 单点故障风险
- 业务超时可能导致锁失效
三、高可用方案:RedLock算法
public class RedLock {
pprivate List<Jedis> jedisList;
private String lockKey;
private String clientId;
private int quorum;
public RedLock(List<Jedis> jedisList, String lockKey) {
this.jedisList = jedisList;
this.lockKey = lockKey;
this.clientId = UUID.randomUUID().toString();
this.quorum = jedisList.size() / 2 + 1;
}
public boolean tryLock(int expireMillis) {
long startTime = System.currentTimeMillis();
// 第一阶段:尝试获取多数节点锁
int successCount = 0;
for (Jedis jedis : jedisList) {
if (tryAcquire(jedis, expireMillis)) {
successCount++;
}
if ((System.currentTimeMillis() - startTime) > expireMillis) {
break;
}
}
// 第二阶段:验证锁有效性
if (successCount >= quorum) {
long validityTime = expireMillis - (System.currentTimeMillis() - startTime);
return validityTime > 0;
}
// 第三阶段:释放已获得的锁
for (Jedis jedis : jedisList) {
release(jedis);
}
return false;
}
private boolean tryAcquire(Jedis jedis, long expireMillis) {
try {
String result = jedis.set(lockKey, clientId, SetParams.setParams().nx().px(expireMillis));
return "OK".equals(result);
} catch (Exception e) {
return false;
}
}
private void release(Jedis jedis) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) else return 0 end";
jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(clientId));
}
}部署要求:
- 至少5个独立Redis实例
- 节点间时钟同步
- 需要配置合理的超时时间
适用场景:
- 金融交易等对可靠性要求极高的场景
- 需要跨机房部署的分布式系统
四、生产级方案:Redisson实现
// 配置Redisson客户端
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
// 获取锁对象
RLock lock = redisson.getLock("orderLock");
try {
// 尝试加锁,最多等待100秒,锁定后30秒自动解锁
boolean isLock = lock.tryLock(100, 30, TimeUnit.SECONDS);
if (isLock) {
// 处理业务
}
} finally {
lock.unlock();
}
// 关闭客户端
redisson.shutdown();
// 自动续期机制(Watchdog),Watchdog实现原理(简化版)
private void renewExpiration() {
Timeout task = commandExecutor.schedule(() -> {
if (redisClient.eval(...)){ // 检查是否仍持有锁
expireAsync(); // 续期
renewExpiration(); // 递归调用
}
}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
}
核心特性:
- 支持可重入锁
- 提供公平锁、联锁(MultiLock)、红锁(RedLock)实现
- 完善的故障处理机制
到此这篇关于Redis分布式锁的几种实现方法的文章就介绍到这了,更多相关Redis分布式锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
