简单整理一下
SpringCache + Redis
的使用,更具体的说明,可以看 Spring 官方关于SpringCache的说明 。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-cacheartifactId>
dependency>
spring:
cache:
type: redis #指定使用redis作为缓存
1、标注 @EnableCaching
到启动类或者配置类上,开启缓存功能
2、使用 SpringCache缓存注解标注到业务方法上,常用的注解如下:
对于缓存声明,Spring的缓存抽象提供了一组Java注解:
@Cacheable
:触发缓存填充的操作。@CacheEvict
: 触发缓存删除的操作。@CachePut
:在不干扰方法执行的情况下更新缓存。@Caching
:重新组合多个缓存操作以应用于一个方法,即使用该注解内部可以组合使用其他注解(@CacheEvict
、@CachePut
、@Cacheable
)@CacheConfig
:在类级别共享一些常见的缓存相关设置。@Cacheable
的使用为例: //1、Cacheable代表当前方法的结果需要缓存,如果缓存中有,方法不用调用。如果缓存中没有会调用方法,最后将方法的结果放入缓存
//2、每一个需要缓存的数据,都要指定要放到哪个名字的缓存分区。【缓存的分区(建议按照业务类型分,方便统一删除)】
@Cacheable(value = {"category"})
@Override
public List<CategoryEntity> getLevel1Categorys() {
// 查db操作 ,略;
}
1)、如果缓存中有数据,该方法不用被调用;
2)、key默认自动生成: 缓存的名字::SimpLeKey[]
(自主生成的key值);
3)、缓存的 value
的值。默认使用 “jdk序列化机制 org.springframework.data.redis.serializer.JdkSerializationRedisSerializer
”,将序列化后的数据存到 redis ;
4)、默认 ttl
时间 -1,永久;
1)、指定生成的缓存使用的 key; key属性指定时,接受一个SpEL的表达式,如:@Cacheable(value = {"category"},key = "#root.methodName")
,其中 key = "#root.methodName"
,意思是使用方法名作为key。其他用法详见 Available Caching SpEL Evaluation Context
2)、指定缓存的数据的存活时间 ttl:配置文件中修改 ttl 即可。
spring:
cache:
type: redis #指定使用redis作为缓存
redis:
# key-prefix: # 配置所有缓存key的前缀,不配置,默认格式为 "缓存自定义的value::SimpleKey []"
time-to-live: 3600000 # 配置 ttl 缓存过期时间,单位毫秒,这里设置了3600秒,即1个钟失效
cache-null-values: true # 是否缓存 Null 值,默认为 true,默认解决“缓存穿透”问题。
use-key-prefix: true # 是否使用key前缀,默认为 true
3)、将数据保存为json格式 :使用自定义缓存配置类,覆盖默认的配置。
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
/**
* @description 自定义缓存配置
*/
@Configuration
public class MyCacheConfig {
/**
* 自定义 redis 的缓存配置(重写默认配置)
* @param cacheProperties cache的配置对象
* @return {@link RedisCacheConfiguration}
*/
@Bean
public RedisCacheConfiguration createConfiguration(CacheProperties cacheProperties) {
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
//获取 默认的缓存配置,后面覆写配置。
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
//设置 key序列器: 使用默认的 {@link StringRedisSerializer}
config.serializeKeysWith(
RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
//设置 value序列器: 使用 {@link GenericFastJsonRedisSerializer}
config = config.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(new GenericFastJsonRedisSerializer()));
//如下代码 照抄默认配置源码,不变
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;
}
}
RedisSerializer 有很多实现类,选择一个 JSON转换的实现类作为 redis的 value 序列化实现类 即可。
@CacheEvict
、@Caching
的使用:@CacheEvict
代表当前方法如果执行,则删除指定的缓存;一般用于更新接口。
查询接口搭配使用 @Cacheable
注解,等触发查询接口时,重新将数据库中新数据缓存起来,@CacheEvict
注解的作用相当于缓存中的“失效模式”。
//1、指定删除“category"缓存分区里,某个缓存key的数据。
@CacheEvict(value = {"category"},key = "'getLevel1Categorys'")
//2、指定删除“category"缓存分区里,某个缓存 key1、key2...keyN 的数据 (批量删)。
@Caching(evict = {
@CacheEvict(value = {"category"}, key = "'getLevel1Categorys'"),
@CacheEvict(value = {"category"}, key = "'getCatelogJson'"),
// @CacheEvict(....) 可以删除多个
}
)
//3、指定删除“category"缓存分区所有数据。比 (2)的删除方式更方便快捷,批量删推荐使用这种方式。
@CacheEvict(value = {"category"},allEntries = true)
@Transactional
@Override
public void updateCascade(CategoryVo categoryVo) {
//略
}
//代表当前方法的结果需要缓存,如果缓存中有,方法不用调用。如果缓存中没有会调用方法,最后将方法的结果放入缓存
@Cacheable(value = {"category"}, key = "#root.methodName")
@Override
public List<CategoryEntity> getLevel1Categorys() {
//略
}
@Cacheable(value = {"category"}, key = "#root.methodName")
@Override
public Map<String, List<Catelog2Vo>> getCatelogJson() {
//略
}
缓存穿透:查询一个null数据。
spring.cache.redis.cache-null-values=true
缓存击穿:大量并发进来同时查询一个正好过期的数据。
sync = true
属性加本地进程锁。sync = true
意思是查询缓存时会使用“本地进程锁”,用法如: @Cacheablesyc(sync = true)
,详情见下面的“源码分析-RedisCache”。缓存雪崩:大量的key同时过期。
-1
永久。可以修改配置加上过期时间。配置属性为:spring.cache.redis.time-to-live
读写加锁。
@Cacheable(sync = true)
,支持读的时候加本地锁。即时性、一致性:Spring-Cache 均不能保证即时性,数据库与缓存的一致性。
常规数据(读多写少、即时性、一致性要求不高的数据):完全可以使用 Spring-Cache 来缓存支持。Spring-Cache 能设置缓存的过期时间,利用 @CachePut
注解实现“双写模式”;利用 @CacheEvict
注解实现“失效模式”,一般的常规场景也足够用了。
特殊数据:
读多写多:直接去数据库查询就行。
即时性、一致性高:引入Canal,通过日志记录文件感知到MySOL的更新后,再去更新Redis缓存库。
SpringBoot 框架在引入
spring-boot-starter-cache
、spring-boot-starter-data-redis
依赖后,帮我们自动配置了哪些东西?
CacheAutoConfiguration
会自动导入 RedisCacheConfiguration
,RedisCacheConfiguration
会导入各种 xxxCacheConfiguration
缓存配置。@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({CacheManager.class})
@ConditionalOnBean({CacheAspectSupport.class})
@ConditionalOnMissingBean(
value = {CacheManager.class},
name = {"cacheResolver"}
)
// 1、CacheProperties 类是 SpringCache的配置,这里导入对应的配置。
@EnableConfigurationProperties({CacheProperties.class})
@AutoConfigureAfter({CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class, HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class})
@Import({CacheAutoConfiguration.CacheConfigurationImportSelector.class, CacheAutoConfiguration.CacheManagerEntityManagerFactoryDependsOnPostProcessor.class})
public class CacheAutoConfiguration {
//导入的组件,略,只标注重点的入口,具体可以自己看源码
/**
* 缓存导入选择器作用:将一堆的缓存配置。
*/
static class CacheConfigurationImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
CacheType[] types = CacheType.values();
String[] imports = new String[types.length];
for (int i = 0; i < types.length; i++) {
//2、导入对应的缓存配置
imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
}
return imports;
}
}
}
RedisCacheConfiguration
配置。final class CacheConfigurations {
private static final Map<CacheType, Class<?>> MAPPINGS;
static {
Map<CacheType, Class<?>> mappings = new EnumMap<>(CacheType.class);
mappings.put(CacheType.GENERIC, GenericCacheConfiguration.class);
mappings.put(CacheType.EHCACHE, EhCacheCacheConfiguration.class);
mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class);
mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class);
mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class);
mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class);
//2、导入 RedisCacheConfiguration
mappings.put(CacheType.REDIS, RedisCacheConfiguration.class);
mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class);
mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class);
mappings.put(CacheType.NONE, NoOpCacheConfiguration.class);
MAPPINGS = Collections.unmodifiableMap(mappings);
}
//2、导入对应的缓存,我这里用的是 Redis 缓存,对应的 RedisCacheConfiguration 配置被导入。
static String getConfigurationClass(CacheType cacheType) {
Class<?> configurationClass = MAPPINGS.get(cacheType);
Assert.state(configurationClass != null, () -> "Unknown cache type " + cacheType);
return configurationClass.getName();
}
/**
* 创建默认的缓存配置如下:
* key的失效时间为:永久
* 缓存默认支持 Null 值插入
* 默认支持缓存key前缀
* 默认key前缀的格式为:>[the actual cache name]
* key serializer 使用:{@link org.springframework.data.redis.serializer.StringRedisSerializer}
* value serializer 使用:{@link org.springframework.data.redis.serializer.JdkSerializationRedisSerializer}
* conversion service 使用这个缓存可以转换器:{@link DefaultFormattingConversionService} with {@link #registerDefaultConverters(ConverterRegistry) default}
*/
public static RedisCacheConfiguration defaultCacheConfig(@Nullable ClassLoader classLoader) {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
registerDefaultConverters(conversionService);
return new RedisCacheConfiguration(Duration.ZERO, true, true, CacheKeyPrefix.simple(),
SerializationPair.fromSerializer(RedisSerializer.string()),
SerializationPair.fromSerializer(RedisSerializer.java(classLoader)), conversionService);
}
}
RedisCacheManager
(如果用户自定义了缓存管理器,则使用自定义的,不使用默认的)@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisConnectionFactory.class)
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {
//3、导入 RedisCacheManager 缓存管理器。
@Bean
RedisCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers,
ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration,
ObjectProvider<RedisCacheManagerBuilderCustomizer> redisCacheManagerBuilderCustomizers,
RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) {
// 3.1 构建缓存配置
RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(
determineConfiguration(cacheProperties, redisCacheConfiguration, resourceLoader.getClassLoader()));
List<String> cacheNames = cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
}
redisCacheManagerBuilderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
return cacheManagerCustomizers.customize(builder.build());
}
//3.2 决定缓存配置的方法
private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfiguration(
CacheProperties cacheProperties,
ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration,
ClassLoader classLoader) {
//3.3 判断是否有用户自己创建的配置,如果有就用自定义的配置,如果没有则创建默认配置
return redisCacheConfiguration.getIfAvailable(() -> createConfiguration(cacheProperties, classLoader));
}
//3.4 创建默认配置方法:决定默认配置具体配置了什么
private org.springframework.data.redis.cache.RedisCacheConfiguration createConfiguration(
CacheProperties cacheProperties, ClassLoader classLoader) {
Redis redisProperties = cacheProperties.getRedis();
org.springframework.data.redis.cache.RedisCacheConfiguration config = org.springframework.data.redis.cache.RedisCacheConfiguration
.defaultCacheConfig();
config = config.serializeValuesWith(
SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(classLoader)));
if (redisProperties.getTimeToLive() != null) {
// 设置 ttl 失效时间
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
// 设置 缓存key前缀
config = config.prefixKeysWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
// 设置 是否禁用缓存Null值
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
// 设置 是否禁用缓存key前缀
config = config.disableKeyPrefix();
}
return config;
}
}
Cache
类如下图:
RedisCache 实现类 如下图:
有一个get方法使用了 synchronized
关键字 (重写了 Cache 接口的 get方法)