文章目录
- 性能优化
- nginx动静分离
- 优化三级分类的获取(优化业务)
- 分布式缓存
- 整合redis
- 高并发下的缓存失效问题
- 缓存穿透
- 缓存雪崩
- 缓存击穿
- 解决这些问题
- 分布式锁
- Redisson
- 可重入锁(Reentrant Lock)
- 指定过期时间
- 读写锁
- 闭锁
- 信号量
- 使用Redssion解决
- 缓存一致性
性能优化
nginx动静分离
将静态文件上传到/mydata/nginx/html/static目录下
修改地址,加上/static/
修改nginx的配置
修改/mydata/nginx/conf/conf.d下的gulimall.conf
重新启动nginx
之前的路径没有修改完
修改JS的路径,根据前端请求资源失败结果修改页面路径
优化三级分类的获取(优化业务)
在CategoryServiceImpl中
将数据库的多次查询变为一次查询
1、先查询所有的分类
2、将查询具体分类封装成一个方法
3、根据传入参数决定查询的是几级分类
@Override
public Map<String, List<Catelog2Vo>> getCatalogJson() {
/*
* 将数据库的多次查询变为一次
*
*/
List<CategoryEntity> selectList = baseMapper.selectList(null);
//获取所有Catelog2Vo
//1、获取所有一级分类,使用之前的方法getLevel1Categorys
List<CategoryEntity> level1Categorys = getParent_cit(selectList,0L);
//封装为一个map
Map<String, List<Catelog2Vo>> parent_cid = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
//根据一级分类id查询二级分类
List<CategoryEntity> categoryEntities = getParent_cit(selectList,v.getCatId());
List<Catelog2Vo> Catelog2Vos=null;
if (categoryEntities!=null){
Catelog2Vos = categoryEntities.stream().map(level2Category -> {
Catelog2Vo catelog2Vo = new Catelog2Vo(level2Category.getParentCid().toString() , null, level2Category.getCatId().toString(), level2Category.getName());
//查找三级分类
List<CategoryEntity> categoryEntities1 = getParent_cit(selectList,level2Category.getCatId());
List<Catelog2Vo.Catelog3Vo> Catelog3List=null;
if (categoryEntities1!=null){
Catelog3List = categoryEntities1.stream().map(level3Category -> {
Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(level2Category.getCatId().toString(), level3Category.getCatId().toString(), level3Category.getName());
return catelog3Vo;
}).collect(Collectors.toList());
}
catelog2Vo.setCatalog3List(Catelog3List);
return catelog2Vo;
}).collect(Collectors.toList());
}
return Catelog2Vos;
}));
return parent_cid;
}
private List<CategoryEntity> getParent_cit(List<CategoryEntity> selectList,Long parent_cid){
List<CategoryEntity> collect = selectList.stream().filter(item -> {
return item.getParentCid() == parent_cid;
}).collect(Collectors.toList());
return collect;
}
分布式缓存
整合redis
1、引入依赖坐标
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、配置相关配置
spring:
redis:
host: 192.168.205.128
port: 6379
3、测试
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
public void testRedis(){
ValueOperations<String, String> stringStringValueOperations =stringRedisTemplate.opsForValue();
stringStringValueOperations.set("hello","world");
//查询
String s = stringStringValueOperations.get("hello");
System.out.println("缓存的数据是:"+s);
}
}
4、改造三级业务,加入缓存
将原来查询数据库封装对象的操作封装为一个方法getCatalogJsonFromDb()
加入缓存逻辑
@Override
public Map<String, List<Catelog2Vo>> getCatalogJson() {
//1、加入缓存逻辑
String json = redisTemplate.opsForValue().get("getCatalogJson");
if (StringUtils.isEmpty(json)){
//2、缓存中没则查询数据库
Map<String, List<Catelog2Vo>> catalogJsonFromDb = getCatalogJsonFromDb();
//3、查到的数据再放入缓存中
//转换为json字符串
String s = JSON.toJSONString(catalogJsonFromDb);
//放入缓存
redisTemplate.opsForValue().set("getCatalogJson",s);
return catalogJsonFromDb;
}
//能从缓存中获取数据
//将获取的json字符串转换为 Map<String, List<Catelog2Vo>>
Map<String, List<Catelog2Vo>> stringListMap = JSON.parseObject(json, new TypeReference<Map<String, List<Catelog2Vo>>>() {
});
return stringListMap;
}
//从数据库查询并封装数据
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDb() {
/*
* 将数据库的多次查询变为一次
*
*/
List<CategoryEntity> selectList = baseMapper.selectList(null);
//获取所有Catelog2Vo
//1、获取所有一级分类,使用之前的方法getLevel1Categorys
List<CategoryEntity> level1Categorys = getParent_cit(selectList,0L);
//封装为一个map
Map<String, List<Catelog2Vo>> parent_cid = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
//根据一级分类id查询二级分类
List<CategoryEntity> categoryEntities = getParent_cit(selectList,v.getCatId());
List<Catelog2Vo> Catelog2Vos=null;
if (categoryEntities!=null){
Catelog2Vos = categoryEntities.stream().map(level2Category -> {
Catelog2Vo catelog2Vo = new Catelog2Vo(level2Category.getParentCid().toString() , null, level2Category.getCatId().toString(), level2Category.getName());
//查找三级分类
List<CategoryEntity> categoryEntities1 = getParent_cit(selectList,level2Category.getCatId());
List<Catelog2Vo.Catelog3Vo> Catelog3List=null;
if (categoryEntities1!=null){
Catelog3List = categoryEntities1.stream().map(level3Category -> {
Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(level2Category.getCatId().toString(), level3Category.getCatId().toString(), level3Category.getName());
return catelog3Vo;
}).collect(Collectors.toList());
}
catelog2Vo.setCatalog3List(Catelog3List);
return catelog2Vo;
}).collect(Collectors.toList());
}
return Catelog2Vos;
}));
return parent_cid;
}
private List<CategoryEntity> getParent_cit(List<CategoryEntity> selectList,Long parent_cid){
List<CategoryEntity> collect = selectList.stream().filter(item -> {
return item.getParentCid() == parent_cid;
}).collect(Collectors.toList());
return collect;
}
进行压力测试:
发现出现错误—产生堆外内存溢出,OutOfDirectMemoryError
解决方法:1、升级lettuce客户端,2、切换使用jedis
这里选择切换使用jedis
在pom文件中,排除lettuce使用jedis即可
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
高并发下的缓存失效问题
缓存穿透
指查询一个一定不存在的数据,由于缓存未命中,将去查询数据库,但是数据库也无记录,我们没有将这次查询的null写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义
风险:利用不存在的数据进行攻击,数据库瞬间压力增大,最终导致崩溃
解决:null结果缓存,并加入短暂过期时间
缓存雪崩
缓存雪崩是指我们在设置缓存时key采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩
解决:在原有的失效时间基础上增加一个随机值,比如1-15分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引起集体失效的事件
缓存击穿
对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发访问,是一种非常“热点”的数据,如果这个key在大量请求同时进来前正好失效,那么所有对这个key的数据查询都落到db,我们成为缓存击穿
解决:加锁
大量并发只让一个人去查,其他人等待,查到以后释放锁,其他人获得锁,先查缓存,就会有数据,不用去db
解决这些问题
1、空结果缓存,解决缓存穿透
2、设置过期时间,解决缓存雪崩
redisTemplate.opsForValue().set("getCatalogJson",s,1, TimeUnit.DAYS);
3、加锁:解决缓存击穿
给查询数据库的时候,加上本地锁,并且在查询数据库之前,再去判断缓存中有没有数据
//从数据库查询并封装数据
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDb() {
/*
* 将数据库的多次查询变为一次
*
*/
synchronized (this){
//再去查询缓存
String list = redisTemplate.opsForValue().get("getCatalogJson");
if (!StringUtils.isEmpty(list)){
//
Map<String, List<Catelog2Vo>> stringListMap = JSON.parseObject(list, new TypeReference<Map<String, List<Catelog2Vo>>>() {
});
return stringListMap;
}
List<CategoryEntity> selectList = baseMapper.selectList(null);
//获取所有Catelog2Vo
//1、获取所有一级分类,使用之前的方法getLevel1Categorys
List<CategoryEntity> level1Categorys = getParent_cit(selectList,0L);
//封装为一个map
Map<String, List<Catelog2Vo>> parent_cid = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
//根据一级分类id查询二级分类
List<CategoryEntity> categoryEntities = getParent_cit(selectList,v.getCatId());
List<Catelog2Vo> Catelog2Vos=null;
if (categoryEntities!=null){
Catelog2Vos = categoryEntities.stream().map(level2Category -> {
Catelog2Vo catelog2Vo = new Catelog2Vo(level2Category.getParentCid().toString() , null, level2Category.getCatId().toString(), level2Category.getName());
//查找三级分类
List<CategoryEntity> categoryEntities1 = getParent_cit(selectList,level2Category.getCatId());
List<Catelog2Vo.Catelog3Vo> Catelog3List=null;
if (categoryEntities1!=null){
Catelog3List = categoryEntities1.stream().map(level3Category -> {
Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(level2Category.getCatId().toString(), level3Category.getCatId().toString(), level3Category.getName());
return catelog3Vo;
}).collect(Collectors.toList());
}
catelog2Vo.setCatalog3List(Catelog3List);
return catelog2Vo;
}).collect(Collectors.toList());
}
return Catelog2Vos;
}));
return parent_cid;
}
}
但是结果显示查询了多次数据库
原因:
就会导致查询的结果还没来得及放入缓存中,就释放了锁
导致下一个进程也会去查数据库
解决办法:
具体实现:
在查询数据库的方法中的最后
//查询数据库完成后直接放入缓存
String s = JSON.toJSONString(parent_cid);
redisTemplate.opsForValue().set("getCatalogJson",s,1, TimeUnit.DAYS);
return parent_cid;
将getCatalogJson中,查询数据库完成后放入缓存的操作删除
@Override
public Map<String, List<Catelog2Vo>> getCatalogJson() {
//1、加入缓存逻辑
String json = redisTemplate.opsForValue().get("getCatalogJson");
if (StringUtils.isEmpty(json)){
//2、缓存中没则查询数据库
Map<String, List<Catelog2Vo>> catalogJsonFromDb = getCatalogJsonFromDb();
//3、查到的数据会直接放入缓存
return catalogJsonFromDb;
}
//能从缓存中获取数据
//将获取的json字符串转换为 Map<String, List<Catelog2Vo>>
Map<String, List<Catelog2Vo>> stringListMap = JSON.parseObject(json, new TypeReference<Map<String, List<Catelog2Vo>>>() {
});
return stringListMap;
}
完整代码:
//TODO 产生堆外内存溢出,OutOfDirectMemoryError
//1、升级lettuce客户端,2、切换使用jedis
@Override
public Map<String, List<Catelog2Vo>> getCatalogJson() {
//1、加入缓存逻辑
String json = redisTemplate.opsForValue().get("getCatalogJson");
if (StringUtils.isEmpty(json)){
//2、缓存中没则查询数据库
Map<String, List<Catelog2Vo>> catalogJsonFromDb = getCatalogJsonFromDb();
//3、查到的数据会直接放入缓存
return catalogJsonFromDb;
}
System.out.println("缓存命中");
//能从缓存中获取数据
//将获取的json字符串转换为 Map<String, List<Catelog2Vo>>
Map<String, List<Catelog2Vo>> stringListMap = JSON.parseObject(json, new TypeReference<Map<String, List<Catelog2Vo>>>() {
});
return stringListMap;
}
//从数据库查询并封装数据
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDb() {
/*
* 将数据库的多次查询变为一次
*
*/
synchronized (this){
//再去查询缓存
String list = redisTemplate.opsForValue().get("getCatalogJson");
if (!StringUtils.isEmpty(list)){
//
Map<String, List<Catelog2Vo>> stringListMap = JSON.parseObject(list, new TypeReference<Map<String, List<Catelog2Vo>>>() {
});
return stringListMap;
}
List<CategoryEntity> selectList = baseMapper.selectList(null);
//获取所有Catelog2Vo
//1、获取所有一级分类,使用之前的方法getLevel1Categorys
List<CategoryEntity> level1Categorys = getParent_cit(selectList,0L);
//封装为一个map
Map<String, List<Catelog2Vo>> parent_cid = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
//根据一级分类id查询二级分类
List<CategoryEntity> categoryEntities = getParent_cit(selectList,v.getCatId());
List<Catelog2Vo> Catelog2Vos=null;
if (categoryEntities!=null){
Catelog2Vos = categoryEntities.stream().map(level2Category -> {
Catelog2Vo catelog2Vo = new Catelog2Vo(level2Category.getParentCid().toString() , null, level2Category.getCatId().toString(), level2Category.getName());
//查找三级分类
List<CategoryEntity> categoryEntities1 = getParent_cit(selectList,level2Category.getCatId());
List<Catelog2Vo.Catelog3Vo> Catelog3List=null;
if (categoryEntities1!=null){
Catelog3List = categoryEntities1.stream().map(level3Category -> {
Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(level2Category.getCatId().toString(), level3Category.getCatId().toString(), level3Category.getName());
return catelog3Vo;
}).collect(Collectors.toList());
}
catelog2Vo.setCatalog3List(Catelog3List);
return catelog2Vo;
}).collect(Collectors.toList());
}
return Catelog2Vos;
}));
//查询数据库完成后直接放入缓存
String s = JSON.toJSONString(parent_cid);
redisTemplate.opsForValue().set("getCatalogJson",s,1, TimeUnit.DAYS);
return parent_cid;
}
}
private List<CategoryEntity> getParent_cit(List<CategoryEntity> selectList,Long parent_cid){
List<CategoryEntity> collect = selectList.stream().filter(item -> {
return item.getParentCid() == parent_cid;
}).collect(Collectors.toList());
return collect;
}
本地锁只能锁住当前线程,所以我们需要分布式锁
分布式锁
我们可以同时去同一个地方“占坑”,如果占到,就执行逻辑,否则就必须等待,直到释放锁。“占坑”可以去redis,可以去数据库
将对数据库的查询封装为一个方法便于观看
private Map<String, List<Catelog2Vo>> getDataFormDb() {
String list = redisTemplate.opsForValue().get("getCatalogJson");
if (!StringUtils.isEmpty(list)) {
//
Map<String, List<Catelog2Vo>> stringListMap = JSON.parseObject(list, new TypeReference<Map<String, List<Catelog2Vo>>>() {
});
return stringListMap;
}
List<CategoryEntity> selectList = baseMapper.selectList(null);
//获取所有Catelog2Vo
//1、获取所有一级分类,使用之前的方法getLevel1Categorys
List<CategoryEntity> level1Categorys = getParent_cit(selectList, 0L);
//封装为一个map
Map<String, List<Catelog2Vo>> parent_cid = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
//根据一级分类id查询二级分类
List<CategoryEntity> categoryEntities = getParent_cit(selectList, v.getCatId());
List<Catelog2Vo> Catelog2Vos = null;
if (categoryEntities != null) {
Catelog2Vos = categoryEntities.stream().map(level2Category -> {
Catelog2Vo catelog2Vo = new Catelog2Vo(level2Category.getParentCid().toString(), null, level2Category.getCatId().toString(), level2Category.getName());
//查找三级分类
List<CategoryEntity> categoryEntities1 = getParent_cit(selectList, level2Category.getCatId());
List<Catelog2Vo.Catelog3Vo> Catelog3List = null;
if (categoryEntities1 != null) {
Catelog3List = categoryEntities1.stream().map(level3Category -> {
Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(level2Category.getCatId().toString(), level3Category.getCatId().toString(), level3Category.getName());
return catelog3Vo;
}).collect(Collectors.toList());
}
catelog2Vo.setCatalog3List(Catelog3List);
return catelog2Vo;
}).collect(Collectors.toList());
}
return Catelog2Vos;
}));
//查询数据库完成后直接放入缓存
String s = JSON.toJSONString(parent_cid);
redisTemplate.opsForValue().set("getCatalogJson", s, 1, TimeUnit.DAYS);
return parent_cid;
}
使用分布式锁
//查询数据库----使用分布式锁
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {
//1、抢占分布式锁,去redis占坑--设置过期时间
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock","111",300,TimeUnit.SECONDS);
if (lock){
//加锁成功
//设置过期时间----如果在删除锁之前,程序闪断,会造成死锁
// redisTemplate.expire("lock",30,TimeUnit.SECONDS);
//但是在设置过期时间之前程序闪断,还是会出现死锁-----设置过期时间必须和加锁是同步的原子的
//执行业务
Map<String, List<Catelog2Vo>> dataFormDb = getDataFormDb();
//删除锁
redisTemplate.delete("lock");
return dataFormDb;
}
else{
//加锁失败--重试--自旋的方式
return getCatalogJsonFromDbWithRedisLock();
}
}
存在的问题:
如果业务时间很长,锁自己过期了,我们直接删除,有可能把别人正在持有的锁删除了。
解决:占锁的时候,值指定一个UUID
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {
//1、抢占分布式锁,去redis占坑--设置过期时间
String uuid = UUID.randomUUID().toString();
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock",uuid,300,TimeUnit.SECONDS);
if (lock){
//加锁成功
//设置过期时间----如果在删除锁之前,程序闪断,会造成死锁
// redisTemplate.expire("lock",30,TimeUnit.SECONDS);
//但是在设置过期时间之前程序闪断,还是会出现死锁-----设置过期时间必须和加锁是同步的原子的
//执行业务
Map<String, List<Catelog2Vo>> dataFormDb = getDataFormDb();
//删除锁
String lockValue= redisTemplate.opsForValue().get("lock");
if (uuid.equals(lockValue)){
redisTemplate.delete("lock");
}
return dataFormDb;
}
else{
//加锁失败--重试--自旋的方式
return getCatalogJsonFromDbWithRedisLock();
}
}
还是有问题:
如果在查询redis确定该uuid还存在,后返回的途中,uuid被自动删除了,然后执行删除操作,就会删除别人的锁
解决办法:
删锁必须也是原子操作
lua脚本解锁
if redis.call('get',KEY[1]==ARGV[1]
then return redis.call('del',KEYS[1]))
else return 0 end
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {
//1、抢占分布式锁,去redis占坑--设置过期时间
String uuid = UUID.randomUUID().toString();
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock",uuid,300,TimeUnit.SECONDS);
if (lock){
//加锁成功
System.out.println("获取分布式锁成功");
//设置过期时间----如果在删除锁之前,程序闪断,会造成死锁
// redisTemplate.expire("lock",30,TimeUnit.SECONDS);
//但是在设置过期时间之前程序闪断,还是会出现死锁-----设置过期时间必须和加锁是同步的原子的
//执行业务
Map<String, List<Catelog2Vo>> dataFormDb;
try {
dataFormDb = getDataFormDb();
}finally {
String script="if redis.call('get',KEY[1]==ARGV[1] then return redis.call('del',KEYS[1])) else return 0 end";
//删除锁
Long lock1 = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock", uuid));
}
// //删除锁
// String lockValue= redisTemplate.opsForValue().get("lock");
// if (uuid.equals(lockValue)){
// redisTemplate.delete("lock");
// }
return dataFormDb;
}
else{
System.out.println("获取分布式锁失败,等待重试");
//加锁失败--重试--自旋的方式
try {
Thread.sleep(200);
} catch (Exception e) {
}
return getCatalogJsonFromDbWithRedisLock();
}
}
Redisson
整合redisson作为分布式锁等功能框架
1、引入redisson
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.12.0</version>
</dependency>
2、配置redisson
程序化配置方法
@Configuration
public class MyRedissonConfig {
//RedissonClient
@Bean(destroyMethod = "shutdown")
public RedissonClient reddis() throws IOException{
//1、创建配置
Config config=new Config();
config.useSingleServer().setAddress("redis://192.168.205.128:6379");
//2、根据Config,创建出RedissonClient实例
return Redisson.create(config);
}
}
测试是否成功
@Test
public void testReddison(){
System.out.println(redissonClient);
}
可重入锁(Reentrant Lock)
演示:
@ResponseBody
@GetMapping("/hello")
public String hello(){
//1 、获取一把锁,只要锁的名字相同,就是同一把锁
RLock lock = redisson.getLock("my-lock");
//2、加锁
lock.lock();//阻塞式等待,默认加的锁都是30s时间
try{
System.out.println("加锁成功,执行业务、、、、"+Thread.currentThread().getId());
Thread.sleep(3000);
}catch (Exception e){
}finally{
//3、释放锁
System.out.println("释放锁"+Thread.currentThread().getId());
lock.unlock();
}
return "hello";
}
}
访问这个hello
http://localhost:10001/hello
redis中会出现my-lock锁
1、锁的自动续期,如果业务超长,运行期间自动给锁续上新的30s,不用担心业务的时间长,锁自动过期被删除掉
2、加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认也会在30s后自动删除
指定过期时间
lock.lock(10, TimeUnit.SECONDS);//设置自动解锁时间为10秒
此时,自动解锁时间大于业务运行时间
则出现异常,在锁时间到了以后不会自动续期
如果我们传递了锁的超时时间,就发送给redis执行脚本,进行占锁,默认超时时间就是我们指定的时间
如果我们未传递锁的超时时间,就使用30*1000【LockWatchdogTimeOut看门狗的默认时间】;只要占锁成功,就会启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】,每隔10s都会自动续期
读写锁
保证一定能读到最新数据
在修改期间,写锁是一个排他锁,读锁是一个共享锁
写锁没释放读就必须等待
代码演示:
@ResponseBody
@GetMapping("/write")
public String writeValue() {
String s = "";
RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
RLock rLock = readWriteLock.writeLock();//获取写锁
try {
//写数据加写锁
s= UUID.randomUUID().toString();
Thread.sleep(3000);
redisTemplate.opsForValue().set("writeValue", s);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
rLock.unlock();
}
return s;
}
@ResponseBody
@GetMapping("/read")
public String readValue() {
String s="";
RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
RLock rLock = readWriteLock.readLock();
try {
//读数据加读锁
Thread.sleep(3000);
s = redisTemplate.opsForValue().get("writeValue");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
rLock.unlock();
}
return s;
}
演示读:从redis中获取writeValue的值
演示写:写入一个UUID生成的随机数
在写的过程中是读不了的,只有在完成后才能获得最新数据
总结:
读+读:就相当于无锁,并发读,只会在redis中记录好所有当前的读锁。他们都会加锁成功
写+读:等待写锁释放
写+写:阻塞
读+写:有读锁,写需要等待读锁释放
只要有写的存在都必须要等待
闭锁
代码演示
@ResponseBody
@GetMapping("/lockDoor")
public String lockDoor() throws InterruptedException {
RCountDownLatch door=redisson.getCountDownLatch("door");
door.trySetCount(5);//给一个闭锁的计数,完成5个就释放闭锁
door.await();//等待闭锁完成
return "放假了";
}
@ResponseBody
@GetMapping("/gogo/{id}")
public String gogo(@PathVariable("id") Long id){
RCountDownLatch door = redisson.getCountDownLatch("door");
door.countDown();
return id+"班的人都走了";
}
先访问http://localhost:10001/lockDoor页面处于加载状态
然后访问http://localhost:10001/gogo/1,页面显示1班的人走了,一直访问从1到5直到5个班的人都走了,http://localhost:10001/lockDoor页面显示放假了
信号量
模拟车库停车
代码演示:
@ResponseBody
@GetMapping("/park")
public String park() throws InterruptedException {
RSemaphore park = redisson.getSemaphore("park");
park.acquire();
return "停车成功";
}
@ResponseBody
@GetMapping("/go")
public String go() throws InterruptedException {
RSemaphore park = redisson.getSemaphore("park");
park.release();
return "出库成功";
}
给rediss设置一个park
访问http://localhost:10001/park
每访问一次,信号量就会减1
访问http://localhost:10001/go信号量就会加1
其中acquire和tryAcquire的区别
// park.acquire();//阻塞式等待
boolean b = park.tryAcquire();//尝试获取一个信号量,如果尝试失败则返回false,不会阻塞
使用Redssion解决
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedissonLock() {
RLock lock = redisson.getLock("CatelogJson-lock");
lock.lock();
//设置过期时间----如果在删除锁之前,程序闪断,会造成死锁
// redisTemplate.expire("lock",30,TimeUnit.SECONDS);
//但是在设置过期时间之前程序闪断,还是会出现死锁-----设置过期时间必须和加锁是同步的原子的
//执行业务
Map<String, List<Catelog2Vo>> dataFormDb;
try {
dataFormDb = getDataFormDb();
}finally {
lock.unlock();
}
return dataFormDb;
}
缓存一致性
双写模式:当数据库的数据更新后,缓存数据也要修改
失效模式:当数据库的数据更新后,将缓存的数据删除
解决办法:
1、缓存的所有数据都有过期时间,数据过期下一次查询触发主动更新
2、读写数据的时候,加上分布式的读写锁
我们能放入缓存的数据本就不应该是实时性,一致性要求超高的,所以缓存数据的时候加上过期时间,保证每天拿到当前最新数据即可
我们不应该过度设计,增加系统的复杂性
遇到实时性,一致性要求高的数据,就应该查数据库,即使慢点