Spring Boot基于Redisson实现Redis分布式可重入锁源码详解
来源:亿速云
2023-05-07 19:53:55
0浏览
收藏
大家好,我们又见面了啊~本文《Spring Boot基于Redisson实现Redis分布式可重入锁源码详解》的内容中将会涉及到等等。如果你正在学习数据库相关知识,欢迎关注我,以后会给大家带来更多数据库相关文章,希望我们能一起进步!下面就开始本文的正式内容~
一、前言
我们在实现使用Redis实现分布式锁,最开始一般使用SET resource-name anystring NX EX max-lock-time进行加锁,使用Lua脚本保证原子性进行实现释放锁。这样手动实现比较麻烦,对此Redis官网也明确说Java版使用Redisson来实现。小编也是看了官网慢慢的摸索清楚,特写此记录一下。从官网到整合Springboot到源码解读,以单节点为例。
二、为什么使用Redisson
1. 我们打开官网
redis中文官网
2. 我们可以看到官方让我们去使用其他

3. 打开官方推荐

4. 找到文档
Redisson地址

5. Redisson结构

三、Springboot整合Redisson
1. 导入依赖
<dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-data-redis</artifactid></dependency><dependency> <groupid>redis.clients</groupid> <artifactid>jedis</artifactid></dependency><!--redis分布式锁--><dependency> <groupid>org.redisson</groupid> <artifactid>redisson</artifactid> <version>3.12.0</version></dependency>
2. 以官网为例查看如何配置

