1. springboot 整合 spring cache
1.1 导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<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>
1.2 cache 的自动装配分析
CacheAutoConfiguration
CacheProperties
RedisCacheConfiguration
1.3 配置
application.properties
spring.cache.type=redis
1.4 缓存注解
@Cacheable: Triggers cache population.
@CacheEvict: Triggers cache eviction.
@CachePut: Updates the cache without interfering with the method execution.
@Caching: Regroups multiple cache operations to be applied on a method.
@CacheConfig: Shares some common cache-related settings at class-level.
1.4 启动类配置
@EnableCaching
1.5 @Cacheable 行为
- 如果缓存命中,方法不调用
- key默认生产的:缓存名::
[]
(自主生产的key值) - value的值,默认使用的是jdk序列化之后的值
- 默认时间是 -1 (永不过期)
自定义项
- 自定义缓存key
- 自定义缓存value,如果使用jdk的序列化机制,在异构系统中,灵活性不足(需要自定义CacheManager)
- 自定义缓存过期时间
1.6 @CacheEvict 行为(失效模式)
清除单个key的缓存
@CacheEvict(cacheNames = {"category"}, key = "'level1Category'")
根据value清除分区下面的所有缓存
@CacheEvict(value = {"category"}, allEntries = true)
1.7 @CachePut(…) (双写模式)
@CachePut(...)
1.7 @Caching 行为
如果单次操作需要调用多个@Cache注解,可以用@Caching注解,例如
@Caching(evict = {
@CacheEvict(cacheNames = {"category"}, key = "'level1Category'"),
@CacheEvict(cacheNames = {"category"}, key = "'getCatalogJson'")
})
2 自定义CacheManager
CacheAutoConfiguration 会从 CacheProperties 里读取有关Cache的配置信息,在CacheAutoConfiguration 中会用CacheConfigurations.getConfigurationClass(types[i])分别读取对应的缓存配置,这个类里面有RedisCacheConfiguration,该类里面有Redis定义的RedisCacheManager, 构建RedisCacheManager时需要调用determineConfiguration()决定使用哪一个redisCacheConfiguration,如果redisCacheConfiguration为null,就获取已经配置的或默认的,否则会读取redisCacheConfiguration的内容。
@Configuration
@EnableConfigurationProperties(CacheProperties.class)
@EnableCaching
public class CustomRedisCacheConfiguration {
/**
* 由于CacheProperties没有@Configuration注解,要想在容器中使用它,可以用@EnableConfigurationProperties注解,将这个类引用进来
*/
@Bean
RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
// 这里需要注意的是,在调用方法后,会返回一个新的RedisCacheConfiguration对象,所以必须用一个对象来接
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericFastJsonRedisSerializer()));
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
// 需要将原来默认的配置带进来
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixKeysWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
}
3.实验配置项
spring.cache.type=redis
# 毫秒
spring.cache.redis.time-to-live=600000
# 缓存key是否用前缀,如果指定了前缀,就用指定了的前缀,如果没有指定,就用缓存的value做为前缀
spring.cache.redis.use-key-prefix=true
# 缓存key的前缀,可以用这个区分其它形式缓存的key。推荐不指定自定义的前缀,而是采用默认的行为,将value做为缓存的前缀
#spring.cache.redis.key-prefix=CACHE_
# 是否缓存空值,防止缓存穿透
spring.cache.redis.cache-null-values=true
4. spring cache 的不足
- 读模式
1)缓存穿透:查询一个为NULL的数据。解决:缓存空数据,cache-null-value=true
2)缓存击穿:大量并发正好同时查询一个即将过期的数据。解决:加锁。spring cache 查询数据和放入缓存没有进行加锁。
在@Cacheable注解的参数中sync=true,该注解可以激活spring cache查询数据库时使用线程sychronized关键的方法,即每个服务最多只能有一个线程查询数据库,则避免了缓存击穿的问题。
@Cacheable(value = {"category"}, key = "'level1Category'", sync = true)
源代码 RedisCache.java 122行
public synchronized <T> T get(Object key, Callable<T> valueLoader) {
ValueWrapper result = get(key);
if (result != null) {
return (T) result.get();
}
T value = valueFromLoader(key, valueLoader);
put(key, value);
return value;
}
3)缓存雪崩:大量的key同时过期。解决方案:加随机时间。加上过期时间。
- 写模式
1)读写加锁
2)引入Canal,感知到MySQL的更新去更新缓存
3)读多写多,直接去查询数据库即可
4. spring cache 原理
CacheManager(RedisCacheManager) -> Cache(RedisCache) 负责缓存的读写
5. 总结
- 常用数据(读多写少,实时性、一致性要求不高的数据),完全可以使用spring cache;写模式(只要缓存的数据有过期时间,就足够);
- 特殊数据,特殊设计;