为了系统性能的提升,我们一般都会将部分数据放入缓存中,加速访问。而DB承担数据罗盘工作。
那些数据适合放入缓存?
- 即时性、数据一致性要求不高的
- 访问量大而且更新频率不高的数据(读多,写少)
二、本地缓存与分布式缓存
本地缓存:
本地缓存可以在单体应用中使用,如果分布式的就会出现问题。
分布式缓存-本地模式在分布式下的问题
本地缓存模式下的分布式有问题,会造成缓存不统一,所以,不应该再使用本地模式的缓存
分布式缓存:
分布式模式下,所有的微服务都共用同一个缓存中间件。
三、整合redis#
1、引入redis#
在gulimall-product/pom.xml中引入redis
<!-- 引入redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
在gulimall-product/src/main/resources/application.yml 文件中配置redis:
spring:
datasource:
username: root
password: root
url: jdbc:mysql://192.168.10.10:3306/gulimall_pms
driver-class-name: com.mysql.cj.jdbc.Driver
# 配置nacos注册中心
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
jackson:
date-format: yyyy-MM-dd HH:mm:ss
thymeleaf:
cache: false # 调试期间,关闭缓存
redis:
host: 192.168.10.10
prot: 6379
单元测试:
@RunWith(SpringRunner.class)
@SpringBootTest
public class GulimallProductApplicationTests {
@Autowired
StringRedisTemplate stringRedisTemplate;
@Test
public void redisTest(){
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
ops.set("hello", "lily");
String key = "hello";
System.out.println(ops.get(key));
}
}
gulimall/product/service/impl/CategoryServiceImpl.java /** * 从数据库查询并封装数据::分布式锁 * @return */ public Map<String, List> getCatalogJsonFromDbWithRedisLock() {
//1、占分布式锁。去redis占坑 设置过期时间必须和加锁是同步的,保证原子性(避免死锁)
String uuid = UUID.randomUUID().toString();
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,300,TimeUnit.SECONDS);
if (lock) {
System.out.println("获取分布式锁成功...");
Map<String, List<Catelog2Vo>> dataFromDb = null;
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";
//删除锁
stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
}
//先去redis查询下保证当前的锁是自己的
//获取值对比,对比成功删除=原子性 lua脚本解锁
// String lockValue = stringRedisTemplate.opsForValue().get("lock");
// if (uuid.equals(lockValue)) {
// //删除我自己的锁
// stringRedisTemplate.delete("lock");
// }
return dataFromDb;
} else {
System.out.println("获取分布式锁失败...等待重试...");
//加锁失败...重试机制
//休眠一百毫秒
try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
return getCatalogJsonFromDbWithRedisLock(); //自旋的方式
}
}
private Map<String, List<Catelog2Vo>> getDataFromDb() {
//得到锁以后,我们应该再去缓存中确定一次,如果没有才需要继续查询
String catalogJson = stringRedisTemplate.opsForValue().get("catalogJson");
if (!StringUtils.isEmpty(catalogJson)) {
//缓存不为空直接返回
Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
});
return result;
}
System.out.println("查询了数据库");
/**
* 将数据库的多次查询变为一次
*/
List<CategoryEntity> selectList = this.baseMapper.selectList(null);
//1、查出所有分类
//1、1)查出所有一级分类
List<CategoryEntity> level1Categorys = getParent_cid(selectList, 0L);
//封装数据
Map<String, List<Catelog2Vo>> parentCid = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
//1、每一个的一级分类,查到这个一级分类的二级分类
List<CategoryEntity> categoryEntities = getParent_cid(selectList, v.getCatId());
//2、封装上面的结果
List<Catelog2Vo> catelog2Vos = null;
if (categoryEntities != null) {
catelog2Vos = categoryEntities.stream().map(l2 -> {
Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName().toString());
//1、找当前二级分类的三级分类封装成vo
List<CategoryEntity> level3Catelog = getParent_cid(selectList, l2.getCatId());
if (level3Catelog != null) {
List<Catelog2Vo.Category3Vo> category3Vos = level3Catelog.stream().map(l3 -> {
//2、封装成指定格式
Catelog2Vo.Category3Vo category3Vo = new Catelog2Vo.Category3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
return category3Vo;
}).collect(Collectors.toList());
catelog2Vo.setCatalog3List(category3Vos);
}
return catelog2Vo;
}).collect(Collectors.toList());
}
return catelog2Vos;
}));
//3、将查到的数据放入缓存,将对象转为json
String valueJson = JSON.toJSONString(parentCid);
stringRedisTemplate.opsForValue().set("catalogJson", valueJson, 1, TimeUnit.DAYS);
return parentCid;
}