简介
官方文档:
https://docs.spring.io/spring-framework/docs/5.2.19.RELEASE/spring-framework-reference/integration.html#cache
Spring 从 3.1 开始定义了 org.springframework.cache.Cache 和 org.springframework.cache.CacheManager 接口来统一不同的缓存技术; 并支持使用 JCache(JSR-107)注解简化我们开发;
Cache 接口为缓存的组件规范定义,包含缓存的各种操作集合;
Cache 接 口 下 Spring 提 供 了 各 种 xxxCache 的 实 现 ; 如 RedisCache , EhCacheCache,ConcurrentMapCache 等;
每次调用需要缓存功能的方法时,Spring 会检查检查指定参数的指定的目标方法是否已
经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓
存结果后返回给用户。下次调用直接从缓存中获取。
使用 Spring 缓存抽象时我们需要关注以下两点
- 1,确定方法需要被缓存以及他们的缓存策略
- 2,从缓存中读取之前缓存存储的数据
整合使用SpringCache
引入依赖
1 2 3 4 5 6 7 8 9 10
| <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> </dependency>
|
配置cache文件
缓存的自动配置
我们需要配置哪些?
1 2 3 4
| spring: cache: type: redis # 指定缓存类型 cache-names: # 指定缓存使用的名称;注意,这里指定缓存名称之后,不能再业务中实时创建,所以最好不指定名称
|
测试使用缓存
1 2 3 4 5 6 7 8 9
| @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.
|
开始缓存功能
@Cacheable自定义使用
@Cacheable的默认行为
- 如果缓存中存在直接返回,方法不用调用
- key默认生成: 缓存的名字: : SimpleKey[] (自主生成的key值)
- 缓存的value值.默认使用jdk序列化机制,将序列化后的数据存到redis
- 默认TTL时间 -1 : 永不过期
我们需要自定义
指定生成的缓存使用的key; 使用key属性指定,默认规则使用 SpEL表达式
指定缓存的数据存活时间; 使用配置文件属性, spring.cache.redis.time-to-live= 100 默认是毫秒
将数据保存为JSON格式; 需要自定义配置
SpringCache配置原理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
|
class RedisCacheConfiguration { @Bean RedisCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers, ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration, ObjectProvider<RedisCacheManagerBuilderCustomizer> redisCacheManagerBuilderCustomizers, RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) { 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()); } private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfiguration( CacheProperties cacheProperties, ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration, ClassLoader classLoader) { return redisCacheConfiguration.getIfAvailable(() -> createConfiguration(cacheProperties, classLoader)); }
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) { config = config.entryTtl(redisProperties.getTimeToLive()); } if (redisProperties.getKeyPrefix() != null) { config = config.prefixCacheNameWith(redisProperties.getKeyPrefix()); } if (!redisProperties.isCacheNullValues()) { config = config.disableCachingNullValues(); } if (!redisProperties.isUseKeyPrefix()) { config = config.disableKeyPrefix(); } return config; } }
|
自定义配置RedisCacheConfiguration (org.springframework.data.redis.cache.RedisCacheConfiguration)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| @EnableConfigurationProperties(CacheProperties.class) @EnableCaching @Configuration public class MyCacheRedisConfiguration {
@Bean RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
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.prefixCacheNameWith(redisProperties.getKeyPrefix()); } if (!redisProperties.isCacheNullValues()) { config = config.disableCachingNullValues(); } if (!redisProperties.isUseKeyPrefix()) { config = config.disableKeyPrefix(); }
return config; } }
|
配置文件配置
1 2 3 4 5 6 7 8
| spring: cache: type: redis # 指定使用缓存类型 redis: time-to-live: 3600000 # 指定过期时间,单位/毫秒 key-prefix: CACHE_ # 指定key前缀,如果没有指定就默认使用缓存的名称做为前缀 use-key-prefix: true # 是否使用key前缀,默认true cache-null-values: true # 是否缓存空值,防止缓存穿透
|
@CacheEvict使用
触发将数据从缓存中删除的操作
再修改了接口之后,删除缓存 –>保证数据一致性的.失效模式
1 2 3 4 5 6 7 8 9 10
| @Caching(evict = { @CacheEvict(value = {"category"}, key = "'getCategoryLevelOne'"), @CacheEvict(value = {"category"}, key = "'getCategoryTwoAndThree'") })
@CacheEvict(value = {"category"}, allEntries = true)
|
@CachePut使用
不影响方法执行更新缓存
1
| 可以与@CacheEvict一起使用,在更新之后的返回值还是需要的值,达到双写模式
|
SpringCache的不足
读模式
- 缓存穿透:查询一个null的数据。解决:缓存空数据:cache-null-values: true
- 缓存击穿:大量并发进来同时查询一个正好过期的数据。解决:加锁:默认是无加锁的;sync=true(手动加锁,解决击穿)
- 缓存雪崩:大量的key同时过期。解决:加随机的时间:加上过期时间,time-to-live: 3600000
写模式(保证缓存与数据的一致性)
- 读写加锁
- 引入Canal,感知到Mysql的更新去更新缓存
- 读多写多的情况,建议直接查询数据库
原理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
public class RedisCache extends AbstractValueAdaptingCache { @Override protected Object lookup(Object key) { byte[] value = cacheWriter.get(name, createAndConvertCacheKey(key)); if (value == null) { return null; }
return deserializeCacheValue(value); } @Override @SuppressWarnings("unchecked") 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; } }
|
总结
1、常规数据(读多写少,即时性,一致性要求不高的数据):完全可以使用SpringCache
写模式(只要缓存中的数据设置了过期时间就没太大问题)
2、特殊数据:需要特殊去设计