文章目录
- 本地缓存:和微服务同一个进程。缺点:分布式时
- 分布式缓存:缓存中间件
本地缓存
本地缓存问题:每个微服务都要有缓存服务、数据更新时只更新自己的缓存,造成缓存数据不一致
解决方案:分布式缓存,微服务共用 缓存中间件
- 采用map来存缓存,直接放到内存中,但是本地缓存就会在分布式服务中出现问题,各个服务的缓存都是不一样的
分布式缓存
redis
整合redis的步骤
- 引入依赖
- 配置redis信息
- 直接使用spring自动配置的RedisTemplate
1、product导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、配置redis主机地址
spring:
redis:
host: localhost
port: 6379
# 可以指定账号密码
3、自动注入了RedisTemplate
,优化菜单获取业务getCatalogJson
- 测试
StringRedisTemplate
@Autowired // 注入StringRedisTemplate
StringRedisTemplate stringRedisTemplate;
@Test
public void test(){
// 相当于拿到redis的map
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
// 保存 set
// 获取 get
}
- 业务getCatalogJson加入缓存逻辑
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
// 每次从缓存中获取
String catalogJson = ops.get("catalogJson");
// 判断redis中是否有此缓存
if (catalogJson == null) {
// 没有,则从数据库中查询
Map<String, List<Catalog2Vo>> categoriesDb = getCategoriesDb();
// 先将数据库的数据转为json在放入缓存中,这也就是【序列化】
// JSON跨语言,跨平台兼容
String toJSONString = JSON.toJSONString(categoriesDb);
// 查到数据放到redis中
ops.set("catalogJson",toJSONString);
return categoriesDb;
}
// 如果缓存有,先【反序列化】,再返回
Map<String, List<Catalog2Vo>> listMap = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {
});
return listMap;
堆外内存溢出
- 当进行压力测试时后期后出现堆外内存溢出OutOfDirectMemoryError
产生原因:
1、springboot2.0以后默认使用lettuce
作为操作redis的客户端,它使用netty进行网络通信
2、lettuce的bug导致netty堆外内存溢出。netty如果没有指定堆外内存,默认使用Xms的值,可以使用-Dio.netty.maxDirectMemory
来设置
解决方案:由于是lettuce的bug造成,不要直接使用-Dio.netty.maxDirectMemory去调大虚拟机堆外内存。
-
升级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>
lettuce和jedis是操作redis的底层客户端,RedisTemplate是再次封装
压力测试
缓存失效
缓存穿透
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为10000的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
解决方案:
- 缓存我们的null对象,但是要设置短暂的过期时间,防止以后有这个数据,而缓存中还是缓存的null
缓存雪崩
缓存雪崩是指在我们设置缓存时key采用了相同的过期时间,导致缓存在某一时刻缓存大面积的同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。
解决方案:
- 避免雪崩:缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
- 如果缓存数据库是分布式部署,将热点数据均匀分布在不同缓存数据库中。
- 设置热点数据永远不过期。
- 出现雪崩:降级 熔断
- 事前:尽量保证整个 redis 集群的高可用性,发现机器宕机尽快补上。选择合适的内存淘汰策略。
- 事中:本地ehcache缓存 + hystrix限流&降级,避免MySQL崩掉
- 事后:利用 redis 持久化机制保存的数据尽快恢复缓存
缓存击穿
三者不同的是:
- 缓存击穿指并发查同一条数据。缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
- 缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
- 缓存穿透也是指并发查同一条数据,但是穿透是数据库也没有此数据
解决方案:
- 设置热点数据永远不过期。
- 加互斥锁
缓存击穿:加锁
本地锁
1、本地锁synchronized(this)、JUC(Lock)
-
竞争到锁后,确认缓存中没有,再去查数据库,将数据放入缓存中,再去释放锁。
-
Springboot所有的组件都是单例的,那么锁this就可以把所有的请求都锁住,但是这是单服务上的,如果是多服务就有多个实例,那么synchronized就不管用了
复制微服务
1、右键点击服务,copy configuration