3. 编写配置类
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author wangzhenjun
* @date 2022/2/9 9:57
*/
@Configuration
public class MyRedissonConfig {
/**
* 所有对redisson的使用都是通过RedissonClient来操作的
* @return
*/
@Bean(destroyMethod="shutdown")
public RedissonClient redisson(){
// 1. 创建配置
Config config = new Config();
// 一定要加redis://
config.useSingleServer().setAddress("redis://192.168.17.130:6379");
// 2. 根据config创建出redissonClient实例
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
}4. 官网测试加锁例子

5. 根据官网简单Controller接口编写
@ResponseBody
@GetMapping("/hello")
public String hello(){
// 1.获取一把锁,只要锁名字一样,就是同一把锁
RLock lock = redisson.getLock("my-lock");
// 2. 加锁
lock.lock();// 阻塞试等待 默认加的都是30s
// 带参数情况
// lock.lock(10, TimeUnit.SECONDS);// 10s自动解锁,自动解锁时间一定要大于业务的执行时间。
try {
System.out.println("加锁成功" + Thread.currentThread().getId());
Thread.sleep(30000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 3. 解锁
System.out.println("解锁成功:" + Thread.currentThread().getId());
lock.unlock();
}
return "hello";
}6. 测试

四、lock.lock()源码分析
1. 打开RedissonLock实现类

2. 找到实现方法
@Override
public void lock() {
try {
// 我们发现不穿过期时间源码默认过期时间为-1
lock(-1, null, false);
} catch (InterruptedException e) {
throw new IllegalStateException();
}
}3. 按住Ctrl进去lock方法
private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
// 获取线程的id,占有锁的时候field的值为UUID:线程号id
long threadId = Thread.currentThread().getId();
// 尝试获得锁
Long ttl = tryAcquire(leaseTime, unit, threadId);
// lock acquired 获得锁,返回
if (ttl == null) {
return;
}
// 这里说明获取锁失败,就通过线程id订阅这个锁
RFuture<redissonlockentry> future = subscribe(threadId);
if (interruptibly) {
commandExecutor.syncSubscriptionInterrupted(future);
} else {
commandExecutor.syncSubscription(future);
}
try {
// 这里进行自旋,不断尝试获取锁
while (true) {
// 继续尝试获取锁
ttl = tryAcquire(leaseTime, unit, threadId);
// lock acquired 获取成功
if (ttl == null) {
// 直接返回,挑出自旋
break;
}
// waiting for message 继续等待获得锁
if (ttl >= 0) {
try {
future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
if (interruptibly) {
throw e;
}
future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
}
} else {
if (interruptibly) {
future.getNow().getLatch().acquire();
} else {
future.getNow().getLatch().acquireUninterruptibly();
}
}
}
} finally {
// 取消订阅
unsubscribe(future, threadId);
}
// get(lockAsync(leaseTime, unit));
}</redissonlockentry>4. 进去尝试获取锁方法

private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) {
// 直接进入异步方法
return get(tryAcquireAsync(leaseTime, unit, threadId));
}
private <t> RFuture<long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {
// 这里进行判断如果没有设置参数leaseTime = -1
if (leaseTime != -1) {
return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
}
// 此方法进行获得锁,过期时间为看门狗的默认时间
// private long lockWatchdogTimeout = 30 * 1000;看门狗默认过期时间为30s
// 加锁和过期时间要保证原子性,这个方法后面肯定调用执行了Lua脚本,我们下面在看
RFuture<long> ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
// 开启一个定时任务进行不断刷新过期时间
ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
if (e != null) {
return;
}
// lock acquired 获得锁
if (ttlRemaining == null) {
// 刷新过期时间方法,我们下一步详细说一下
scheduleExpirationRenewal(threadId);
});
return ttlRemainingFuture;</long></long></t>5. 查看tryLockInnerAsync()方法
<t> RFuture<t> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<t> command) {
internalLockLeaseTime = unit.toMillis(leaseTime);
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
// 首先判断锁是否存在
"if (redis.call('exists', KEYS[1]) == 0) then " +
// 存在则获取锁
"redis.call('hset', KEYS[1], ARGV[2], 1); " +
// 然后设置过期时间
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
// hexists查看哈希表的指定字段是否存在,存在锁并且是当前线程持有锁
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
// hincrby自增一
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
// 锁的值大于1,说明是可重入锁,重置过期时间
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
// 锁已存在,且不是本线程,则返回过期时间ttl
"return redis.call('pttl', KEYS[1]);",
Collections.<object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
}</object></t></t></t>6. 进入4留下的定时任务scheduleExpirationRenewal()方法
一步步往下找源码:scheduleExpirationRenewal --->renewExpiration
根据下面源码,定时任务刷新时间为:internalLockLeaseTime / 3,是看门狗的1/3,即为10s刷新一次
private void renewExpiration() {
ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
if (ee == null) {
return;
}
Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());
if (ent == null) {
return;
}
Long threadId = ent.getFirstThreadId();
if (threadId == null) {
return;
}
RFuture<boolean> future = renewExpirationAsync(threadId);
future.onComplete((res, e) -> {
if (e != null) {
log.error("Can't update lock " + getName() + " expiration", e);
return;
}
if (res) {
// reschedule itself
renewExpiration();
}
});
}
}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
ee.setTimeout(task);
}</boolean>五、lock.lock(10, TimeUnit.SECONDS)源码分析
1. 打开实现类
@Override
public void lock(long leaseTime, TimeUnit unit) {
try {
// 这里的过期时间为我们输入的10
lock(leaseTime, unit, false);
} catch (InterruptedException e) {
throw new IllegalStateException();
}
}2. 方法lock()实现展示,同三.3源码
3. 直接来到尝试获得锁tryAcquireAsync()方法
private <t> RFuture<long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {
// 这里进行判断如果没有设置参数leaseTime = -1,此时我们为10
if (leaseTime != -1) {
// 来到此方法
return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
}
// 此处省略后面内容,前面以详细说明。。。。
}</long></t>4. 打开tryLockInnerAsync()方法
我们不难发现和没有传过期时间的方法一样,只不过leaseTime的值变了。
<t> RFuture<t> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<t> command) {
internalLockLeaseTime = unit.toMillis(leaseTime);
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
// 首先判断锁是否存在
"if (redis.call('exists', KEYS[1]) == 0) then " +
// 存在则获取锁
"redis.call('hset', KEYS[1], ARGV[2], 1); " +
// 然后设置过期时间
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
// hexists查看哈希表的指定字段是否存在,存在锁并且是当前线程持有锁
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
// hincrby自增一
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
// 锁的值大于1,说明是可重入锁,重置过期时间
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
// 锁已存在,且不是本线程,则返回过期时间ttl
"return redis.call('pttl', KEYS[1]);",
Collections.<object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
}</object></t></t></t>六、lock.unlock()源码分析
1. 打开方法实现
@Override
public void unlock() {
try {
// 点击进入释放锁方法
get(unlockAsync(Thread.currentThread().getId()));
} catch (RedisException e) {
if (e.getCause() instanceof IllegalMonitorStateException) {
throw (IllegalMonitorStateException) e.getCause();
} else {
throw e;
}
}
// Future<void> future = unlockAsync();
// future.awaitUninterruptibly();
// if (future.isSuccess()) {
// return;
// }
// if (future.cause() instanceof IllegalMonitorStateException) {
// throw (IllegalMonitorStateException)future.cause();
// }
// throw commandExecutor.convertException(future);
}</void>2. 打开unlockAsync()方法
@Override
public RFuture<void> unlockAsync(long threadId) {
RPromise<void> result = new RedissonPromise<void>();
// 解锁方法,后面展开说
RFuture<boolean> future = unlockInnerAsync(threadId);
// 完成
future.onComplete((opStatus, e) -> {
if (e != null) {
// 取消到期续订
cancelExpirationRenewal(threadId);
// 将这个未来标记为失败并通知所有人
result.tryFailure(e);
return;
}
// 状态为空,说明解锁的线程和当前锁不是同一个线程
if (opStatus == null) {
IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: "
+ id + " thread-id: " + threadId);
result.tryFailure(cause);
return;
}
cancelExpirationRenewal(threadId);
result.trySuccess(null);
});
return result;
}</boolean></void></void></void>3. 打开unlockInnerAsync()方法
protected RFuture<boolean> unlockInnerAsync(long threadId) {
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
// 判断释放锁的线程和已存在锁的线程是不是同一个线程,不是返回空
"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
"return nil;" +
"end; " +
// 释放锁后,加锁次数减一
"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
// 判断剩余数量是否大于0
"if (counter > 0) then " +
// 大于0 ,则刷新过期时间
"redis.call('pexpire', KEYS[1], ARGV[2]); " +
"return 0; " +
"else " +
// 释放锁,删除key并发布锁释放的消息
"redis.call('del', KEYS[1]); " +
"redis.call('publish', KEYS[2], ARGV[1]); " +
"return 1; "+
"end; " +
"return nil;",
Arrays.<object>asList(getName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));
}</object></boolean> 以上就是本文的全部内容了,是否有顺利帮助你解决问题?若是能给你带来学习上的帮助,请大家多多支持golang学习网!更多关于数据库的相关知识,也可关注golang学习网公众号。
版本声明
本文转载于:亿速云 如有侵犯,请联系study_golang@163.com删除
Java与Mysql锁的相关知识点有哪些?
- 上一篇
- Java与Mysql锁的相关知识点有哪些?
- 下一篇
- MySQL关联查询的注意事项
查看更多
最新文章
-
- 数据库 · Redis | 1小时前 |
- 扩展Redis集群节点的完整步骤与注意事项
- 403浏览 收藏
-
- 数据库 · Redis | 2天前 |
- 监控Redis集群健康状态的工具与指标
- 112浏览 收藏
-
- 数据库 · Redis | 1星期前 |
- Redis数据安全防护全攻略
- 252浏览 收藏
-
- 数据库 · Redis | 2星期前 |
- Redis主从复制故障排查与修复技巧
- 302浏览 收藏
-
- 数据库 · Redis | 2星期前 |
- Redis与HBase存储方案详解
- 325浏览 收藏
-
- 数据库 · Redis | 2星期前 |
- Redis数据安全防护全攻略
- 157浏览 收藏
-
- 数据库 · Redis | 3星期前 |
- 高并发Redis优化技巧分享
- 257浏览 收藏
-
- 数据库 · Redis | 3星期前 |
- Redis数据安全防护全攻略
- 398浏览 收藏
-
- 数据库 · Redis | 3星期前 |
- Redis配置加密方法与安全设置
- 232浏览 收藏
-
- 数据库 · Redis | 3星期前 |
- RedisHyperLogLog高效统计技巧
- 283浏览 收藏
-
- 数据库 · Redis | 3星期前 |
- Redis与MySQL缓存同步方法详解
- 141浏览 收藏
-
- 数据库 · Redis | 4星期前 |
- Redis布隆过滤器防穿透原理解析
- 312浏览 收藏
查看更多
课程推荐
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
查看更多
AI推荐
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3186次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3398次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3429次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4535次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 3807次使用
查看更多
相关文章
-
- redis复制有可能碰到的问题汇总
- 2023-01-01 501浏览
-
- 使用lua+redis解决发多张券的并发问题
- 2023-01-27 501浏览
-
- Redis应用实例分享:社交媒体平台设计
- 2023-06-21 501浏览
-
- 使用Python和Redis构建日志分析系统:如何实时监控系统运行状况
- 2023-08-08 501浏览
-
- 如何利用Redis和Python实现消息队列功能
- 2023-08-16 501浏览

