目录
🚗🚗🚗1.引入缓存的优势
🚗🚗🚗2.哪些数据适合放入缓存
🚗🚗🚗3.使用redis作为缓存组件
先确保reidis正常启动
3.1配置redis
- 1.引入依赖
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 2.配置reids信息
spring
redis:
port: 6379
host: ip地址
password: XXX
3.2优化查询
之前都是从数据库查询的,现在加入缓存逻辑~
/**
* 使用redis缓存
*/
@Override
public Map<String, List<Catalog2Vo>> getCatalogJson() {
//1.加入缓存,缓存中存的数据全都是json
String catalogJson = redisTemplate.opsForValue().get("catalogJson");
if (StringUtils.isEmpty(catalogJson)) {
//2.缓存中如果没有,再去数据库查找
Map<String, List<Catalog2Vo>> catalogJsonFromDB = getCatalogJsonFromDB();
//3.将数据库查到的数据,将对象转换为json存放到缓存
String s = JSON.toJSONString(catalogJsonFromDB);
redisTemplate.opsForValue().set("catalogJson", s);
}
//4.从缓存中获取,转换为我们指定的类型
Map<String, List<Catalog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {
});
return result;
}
/**
* 从数据库查询并封装的分类数据
*
* @return
*/
public Map<String, List<Catalog2Vo>> getCatalogJsonFromDB() {
/**
* 优化:将数据库查询的多次变为一次
*/
List<CategoryEntity> selectList = baseMapper.selectList(null);
//1.查出所有1级分类
List<CategoryEntity> leve1Categorys = getParent_cid(selectList, 0L);
//2.封装数据
Map<String, List<Catalog2Vo>> parentCid = leve1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
//1.每一个一级分类
List<CategoryEntity> categoryEntities = getParent_cid(selectList, v.getCatId());
List<Catalog2Vo> catalog2Vos = null;
if (categoryEntities != null) {
catalog2Vos = categoryEntities.stream().map(l2 -> {
Catalog2Vo vo = new Catalog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());
//找二级分类的三级分类
List<CategoryEntity> categoryEntities3 = getParent_cid(selectList, l2.getCatId());
if (categoryEntities3 != null) {
List<Catalog2Vo.Catalog3Vo> collect = categoryEntities3.stream().map(l3 -> {
Catalog2Vo.Catalog3Vo catalog3Vo = new Catalog2Vo.Catalog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
return catalog3Vo;
}).collect(Collectors.toList());
vo.setCatalog3List(collect);
}
return vo;
}).collect(Collectors.toList());
}
return catalog2Vos;
}));
return parentCid;
}
3.3测试
在本地第一次查询后,查看redis,发现redis已经存储
使用JMeter压测一下
🚗🚗🚗4.redis存在的问题
4.1缓存穿透
4.2缓存雪崩
4.3缓存击穿
🚗🚗🚗5.添加本地锁
/**
* 使用redis缓存
*/
@Override
public Map<String, List<Catalog2Vo>> getCatalogJson() {
//1.使用redis缓存,存储为json对象
String catalogJson = redisTemplate.opsForValue().get("catalogJson");
//2.判断缓存中是否有
if (StringUtils.isEmpty(catalogJson)) {
System.out.println("缓存没有命中~查询数据库...");
//3.如果缓存中没有,从数据库
Map<String, List<Catalog2Vo>> catalogJsonFromDB = getCatalogJsonFromDB();
return catalogJsonFromDB;
}
System.out.println("缓存命中....直接返回");
//5.如果缓存中有,转换为我们需要的类型
Map<String, List<Catalog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {
});
return result;
}
/**
* 从数据库查询并封装的分类数据
*
* @return
*/
public Map<String, List<Catalog2Vo>> getCatalogJsonFromDB() {
/**
* 优化:将数据库查询的多次变为一次
*/
//TODO 本地锁,在分布式下,必须使用分布式锁
//加锁,防止缓存击穿,使用同一把锁
synchronized (this) {
//加所以后,我们还要去缓存中确定一次,如果缓存中没有,才继续查数据库
String catalogJson = redisTemplate.opsForValue().get("catalogJson");
if (!StringUtils.isEmpty(catalogJson)) {
//如果缓存中有,从缓存中获取
Map<String, List<Catalog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {
});
return result;
}
System.out.println("查询了数据库~");
List<CategoryEntity> selectList = baseMapper.selectList(null);
//1.查出所有1级分类
List<CategoryEntity> leve1Categorys = getParent_cid(selectList, 0L);
//2.封装数据
Map<String, List<Catalog2Vo>> parentCid = leve1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
//1.每一个一级分类
List<CategoryEntity> categoryEntities = getParent_cid(selectList, v.getCatId());
List<Catalog2Vo> catalog2Vos = null;
if (categoryEntities != null) {
catalog2Vos = categoryEntities.stream().map(l2 -> {
Catalog2Vo vo = new Catalog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());
//找二级分类的三级分类
List<CategoryEntity> categoryEntities3 = getParent_cid(selectList, l2.getCatId());
if (categoryEntities3 != null) {
List<Catalog2Vo.Catalog3Vo> collect = categoryEntities3.stream().map(l3 -> {
Catalog2Vo.Catalog3Vo catalog3Vo = new Catalog2Vo.Catalog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
return catalog3Vo;
}).collect(Collectors.toList());
vo.setCatalog3List(collect);
}
return vo;
}).collect(Collectors.toList());
}
return catalog2Vos;
}));
//4.将从数据库中获取的数据,转换为Json存储到redis
String s = JSON.toJSONString(parentCid);
//设置缓存时间,方式雪崩
redisTemplate.opsForValue().set("catalogJson", s, 1, TimeUnit.DAYS);
return parentCid;
}
}
🚗🚗🚗6.添加分布式锁
使用分布式锁 步骤
/**
* 使用redis缓存
*/
@Override
public Map<String, List<Catalog2Vo>> getCatalogJson() {
//1.使用redis缓存,存储为json对象
String catalogJson = redisTemplate.opsForValue().get("catalogJson");
//2.判断缓存中是否有
if (StringUtils.isEmpty(catalogJson)) {
System.out.println("缓存没有命中~查询数据库...");
//3.如果缓存中没有,从数据库
Map<String, List<Catalog2Vo>> catalogJsonFromDB = getCatalogJsonFromDBWithRedisLock();
return catalogJsonFromDB;
}
System.out.println("缓存命中....直接返回");
//5.如果缓存中有,转换为我们需要的类型
Map<String, List<Catalog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {
});
return result;
}
/**
* 从数据库中获取数据,使用分布所锁
*
* @return
*/
public Map<String, List<Catalog2Vo>> getCatalogJsonFromDBWithRedisLock() {
//1.占分布式锁,去redis占位
String uuid = UUID.randomUUID().toString();
//2.设置过期时间,必须和加锁同步
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
if (lock) {
System.out.println("获取分布式锁成功..." + redisTemplate.opsForValue().get("lock"));
//加锁成功...执行业务
Map<String, List<Catalog2Vo>> dataFromDb;
try {
dataFromDb = getDataFromDb();
} finally {
String script = "if redis.call('get', KEYS[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);
}
return getDataFromDb();
} else {
//加锁失败...重试
//休眠100毫秒
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
return getCatalogJsonFromDBWithRedisLock();//自旋方式
}
}
/**
* 提起方法,从数据库中获取
*
* @return
*/
private Map<String, List<Catalog2Vo>> getDataFromDb() {
//加所以后,我们还要去缓存中确定一次,如果缓存中没有,才继续查数据库
String catalogJson = redisTemplate.opsForValue().get("catalogJson");
if (!StringUtils.isEmpty(catalogJson)) {
//如果缓存中有,从缓存中获取
Map<String, List<Catalog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {
});
return result;
}
System.out.println("查询了数据库~");
List<CategoryEntity> selectList = baseMapper.selectList(null);
//1.查出所有1级分类
List<CategoryEntity> leve1Categorys = getParent_cid(selectList, 0L);
//2.封装数据
Map<String, List<Catalog2Vo>> parentCid = leve1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
//1.每一个一级分类
List<CategoryEntity> categoryEntities = getParent_cid(selectList, v.getCatId());
List<Catalog2Vo> catalog2Vos = null;
if (categoryEntities != null) {
catalog2Vos = categoryEntities.stream().map(l2 -> {
Catalog2Vo vo = new Catalog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());
//找二级分类的三级分类
List<CategoryEntity> categoryEntities3 = getParent_cid(selectList, l2.getCatId());
if (categoryEntities3 != null) {
List<Catalog2Vo.Catalog3Vo> collect = categoryEntities3.stream().map(l3 -> {
Catalog2Vo.Catalog3Vo catalog3Vo = new Catalog2Vo.Catalog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
return catalog3Vo;
}).collect(Collectors.toList());
vo.setCatalog3List(collect);
}
return vo;
}).collect(Collectors.toList());
}
return catalog2Vos;
}));
//4.将从数据库中获取的数据,转换为Json存储到redis
String s = JSON.toJSONString(parentCid);
//设置缓存时间,方式雪崩
redisTemplate.opsForValue().set("catalogJson", s, 1, TimeUnit.DAYS);
return parentCid;
}
🚗🚗🚗7.整合redisson作为分布式锁
7.1引入依赖
<!--redisson作为所有分布式锁-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.12.0</version>
</dependency>
7.2程序化配置
在配置地址时,一定要添加reds://
@Configuration
public class MyRedissonConfig {
@Bean(destroyMethod = "shutdown")
public RedissonClient redissonClient() throws IOException {
//1.创建配置
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.20.130:6379");
//2.根据config创建redisson实例
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
}
7.3实例解析
@Autowired
RedissonClient redissonClient;
@ResponseBody
@GetMapping("/hello")
public String hello() {
//1.设置redis的key获取一把锁,只要名字相同,就是同一把锁
RLock lock = redissonClient.getLock("my-lock");
//2.手动枷锁
lock.lock();//阻塞式等待,默认30秒
try {
//3.执行业务
System.out.println("枷锁成功!" + Thread.currentThread().getName());
//4.模拟业务消耗时间
Thread.sleep(20000);
} catch (Exception e) {
} finally {
//3.释放锁
System.out.println("释放锁~"+Thread.currentThread().getName());
lock.unlock();//不删除,默认30秒后过期,自动删除
}
return "hello";
}
7.4读写锁
@GetMapping("/write")
@ResponseBody
public String write() {
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("readWrite-lock");
String word = "";
RLock rLock = readWriteLock.writeLock();
try {
//该数据加写锁,读数据加读锁
rLock.lock();
word = UUID.randomUUID().toString();
Thread.sleep(3000);
redisTemplate.opsForValue().set("writeValue", word);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rLock.unlock();
}
return word;
}
/**
* 读写锁
* @return
*/
@GetMapping("/read")
@ResponseBody
public String read() {
String word = "";
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("readWrite-lock");
//加读锁
RLock rLock = readWriteLock.readLock();
rLock.lock();
try {
word = redisTemplate.opsForValue().get("writeValue");
} catch (Exception e) {
e.printStackTrace();
}finally {
rLock.unlock();
}
return word;
}