Redis缓存使用
Redis引入
导入redis
<!-- 引入redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置:
spring:
redis:
host: localhost
port: 6379
测试:
@Autowired
StringRedisTemplate stringRedisTemplate;
@Test
public void testStringRedisTemplate(){
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
ops.set("hello", "world"+ UUID.randomUUID().toString());
String hello = ops.get("hello");
System.out.println("redxis中保存的值是:"+hello);
}
修改获取三级分类菜单,使用redis做缓存:
@Override
public Map<String, List<Catelog2Vo>> getCatalogJsonFromRedis() {
//1.加入缓存 给缓存中放json字符串,方便以后使用
String catalogJson = stringRedisTemplate.opsForValue().get("catalogJson");
if (StringUtils.isEmpty(catalogJson)) {
//缓存中没有数据,查询数据库
Map<String, List<Catelog2Vo>> catalogJsonFromDb = getCatalogJsonFromDb();
//转为json放入缓存中
String jsonString = JSON.toJSONString(catalogJsonFromDb);
stringRedisTemplate.opsForValue().set("catalogJson", jsonString);
return catalogJsonFromDb;
}
Map<String, List<Catelog2Vo>> result = JSON.parseObject(
catalogJson,
new TypeReference<Map<String, List<Catelog2Vo>>>() {});
return result;
}
一开始压力测试没问题,时间久了会报错:
这是因为springboot2.0以后默认使用lettuce作为操作redis的客户端。它使用netty进行网络通信。 lettucel的bug导致nettyi堆外内存溢出-Xmx300m;netty如果没有指定堆外内存,默认使用-Xmx300m 可以通过-Dio.netty.maxDirectMemoryi进行设置 解决方案:不能使用-Dio.netty.maxDirectMemory只去调大堆外内存。
- 升级lettuce客户端。
- 或者切换使用jedis
<!-- 引入redis -->
<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>
缓存穿透
缓存穿透是指在缓存中找不到需要的数据,导致每次请求都要查询数据库或其他存储系统,从而影响性能。这通常发生在请求的键值对在存储系统中不存在,但被频繁地查询。
为了解决缓存穿透问题,可以考虑以下方法:
- 空值缓存: 当查询数据库或存储系统后,如果发现数据不存在,可以将这个空值也存入缓存,但设置一个较短的过期时间,防止频繁查询。
- 布隆过滤器: 使用布隆过滤器来快速判断一个键值是否存在于缓存中。这样可以在缓存层面快速拦截掉那些 明显不存在于存储系统中的请求。
- 预热缓存: 在系统启动时或数据更新时,可以通过预热缓存来提前将热门数据加载到缓存中,减少冷启动时的缓存穿透问题。
- 限制频繁查询: 对于频繁查询但不会经常变化的数据,可以考虑在缓存层面添加限制,例如采用缓存击穿的防护机制,防止大量请求同时穿透到存储系统。
缓存雪崩
缓存雪崩是指缓存中大量的缓存数据在同一时间失效或过期,导致大量的请求直接访问底层存储系统,从而导致存储系统负载激增,影响系统性能。
为了避免缓存雪崩,可以采取以下一些措施:
- 过期时间随机化: 设置缓存数据的过期时间时,可以考虑添加一些随机因素,防止大量缓存在同一时刻过期,减缓对底层系统的冲击。
- 持久化缓存: 对于一些重要的缓存数据,可以考虑使用永不过期或较长时间的过期时间,确保即使发生缓存失效,系统也能够继续提供服务。
- 分布式锁: 在缓存失效时,可以使用分布式锁来保证只有一个线程或节点可以重新加载缓存,防止大量请求同时击穿。
- 多级缓存: 使用多级缓存架构,将缓存数据分布在不同的缓存层级中,即使某一层缓存失效,其他层仍然可以提供部分数据,减轻雪崩效应。
- 异步加载: 缓存的异步加载机制可以在缓存失效时,通过异步任务去加载缓存,而不是同步地直接访问底层存储系统,从而减少对底层系统的冲击。
缓存击穿
缓存击穿是指某个缓存键对应的数据在缓存中不存在,但多个并发请求同时请求这个不存在的数据,导致请求穿透到底层存储系统,增加了系统负载。
为了避免缓存击穿,可以采取以下措施:
- 缓存预加载: 在系统启动时或数据更新时,可以通过预加载缓存来将热门数据加载到缓存中,避免在请求到来时才去加载。这样可以减少对底层系统的冲击。
- 使用互斥锁: 在查询缓存时,可以使用互斥锁来保证只有一个线程或请求可以进行缓存的查询操作。这样可以防止多个请求同时穿透到底层存储系统。
- 缓存穿透检测: 在缓存层面可以添加一些检测机制,判断某个键是否存在于缓存中,如果不存在,可以通过一定的策略(如设置一个临时的占位值)防止多个请求同时穿透。
- 设置短暂的缓存过期时间: 对于一些不常变化的数据,可以设置一个较短的缓存过期时间,以保证缓存数据能够及时更新,降低缓存失效的概率。
- 使用分布式锁: 在缓存失效时,可以使用分布式锁来保证只有一个线程或节点可以重新加载缓存,防止多个请求同时穿透。
使用本地锁
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDb() {
//只要是同一把锁,就能锁住所有的线程
//synchronized (this):SpringBoot所有的组件在容器中都是单例的
//todo 本地锁 synchronized ,JUC锁 Lock ,在分布式情况下,使用分布式锁 zookeeper redis
synchronized (this){
//得到锁以后,再去缓存中确定是否有数据
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;
}
//查出剩余结果。。。
String jsonString = JSON.toJSONString(parentCid);
stringRedisTemplate.opsForValue().set("catalogJson", jsonString,1, TimeUnit.HOURS);
return parentCid;
}
}
问题:
本地锁只会锁住这台机器,但是分布式系统下面有多台机器,在高并发情况下吗,每台机器还是会都查询一次数据库