网站设计需求方案,橱柜网站模板,国外html响应式网站模板下载,wordpress toc微服务实战项目-学成在线-项目优化(redis缓存优化)
1 优化需求
视频播放页面用户未登录也可以访问#xff0c;当用户观看试学课程时需要请求服务端查询数据#xff0c;接口如下#xff1a;
1、根据课程id查询课程信息。
2、根据文件id查询视频信息。
这些接口在用户未认…微服务实战项目-学成在线-项目优化(redis缓存优化)
1 优化需求
视频播放页面用户未登录也可以访问当用户观看试学课程时需要请求服务端查询数据接口如下
1、根据课程id查询课程信息。
2、根据文件id查询视频信息。
这些接口在用户未认证状态下也可以访问如果接口的性能不高当高并发到来很可能耗尽整个系统的资源将整个系统压垮所以特别需要对这些暴露在外边的接口进行优化。
下边对 根据课程id查询课程信息 接口进行优化下边的内容将此接口简称为课程查询接口。
接口地址http://www.51xuecheng.cn/open/content/course/whole/{courseId} 2 压力测试
2.1 性能指标
对接口进行优化之前需要对接口进行压力测试不仅接口需要压力测试整个微服务在发布前也是需要经历压力测试的因为压力测试可以暴露功能测试所发现不了的问题。
功能测试即是对系统的功能按用户需求进行测试比如添加一门课程根据需求文档先准备测试数据再通过前端界面将一门课程添加到系统测试是否可以操作成功。整个过程就是测试软件是否可以实现用户的需求。
压力测试是通过测试工具制造大规模的并发请求去访问系统测试系统是否经受住压力。
比如一个在线学习网站上线要求该网站可以支持1万用户同时在线此时就需要模拟1万并发请求去访问网站的关键业务流程比如测试点播学习流程测试系统是否可以抗住1万并发请求。
一些功能测试时无法发现的问题在压力测试时就会发现比如内存泄露、线程安全、IO异常等问题。
压力测试常用的性能指标如下
1、吞吐量
吞吐量是系统每秒可以处理的事务数也称为TPSTransaction Per Second。
比如一次点播流程从请求进入系统到视频画图显示出来这整个流程就是一次事务。
所以吞吐量并不是一次数据库事务它是完成一次业务的整体流程。
2、响应时间
响应时间是指客户端请求服务端从请求进入系统到客户端拿到响应结果所经历的时间。响应时间包括最大响应时间、最小响应时间、平均响应时间。
3、每秒查询数
每秒查询数即QPSQueries-per-second它是衡量查询接口的性能指标比如商品信息查询 一秒可以请求该接口查询商品信息的次数就是QPS。
拿查询接口举例一次查询请求内部不会再去请求其它接口此时 QPSTPS
如果一次查询请求内容需要远程调用另一个接口查询数据此时 QPS2 * TPS
4、错误率
错误率 是一批请求发生错误的请求占全部请求的比例。
不同的指标其要求不同比如现在进行接口优化优化后的接口响应时间应该越来越小吞吐量越来越大以及QPS值也是越大越好错误率要保持在一个很小的范围。
另外除了关注这些性能指标以外还要关注系统的负载情况
1、CPU使用率不高于85%
2、内存利用率不高于 85%
3、网络利用率不高于 80%
4、磁盘IO
磁盘IO的性能指标是IOPS (Input/Output Per Second)即每秒的输入输出量(或读写次数)。
如果过大说明IO操作密集IO过大也会影响性能指标。
2.2 安装Jmeter
Apache JMeter 是 Apache 组织基于 Java 开发的压力测试工具用于对软件做压力测试。
下载Jmeter
https://jmeter.apache.org/download_jmeter.cgi 下载解压进入bin目录修改jmeter.properties设置中文和字体
languagezh_CN
jmeter.hidpi.modetrue
jmeter.hidpi.scale.factor1.8
jsyntaxtextarea.font.family Hack
jsyntaxtextarea.font.size25
jmeter.toolbar.icons.size32x32双击运行bin目录下的jmeter.bat文件。 界面如下图 2.3 压力测试
样本数200个线程每个线程请求100次共20000次
压力机通常压力机是单独的客户端。
测试gatewaycontent
吞吐量180左右 测试content
吞吐量300左右 2.4 优化日志
内容管理日志级别改为info级别. 单独请求内容管理测试吞吐量达到1500左右 3 缓存优化
3.1 redis缓存
测试用例是根据id查询课程信息这里不存在复杂的SQL也不存在数据库连接不释放的问题暂时不考虑数据库方面的优化。
课程发布信息的特点的是查询较多修改很少这里考虑将课程发布信息进行缓存。
课程信息缓存的流程如下 在nacos配置redis-dev.yamlgroupxuecheng-plus-common
spring: redis:host: 192.168.101.65port: 6379password: redisdatabase: 0lettuce:pool:max-active: 20max-idle: 10min-idle: 0timeout: 10000在content-api微服务加载redis-dev.yaml
shared-configs:- data-id: redis-${spring.profiles.active}.yamlgroup: xuecheng-plus-commonrefresh: true在content-service微服务中添加依赖
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId
/dependency
dependencygroupIdorg.apache.commons/groupIdartifactIdcommons-pool2/artifactIdversion2.6.2/version
/dependency定义查询缓存接口
/*** description 查询缓存中的课程信息* param courseId * return com.xuecheng.content.model.po.CoursePublish* author Mr.M* date 2022/10/22 16:15
*/
public CoursePublish getCoursePublishCache(Long courseId);接口实现如下
public CoursePublish getCoursePublishCache(Long courseId){//查询缓存Object jsonObj redisTemplate.opsForValue().get(course: courseId);if(jsonObj!null){String jsonString jsonObj.toString();System.out.println(从缓存查);CoursePublish coursePublish JSON.parseObject(jsonString, CoursePublish.class);return coursePublish;} else {System.out.println(从数据库查询...);//从数据库查询CoursePublish coursePublish getCoursePublish(courseId);if(coursePublish!null){redisTemplate.opsForValue().set(course: courseId, JSON.toJSONString(coursePublish));}return coursePublish;}
}
}修改controller接口调用代码
ApiOperation(获取课程发布信息)ResponseBodyGetMapping(/course/whole/{courseId})public CoursePreviewDto getCoursePublish(PathVariable(courseId) Long courseId) {//查询课程发布信息CoursePublish coursePublish coursePublishService.getCoursePublishCache(courseId);
// CoursePublish coursePublish coursePublishService.getCoursePublish(courseId);if(coursePublishnull){return new CoursePreviewDto();}//课程基本信息CourseBaseInfoDto courseBase new CourseBaseInfoDto();BeanUtils.copyProperties(coursePublish, courseBase);//课程计划ListTeachplanDto teachplans JSON.parseArray(coursePublish.getTeachplan(), TeachplanDto.class);CoursePreviewDto coursePreviewInfo new CoursePreviewDto();coursePreviewInfo.setCourseBase(courseBase);coursePreviewInfo.setTeachplans(teachplans);return coursePreviewInfo;}重新测试请求内容管理服务课程查询接口。
吞吐量达到2700左右增加了近一倍。 3.2 缓存穿透问题
3.2.1 什么是缓存穿透
使用缓存后代码的性能有了很大的提高虽然性能有很大的提升但是控制台打出了很多从数据库查询的日志明明判断了如果缓存存在课程信息则从缓存查询为什么要有这么多从数据库查询的请求的
这是因为并发数高很多线程会同时到达查询数据库代码处去执行。
我们分析下代码 如果存在恶意攻击的可能如果有大量并发去查询一个不存在的课程信息会出现什么问题呢
比如去请求/content/course/whole/181查询181号课程该课程并不在课程发布表中。
进行压力测试发现会去请求数据库。
大量并发去访问一个数据库不存在的数据由于缓存中没有该数据导致大量并发查询数据库这个现象要缓存穿透。 缓存穿透可以造成数据库瞬间压力过大连接数等资源用完最终数据库拒绝连接不可用。
3.2.2 解决缓存穿透
如何解决缓存穿透?
1、对请求增加校验机制
比如课程Id是长整型如果发来的不是长整型则直接返回。
2、使用布隆过滤器
什么是布隆过滤器以下摘自百度百科
布隆过滤器可以用于检索一个元素是否在一个集合中。如果想要判断一个元素是不是在一个集合里一般想到的是将所有元素保存起来然后通过比较确定。链表树等等数据结构都是这种思路. 但是随着集合中元素的增加我们需要的存储空间越来越大检索速度也越来越慢(O(n),O(logn))。不过世界上还有一种叫作散列表又叫哈希表Hash table的数据结构。它可以通过一个Hash函数将一个元素映射成一个位阵列Bit array中的一个点。这样一来我们只要看看这个点是不是1就可以知道集合中有没有它了。这就是布隆过滤器的基本思想。
布隆过滤器的特点是高效地插入和查询占用空间少查询结果有不确定性如果查询结果是存在则元素不一定存在如果不存在则一定不存在另外它只能添加元素不能删除元素因为删除元素会增加误判率。
比如将商品id写入布隆过滤器如果分3次hash此时在布隆过滤器有3个点当从布隆过滤器查询该商品id通过hash找到了该商品id在过滤器中的点此时返回1如果找不到一定会返回0。
所以为了避免缓存穿透我们需要缓存预热将要查询的课程或商品信息的id提前存入布隆过滤器添加数据时将信息的id也存入过滤器当去查询一个数据时先在布隆过滤器中找一下如果没有到到就说明不存在此时直接返回。
实现方法有
Google工具包Guava实现。
redisson 。
2、缓存空值或特殊值
请求通过了第一步的校验查询数据库得到的数据不存在此时我们仍然去缓存数据缓存一个空值或一个特殊值的数据。
但是要注意如果缓存了空值或特殊值要设置一个短暂的过期时间。
public CoursePublish getCoursePublishCache(Long courseId) {//查询缓存Object jsonObj redisTemplate.opsForValue().get(course: courseId);if(jsonObj!null){String jsonString jsonObj.toString();if(jsonString.equals(null))return null;CoursePublish coursePublish JSON.parseObject(jsonString, CoursePublish.class);return coursePublish;} else {//从数据库查询System.out.println(从数据库查询数据...);CoursePublish coursePublish getCoursePublish(courseId);//设置过期时间300秒redisTemplate.opsForValue().set(course: courseId, JSON.toJSONString(coursePublish),30, TimeUnit.SECONDS);return coursePublish;}
}再测试虽然还存在个别请求去查询数据库但不是所有请求都去查询数据库基本上都命中缓存。
3.3 缓存雪崩
3.3.1 什么是缓存雪崩
缓存雪崩是缓存中大量key失效后当高并发到来时导致大量请求到数据库瞬间耗尽数据库资源导致数据库无法使用。
造成缓存雪崩问题的原因是是大量key拥有了相同的过期时间比如对课程信息设置缓存过期时间为10分钟在大量请求同时查询大量的课程信息时此时就会有大量的课程存在相同的过期时间一旦失效将同时失效造成雪崩问题。
3.3.2 解决缓存雪崩
如何解决缓存雪崩
1、使用同步锁控制查询数据库的线程
使用同步锁控制查询数据库的线程只允许有一个线程去查询数据库查询得到数据后存入缓存。
synchronized(obj){//查询数据库//存入缓存
}2、对同一类型信息的key设置不同的过期时间
通常对一类信息的key设置的过期时间是相同的这里可以在原有固定时间的基础上加上一个随机时间使它们的过期时间都不相同。
示例代码如下 //设置过期时间300秒redisTemplate.opsForValue().set(course: courseId, JSON.toJSONString(coursePublish),300new Random().nextInt(100), TimeUnit.SECONDS);3、缓存预热
不用等到请求到来再去查询数据库存入缓存可以提前将数据存入缓存。使用缓存预热机制通常有专门的后台程序去将数据库的数据同步到缓存。
3.4 缓存击穿
3.4.1 什么是缓存击穿
缓存击穿是指大量并发访问同一个热点数据当热点数据失效后同时去请求数据库瞬间耗尽数据库资源导致数据库无法使用。
比如某手机新品发布当缓存失效时有大量并发到来导致同时去访问数据库。 3.4.2 解决缓存击穿
如何解决缓存击穿
1、使用同步锁控制查询数据库的线程
使用同步锁控制查询数据库的代码只允许有一个线程去查询数据库查询得到数据库存入缓存。
synchronized(obj){//查询数据库//存入缓存
}2、热点数据不过期
可以由后台程序提前将热点数据加入缓存缓存过期时间不过期由后台程序做好缓存同步。
下边使用synchronized对代码加锁。
public CoursePublish getCoursePublishCache(Long courseId){synchronized(this){//查询缓存String jsonString (String) redisTemplate.opsForValue().get(course: courseId);if(StringUtils.isNotEmpty(jsonString)){if(jsonString.equals(null))return null;CoursePublish coursePublish JSON.parseObject(jsonString, CoursePublish.class);return coursePublish;}else{System.out.println(从数据库查询);//从数据库查询CoursePublish coursePublish getCoursePublish(courseId);//设置过期时间300秒redisTemplate.opsForValue().set(course: courseId, JSON.toJSONString(coursePublish),300, TimeUnit.SECONDS);return coursePublish;}}}测试吞吐量有1300左右 对上边的代码进行优化对查询缓存的代码不用synchronized加锁控制只对查询数据库进行加锁如下
public CoursePublish getCoursePublishCache(Long courseId){//查询缓存Object jsonObj redisTemplate.opsForValue().get(course: courseId);if(jsonObj!null){String jsonString jsonObj.toString();CoursePublish coursePublish JSON.parseObject(jsonString, CoursePublish.class);return coursePublish;}else{synchronized(this){Object jsonObj redisTemplate.opsForValue().get(course: courseId);if(jsonObj!null){String jsonString jsonObj.toString();CoursePublish coursePublish JSON.parseObject(jsonString, CoursePublish.class);return coursePublish;}System.out.println(从数据库查询);//从数据库查询CoursePublish coursePublish getCoursePublish(courseId);//设置过期时间300秒redisTemplate.opsForValue().set(course: courseId, JSON.toJSONString(coursePublish),300, TimeUnit.SECONDS);return coursePublish;}}}测试查询数据库只发生一次整个测试过程的吞吐量在3800左右。 3.4.3 小结
1缓存穿透
去访问一个数据库不存在的数据无法将数据进行缓存导致查询数据库当并发较大就会对数据库造成压力。缓存穿透可以造成数据库瞬间压力过大连接数等资源用完最终数据库拒绝连接不可用。
解决的方法
缓存一个null值。
使用布隆过滤器。
2缓存雪崩
缓存中大量key失效后当高并发到来时导致大量请求到数据库瞬间耗尽数据库资源导致数据库无法使用。
造成缓存雪崩问题的原因是是大量key拥有了相同的过期时间。
解决办法
使用同步锁控制
对同一类型信息的key设置不同的过期时间比如使用固定数随机数作为过期时间。
3缓存击穿
大量并发访问同一个热点数据当热点数据失效后同时去请求数据库瞬间耗尽数据库资源导致数据库无法使用。
解决办法
使用同步锁控制
设置key永不过期
无中生有是穿透布隆过滤null隔离。 缓存击穿key过期 锁与非期解难题。 大量过期成雪崩过期时间要随机。 面试必考三兄弟可用限流来保底。
限流技术方案
alibaba/Sentinel
nginxLua
3.5 分布式锁
3.5.1 本地锁的问题
上边的程序使用了同步锁解决了缓存击穿、缓存雪崩的问题保证同一个key过期后只会查询一次数据库。
如果将同步锁的程序分布式部署在多个虚拟机上则无法保证同一个key只会查询一次数据库如下图 一个同步锁程序只能保证同一个虚拟机中多个线程只有一个线程去数据库如果高并发通过网关负载均衡转发给各个虚拟机此时就会存在多个线程去查询数据库情况因为虚拟机中的锁只能保证该虚拟机自己的线程去同步执行无法跨虚拟机保证同步执行。
我们将虚拟机内部的锁叫本地锁本地锁只能保证所在虚拟机的线程同步执行。
下边进行测试
启动三个内容管理服务 通过网关访问课程查询网关通过负载均衡将请求转发给三个服务。
通过测试发现有两个服务各有一次数据库查询这说明本地锁无法跨虚拟机保证同步执行。
3.5.2 什么是分布锁
本地锁只能控制所在虚拟机中的线程同步执行现在要实现分布式环境下所有虚拟机中的线程去同步执行就需要让多个虚拟机去共用一个锁虚拟机可以分布式部署锁也可以分布式部署如下图 虚拟机都去抢占同一个锁锁是一个单独的程序提供加锁、解锁服务谁抢到锁谁去查询数据库。
该锁已不属于某个虚拟机而是分布式部署由多个虚拟机所共享这种锁叫分布式锁。
3.5.3 分布式锁的实现方案
实现分布式锁的方案有很多常用的如下
1、基于数据库实现分布锁
利用数据库主键唯一性的特点或利用数据库唯一索引的特点多个线程同时去插入相同的记录谁插入成功谁就抢到锁。
2、基于redis实现锁
redis提供了分布式锁的实现方案比如SETNX、set nx、redisson等。
拿SETNX举例说明SETNX命令的工作过程是去set一个不存在的key多个线程去设置同一个key只会有一个线程设置成功设置成功的的线程拿到锁。
3、使用zookeeper实现
zookeeper是一个分布式协调服务主要解决分布式程序之间的同步的问题。zookeeper的结构类似的文件目录多线程向zookeeper创建一个子目录(节点)只会有一个创建成功利用此特点可以实现分布式锁谁创建该结点成功谁就获得锁。
3.5.4 Redis NX实现分布式锁
redis实现分布式锁的方案可以在redis.cn网站查阅地址http://www.redis.cn/commands/set.html
使用命令 SET resource-name anystring NX EX max-lock-time 即可实现。
NX表示key不存在才设置成功。
EX设置过期时间
这里启动三个ssh客户端连接redis: docker exec -it redis redis-cli
先认证: auth redis
同时向三个客户端发送测试命令如下
表示设置lock001锁value为001过期时间为30秒
SET lock001 001 NX EX 30命令发送成功观察三个ssh客户端发现只有一个设置成功其它两个设置失败设置成功的请求表示抢到了lock001锁。
如何在代码中使用Set nx去实现分布锁呢
使用spring-boot-starter-data-redis 提供的api即可实现set nx。
添加依赖
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId
/dependency
dependencygroupIdorg.apache.commons/groupIdartifactIdcommons-pool2/artifactIdversion2.6.2/version
/dependency添加依赖后在bean中注入restTemplate。
我们先分析一段伪代码如下
if(缓存中有){返回缓存中的数据
}else{获取分布式锁if(获取锁成功{try{查询数据库}finally{释放锁}}}1、获取分布式锁
使用redisTemplate.opsForValue().setIfAbsent(key,vaue)获取锁
这里考虑一个问题当set nx一个key/value成功1后这个key(就是锁)需要设置过期时间吗
如果不设置过期时间当获取到了锁却没有执行finally这个锁将会一直存在其它线程无法获取这个锁。
所以执行set nx时要指定过期时间即使用如下的命令
SET resource-name anystring NX EX max-lock-time
具体调用的方法是redisTemplate.opsForValue().setIfAbsent(K var1, V var2, long var3, TimeUnit var5)
2、如何释放锁
释放锁分为两种情况key到期自动释放手动删除。
1key到期自动释放的方法
因为锁设置了过期时间key到期会自动释放但是会存在一个问题就是 查询数据库等操作还没有执行完时key到期了此时其它线程就抢到锁了最终重复查询数据库执行了重复的业务操作。
怎么解决这个问题
可以将key的到期时间设置的长一些足以执行完成查询数据库并设置缓存等相关操作。
如果这样效率会低一些另外这个时间值也不好把控。
2手动删除锁
如果是采用手动删除锁可能和key到期自动删除有所冲突造成删除了别人的锁。
比如当查询数据库等业务还没有执行完时key过期了此时其它线程占用了锁当上一个线程执行查询数据库等业务操作完成后手动删除锁就把其它线程的锁给删除了。
要解决这个问题可以采用删除锁之前判断是不是自己设置的锁伪代码如下
if(缓存中有){返回缓存中的数据
}else{获取分布式锁: set lock 01 NXif(获取锁成功{try{查询数据库}finally{if(redis.call(get,lock)01){释放锁: redis.call(del,lock)}}}}以上代码第11行到13行非原子性也会导致删除其它线程的锁。
查看文档上的说明http://www.redis.cn/commands/set.html 在调用setnx命令设置key/value时每个线程设置不一样的value值这样当线程去删除锁时可以先根据key查询出来判断是不是自己当时设置的vlaue如果是则删除。
这整个操作是原子的实现方法就是去执行上边的lua脚本。
Lua 是一个小巧的脚本语言redis在2.6版本就支持通过执行Lua脚本保证多个命令的原子性。
什么是原子性
这些指令要么全成功要么全失败。
以上就是使用Redis Nx方式实现分布式锁为了避免删除别的线程设置的锁需要使用redis去执行Lua脚本的方式去实现这样就具有原子性但是过期时间的值设置不存在不精确的问题。
3.5.5 Redisson实现分布式锁
3.5.5.1 什么是Redisson
再查阅 文档http://www.redis.cn/commands/set.html 点击链接查看 我们选用Java的实现方案 https://github.com/redisson/redisson
Redisson的文档地址https://github.com/redisson/redisson/wiki/Table-of-Content
Redisson底层采用的是Netty 框架。支持Redis 2.8以上版本支持Java1.6以上版本。Redisson是一个在Redis的基础上实现的Java驻内存数据网格In-Memory Data Grid。它不仅提供了一系列的分布式的Java常用对象还提供了许多分布式服务。其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service) 。 使用Redisson可以非常方便将Java本地内存中的常用数据结构的对象搬到分布式缓存redis中。
也可以将常用的并发编程工具如AtomicLong、CountDownLatch、Semaphore等支持分布式。
使用RScheduledExecutorService 实现分布式调度服务。
支持数据分片将数据分片存储到不同的redis实例中。
支持分布式锁基于Java的Lock接口实现分布式锁方便开发。
下边使用Redisson将Queue队列的数据存入Redis实现一个排队及出队的接口。 添加redisson的依赖
dependencygroupIdorg.redisson/groupIdartifactIdredisson-spring-boot-starter/artifactIdversion3.11.2/version
/dependency从课程资料目录拷贝singleServerConfig.yaml到config工程下
在redis配置文件中添加
spring:redis:redisson:#配置文件目录config: classpath:singleServerConfig.yaml#config: classpath:clusterServersConfig.yamlredis集群配置clusterServersConfig.yaml.
Redisson相比set nx实现分布式锁要简单的多工作原理如下 加锁机制
线程去获取锁获取成功: 执行lua脚本保存数据到redis数据库。
线程去获取锁获取失败: 一直通过while循环尝试获取锁获取成功后执行lua脚本保存数据到redis
WatchDog自动延期看门狗机制
第一种情况在一个分布式环境下假如一个线程获得锁后突然服务器宕机了那么这个时候在一定时间后这个锁会自动释放你也可以设置锁的有效时间(当不设置默认30秒时这样的目的主要是防止死锁的发生 第二种情况线程A业务还没有执行完时间就过了线程A 还想持有锁的话就会启动一个watch dog后台线程不断的延长锁key的生存时间。 lua脚本-保证原子性操作
主要是如果你的业务逻辑复杂的话通过封装在lua脚本中发送给redis而且redis是单线程的这样就保证这段复杂业务逻辑执行的原子性
具体使用RLock操作分布锁RLock继承JDK的Lock接口所以他有Lock接口的所有特性比如lock、unlock、trylock等特性,同时它还有很多新特性强制锁释放带有效期的锁,。
public interface RRLock {//----------------------Lock接口方法-----------------------/*** 加锁 锁的有效期默认30秒*/void lock();/*** 加锁 可以手动设置锁的有效时间** param leaseTime 锁有效时间* param unit 时间单位 小时、分、秒、毫秒等*/void lock(long leaseTime, TimeUnit unit);/*** tryLock()方法是有返回值的用来尝试获取锁* 如果获取成功则返回true如果获取失败即锁已被其他线程获取则返回false .*/boolean tryLock();/*** tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的* 只不过区别在于这个方法在拿不到锁时会等待一定的时间* 在时间期限之内如果还拿不到锁就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁则返回true。** param time 等待时间* param unit 时间单位 小时、分、秒、毫秒等*/boolean tryLock(long time, TimeUnit unit) throws InterruptedException;/*** 比上面多一个参数多添加一个锁的有效时间** param waitTime 等待时间* param leaseTime 锁有效时间* param unit 时间单位 小时、分、秒、毫秒等* waitTime 大于 leaseTime*/boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException;/*** 解锁*/void unlock();
}lock()
此方法为加锁但是锁的有效期采用默认30秒
如果主线程未释放且当前锁未调用unlock方法则进入到watchDog机制
如果主线程未释放且当前锁调用unlock方法则直接释放锁
3.5.5.2 分布式锁避免缓存击穿
下边使用分布式锁修改查询课程信息的接口。
//Redisson分布式锁
public CoursePublish getCoursePublishCache(Long courseId){//查询缓存String jsonString (String) redisTemplate.opsForValue().get(course: courseId);if(StringUtils.isNotEmpty(jsonString)){if(jsonString.equals(null)){return null;}CoursePublish coursePublish JSON.parseObject(jsonString, CoursePublish.class);return coursePublish;}else{//每门课程设置一个锁RLock lock redissonClient.getLock(coursequerylock:courseId);//获取锁lock.lock();try {jsonString (String) redisTemplate.opsForValue().get(course: courseId);if(StringUtils.isNotEmpty(jsonString)){CoursePublish coursePublish JSON.parseObject(jsonString, CoursePublish.class);return coursePublish;}System.out.println(从数据库查询);//从数据库查询CoursePublish coursePublish getCoursePublish(courseId);redisTemplate.opsForValue().set(course: courseId, JSON.toJSONString(coursePublish),1,TimeUnit.DAYS);return coursePublish;}finally {//释放锁lock.unlock();}}}启动多个内容管理服务实例使用JMeter压力测试只有一个实例查询一次数据库。
测试Redisson自动续期功能。
在查询数据库处添加休眠观察锁是否会自动续期。
try {Thread.sleep(60000);
} catch (InterruptedException e) {throw new RuntimeException(e);
}