Spring Cache
学习Spring Cache的使用,结合Redis,完成对数据的缓存
1.添加依赖
1 | <dependency> |
此外,Spring Cache支持缓存品种多,常见缓存Redis、EhCache、Caffeine均支持。它们之间既能独立使用,也能组合使用。
这里使用最常用的Redis作为缓存数据库
1 | <dependency> |
确保Redis配置正确,能正常连接后即可使用SpringCache
2.常用注解
@EnableCaching
@EnableCaching开启缓存注解功能,通常加在启动类上
@Cacheable
@Cacheble注解表示这个方法有了缓存的功能,方法的返回值会被缓存下来,下一次调用该方法前,会去检查是否缓存中已经有值,如果有就直接返回,不调用方法。如果没有,就调用方法,然后把结果缓存起来。这个注解一般用在查询方法上。
@CachePut
加了@CachePut注解的方法,会把方法的返回值put到缓存里面缓存起来,供其它地方使用。它通常用在新增方法上。
@CacheEvict
使用了CacheEvict注解的方法,会清空指定缓存。一般用在更新或者删除的方法上。
3.开启缓存
在启动类加上@EnableCaching注解即可开启使用缓存。
4.加缓存注解
根据情况为接口加上@Cacheable、@CachePut或@CacheEvict 注解
注解内的参数cacheNames和key共同拼接为存入的缓存的名字,其中key以SpEL的方式调用传入的参数
如下面这个(cacheNames = "userCache",key = "#user.id"),传入id=1,存入后缓存的key为userCache::1
@CachePut
通常用在新增方法上
通过调试后,可以在mysql和redis中看到对应的数据,说明缓存成功
@Cacheable
通常用在查询方法上
调用接口后,通过断点调试可以发现,当发送/user的GET请求时,若缓存已经存在,则不会调用到userMapper.getById(id)
@CacheEvict
通常用在更新或者删除方法上
如第一个接口,当请求删除id为3的用户时,redis中相对应的userCache::3也会被删除
第二个接口中,使用参数allEntries=true,那么当调用该接口时,所有的userCache都会被删除
5.常用配置项
1 | spring: |
6.概念补充
6.1缓存穿透
缓存穿透简单来说是一种恶意攻击,当客户端通过访问接口,故意大量地去查询一个不存在的数据时,会经过以下步骤:
①访问缓存,发现没有该数据
②访问数据库进行查询,查询不到数据,自然也不会缓存数据
由于这种不存在的数据没有缓存,所以每一次访问接口都会去数据库查询,当客户端恶意地多次访问该接口就会对数据库造成压力
解决方案:
①缓存空值:springcache中默认会实现这种方式
②使用布隆过滤器
….
6.2缓存击穿
而且缓存击穿是一个热点的Key,有大并发集中对其进行访问,突然间这个Key失效了,导致大并发全部打在数据库上,导致数据库压力剧增。这种现象就叫做缓存击穿。
解决方案:
①如果业务允许的话,对于热点的key可以设置永不过期的key。
②使用互斥锁。如果缓存失效的情况,只有拿到锁才可以查询数据库,降低了在同一时刻打在数据库上的请求。当然这样会导致系统的性能变差。在SpringCache中的实现方法为 加一个属性“sync=true”,如下
@Cacheable(cacheNames = “shop”,key = “#id”,sync = true)
区别:缓存穿透是请求缓存和数据库都没有的数据,而缓存击穿是请求缓存中没有,而数据库中有的数据。
6.3缓存雪崩
缓存雪崩是,当某一个时刻出现大规模的缓存失效的情况,那么就会导致大量的请求直接打在数据库上面,导致数据库压力巨大,如果在高并发的情况下,可能瞬间就会导致数据库宕机。这时候如果运维马上又重启数据库,马上又会有新的流量把数据库打死。
解决方案:
①在设置key的时候,使用随机的TTL,避免大量key同时失效。
②可以搭建Redis集群,提高Redis的容灾性
③给业务添加多级缓存
…
7.后续问题解决
报错:对象无法序列化
在新项目中发现,当使用Redis作为缓存时,执行以下方法会报错:
2023-11-30 10:25:08.043 ERROR 14556 — [nio-8081-exec-1] com.hmdp.config.WebExceptionAdvice : org.springframework.data.redis.serializer.SerializationException: Cannot serialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize object using DefaultSerializer; nested exception is java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [com.hmdp.dto.Result]
这个错误是说,使用Redis作为缓存时,尝试序列化一个不支持的Java对象类型(Result)。
查看Result类,发现是由于没有实现序列化的接口
只要添加上 implements Serializable 即可解决这个问题
查看存储对象为乱码
当存储一个自定义的对象时,进入redis查看,发现他的数据保存格式是Hex,导致内容不可见。
这个问题可以通过在配置类中添加以下配置解决,将value存入时的JDK序列化改为json格式:
1 |
|
time-to-live不生效
可能是由于在部分Springboot的版本中不支持通过配置文件设置Redis缓存的TTL。
可以通过代码的形式配置,类似上面的 “查看存储对象为乱码” 中的配置,直接在上面代码中添加即可
1 |
|