Redis分布式锁的三种实现方式_分布式锁解决方案
总结写在前面:
RLock rLock = redissonClient.getLock("lbhTestLock");
使用tryLock无参方法时,redisson会自动添加一个定时任务,定时刷新锁的失效时间,如果unlock时失败,则会出现该锁一直不释放的情况。
而当tryLock传释放时间时,则不会添加这个定时任务。
所以说 ,当服务挂了的时候,不会再有定时任务去刷新锁时间,就会解锁。
测试如下:
1、tryLock 无参数
@Test
public void testLock() throws Exception {
RLock rLock = redissonClient.getLock("lbhTestLock");
rLock.tryLock();
new Thread(new Runnable() {
@Override
public void run() {
try {
if (rLock.tryLock()) {
System.out.println("成功获取锁");
} else {
System.out.println("未获取锁");
}
Thread.sleep(35000);
if (rLock.tryLock()) {
System.out.println("成功获取锁");
} else {
System.out.println("未获取锁");
}
} catch (Exception e) {
}
}
}).start();
Thread.sleep(40000);
rLock.unlock();
}
输出:
未获取锁
未获取锁
2、tryLock 传释放时间
rLock.tryLock(0,30,TimeUnit.SECONDS);
输出:
未获取锁 成功获取锁
经测试
@Test
public void testLock2() throws Exception {
new Thread(new Runnable() {
@Override
public void run() {
RLock rLock = redissonClient.getLock("lbhTestLock");
rLock.tryLock();
}
}).start();
Thread.sleep(600000);
//rLock.unlock();
}
redis控制台:
test01:0>ttl lbhTestLock
“29” test01:0>ttl lbhTestLock
“28” test01:0>ttl lbhTestLock
“27” test01:0>ttl lbhTestLock
“25” test01:0>ttl lbhTestLock
“24” test01:0>ttl lbhTestLock
“22” test01:0>ttl lbhTestLock
“21”
//过期时间被重置了 test01:0>ttl lbhTestLock
“28”
// 程序关闭后 test01:0>ttl lbhTestLock
“18” test01:0>ttl lbhTestLock
“15” test01:0>ttl lbhTestLock
“13”
如果调用了tryLock()缺没有unlock或者unlock时失败,如果程序不关闭,另外一个线程永远不会获取到这个锁,因为redisson会刷新该锁的过期时间, ttl 会一直重置,除非redisson实例关闭/程序关闭,由redis自动清除
源码分析:
调用tryLock() 无参方法时:
@Override
public boolean tryLock() {
return get(tryLockAsync());
}
@Override
public RFuture<Boolean> tryLockAsync() {
return tryLockAsync(Thread.currentThread().getId());
}
public RFuture<Boolean> tryLockAsync(long threadId) {
return tryAcquireOnceAsync(-1, null, threadId);
}
private RFuture<Boolean> tryAcquireOnceAsync(long leaseTime, TimeUnit unit, final long threadId) {
//无参时不走这一步
if (leaseTime != -1) {
return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
}
//将key存于redis数据库中
RFuture<Boolean> ttlRemainingFuture = tryLockInnerAsync(LOCK_EXPIRATION_INTERVAL_SECONDS, TimeUnit.SECONDS, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
// 添加Listener 重点!
ttlRemainingFuture.addListener(new FutureListener<Boolean>() {
//future任务执行完毕后会回调
@Override
public void operationComplete(Future<Boolean> future) throws Exception {
if (!future.isSuccess()) {
return;
}
//获取future任务的返回值 即存储是否成功
Boolean ttlRemaining = future.getNow();
// lock acquired
// 如果存储key成功
if (ttlRemaining) {
//添加定时任务 定时刷新锁的过期时间
scheduleExpirationRenewal(threadId);
}
}
});
return ttlRemainingFuture;
}
private void scheduleExpirationRenewal(final long threadId) {
if (expirationRenewalMap.containsKey(getEntryName())) {
return;
}
Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
// 刷新过期时间
RFuture<Boolean> future = commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
//设置key过期时间{防止获取锁后线程挂掉导致死锁}
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return 1; " +
"end; " +
"return 0;",
Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
future.addListener(new FutureListener<Boolean>() {
@Override
public void operationComplete(Future<Boolean> future) throws Exception {
expirationRenewalMap.remove(getEntryName());
if (!future.isSuccess()) {
log.error("Can't update lock " + getName() + " expiration", future.cause());
return;
}
if (future.getNow()) {
// reschedule itself
scheduleExpirationRenewal(threadId);
}
}
});
}
}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
if (expirationRenewalMap.putIfAbsent(getEntryName(), task) != null) {
task.cancel();
}
}
添加一个定时任务 每隔过期时间的1/3(默认30s的1/3 10s)刷新锁的过期时间
//lua脚本解析
首先判断这个锁key的map结构中是否存在对应的key8a9649f5-f5b5-48b4-beaa-d0c24855f9ab:anyLock:1,如果存在,就直接调用pexpire命令设置锁key的过期时间,默认30000毫秒。
关于这一原理,可以参考:https://blog.csdn.net/ice24for/article/details/86177152
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/181714.html原文链接:https://javaforall.cn
相关文章
- k8s部署redis cluster集群的实现
- 实现使用Redis客户端实现高性能分布式缓存(redis的客户端)
- 打开大门分布式Redis进入新时代(打开分布式redis)
- 突破性应用基于Redis的分布式选举系统(根据redis实现选举)
- 探索Redis分布式锁的使用方法(查看redis分布式锁)
- 本地访问远程Redis实现跨地域数据通讯(本地访问远程redis)
- Redis清空重启是否必要(清空redis需要重启吗)
- 缓存深入研究替代Redis自动删除缓存的选项(替代redis自动删除)
- Redis数据库付出了什么样的费用(一个redis的费用)
- Web前端快速构建Redis连接(web前端连接redis)
- 单机搭建Redis集群简单实现分布式环境(单机下redis集群)
- 华为Redis集群探索分布式缓存的极致性能(华为redis集群实例)
- 分布式Redis重温知识和技巧(分布式redis复习)
- 如何自动安装启动Redis数据库(怎么自动启动redis库)
- 如何快速关闭Redis服务(如何关闭redis服务)
- 节点构建Redis集群指定节点实现(redis集群指定)
- Redis集群安装指南一步一步轻松搭建(redis集群安装步骤)
- 稳固安全Redis集群分布式锁(redis集群分布式锁6)
- Redis集群实现分布式部署的新方法(redis集群分布式部署)
- 重建Redis集群修复节点之路(redis集群修复节点)
- 崩溃恢复Redis集群从节点崩溃到恢复(redis 集群 从节点)
- Redis删除优化TP框架解决之道(tp的redis删除)
- 的Redis由谁发明的分布式内存数据库(redis谁发明)
- 查看Redis缓存的简单命令指南(redis查看缓存的命令)