緩存管理器CacheManager 一、背景 代碼併發量因建行活動頁上升,大量請求打到Mongo導致資料庫cpu100%從而服務不可用,目前解決方案,使用編程式緩存,即對緩存的操作與業務代碼耦合。目前基本上可以解決併發問題。此次提出CacheManager主要是優化代碼。使用聲明式,即註解的方式 ...
緩存管理器CacheManager
一、背景
代碼併發量因建行活動頁上升,大量請求打到Mongo導致資料庫cpu100%從而服務不可用,目前解決方案,使用編程式緩存,即對緩存的操作與業務代碼耦合。目前基本上可以解決併發問題。此次提出CacheManager主要是優化代碼。使用聲明式,即註解的方式,靈活操縱緩存,不需要與業務代碼耦合。
二、與Springboot2集成
1、引入依賴
<!--SpringCache的依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
2、SpringCache配置
/**
* @Title: CacheManagerConfiguration
* @author: simon
* @date: 2021/12/27 14:14
*/
@EnableCaching
@Configuration
public class CacheManagerConfiguration extends CachingConfigurerSupport {
private static String SEPARATOR = "&";
@Autowired
ReactiveMongoTemplate reactiveMongoTemplate;
/**
* 緩存管理器
*
* @param lettuceConnectionFactory
* @return
* @author simon
*/
@Bean("redisCacheManager")
@Primary
public CacheManager cacheManager(@Qualifier("RedisConnectionFactory")
LettuceConnectionFactory lettuceConnectionFactory)
{
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
// 解決查詢緩存轉換異常的問題
ObjectMapper mapper = new ObjectMapper();
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
//反序列化時候遇到不匹配的屬性並不拋出異常
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//序列化時候遇到空對象不拋出異常
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
//反序列化的時候如果是無效子類型,不拋出異常
mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);
//不使用預設的dateTime進行序列化,
mapper.configure(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false);
//使用JSR310提供的序列化類,裡面包含了大量的JDK8時間序列化類
mapper.registerModule(new JavaTimeModule());
//啟用反序列化所需的類型信息,在屬性中添加@class
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
RedisSerializer<Object> jsonSerializer = new GenericJackson2JsonRedisSerializer(mapper);
RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair.fromSerializer(jsonSerializer);
// 初始化一個RedisCacheWriter
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(lettuceConnectionFactory);
// 預設緩存配置
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
.computePrefixWith(comPrefix -> RedisConstants.KEY_BASE_PRIMITIVE.concat(RedisConstants.KEY_SEPARATOR)
.concat("cacheManager")
.concat(RedisConstants.KEY_SEPARATOR)
.concat(comPrefix)
.concat(RedisConstants.KEY_SEPARATOR))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(pair);
// 初始化緩存key
Map<String, RedisCacheConfiguration> configurationMap = new HashMap<>(16);
configurationMap.put(CacheDurationConstant.FOREVER_CACHE, defaultCacheConfig);
configurationMap.put(CacheDurationConstant.THREE_DAYS_CACHE, defaultCacheConfig.entryTtl(Duration.ofDays(3)));
configurationMap.put(CacheDurationConstant.ONE_DAYS_CACHE, defaultCacheConfig.entryTtl(Duration.ofDays(1)));
configurationMap.put(CacheDurationConstant.ONE_HOURS_CACHE, defaultCacheConfig.entryTtl(Duration.ofHours(1)));
configurationMap.put(CacheDurationConstant.TWO_MINUTES_CACHE, defaultCacheConfig.entryTtl(Duration.ofMinutes(2)));
configurationMap.put(CacheDurationConstant.FIVE_SECONDS_CACHE, defaultCacheConfig.entryTtl(Duration.ofSeconds(5)));
return RedisCacheManager.builder(redisCacheWriter)
// 初始化緩存空間
.initialCacheNames(configurationMap.keySet())
// 初始化緩存配置
.withInitialCacheConfigurations(configurationMap)
// 預設緩存配置
.cacheDefaults(defaultCacheConfig)
.build();
}
@Bean
@Primary
@Override //繼承上面這個類,並且加上這個之後才能把它設置為預設的。
public KeyGenerator keyGenerator() {
return (target, method, params) -> {
String className = target.getClass().getSimpleName();
String methodName = method.getName();
StringBuilder paramBuilder = new StringBuilder();
if (params.length > 0) {
Object param = params[0];
// 參數為map自定義key=類名+方法名+map的key-value值
if (param instanceof Map) {
Map<String, Object> map = (Map<String, Object>) param;
if (!map.isEmpty()) {
for (String key : map.keySet()) {
paramBuilder.append(key).append("-").append(map.get(key)).append(CacheManagerConfiguration.SEPARATOR);
}
}
} else {
for (Object key : params) {
if (key != null) {
paramBuilder.append(JSONObject.toJSONString(key)).append(CacheManagerConfiguration.SEPARATOR);
}
}
}
}
String paramString = paramBuilder.toString().replaceAll(":", ":");
if (paramString.endsWith(CacheManagerConfiguration.SEPARATOR)) {
paramString = paramString.substring(0, paramString.length() - 1);
}
SimpleKey key = new SimpleKey(className, methodName, paramString);
return key;
};
}
}
詳情請看附件
註:註意不要將ObjectMapper加入到Spring容器中,因為Spring容器中存在一個ObjectMapper,以用於@RequestBody
、ResponseBody
、RestTemplate
等地的序列化和反序列化。
為什麼不採用Spring容器的ObjectMapper對象,而要自己設置是因為Redis配置了objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
屬性,在序列化時記錄類/屬性的類型,以便在反序列化時得到POJO對象。
三、使用
1、基於聲明式註釋的緩存
SpringCache是Service層的聲明式緩存。即無需與業務代碼耦合,通過註解完成緩存。
註解通用屬性:
屬性/方法名 | 解釋 |
---|---|
value | 緩存名,必填,它指定了你的緩存存放在哪塊命名空間 |
cacheNames | 與 value 差不多,二選一即可 |
key | 可選屬性,可以使用 SpEL 標簽自定義緩存的key |
keyGenerator | key的生成器。key/keyGenerator二選一使用 |
cacheManager | 指定緩存管理器 |
cacheResolver | 指定獲取解析器 |
condition | 條件符合則緩存 |
unless | 條件符合則不緩存 |
1.1、@Cacheable註解
根據方法對其返回結果進行緩存,下次請求時,如果緩存存在,則直接讀取緩存數據返回;如果緩存不存在,則執行方法,並把返回的結果存入緩存中。一般用在查詢方法上。 查看源碼,特殊屬性值如下:
屬性/方法名 | 解釋 |
---|---|
sync | 是否使用非同步模式,預設為false |
附:SpringCache的元數據,可用在Key上
名稱 | 位置 | 描述 | 例子 |
---|---|---|---|
methodMame | root | 被調用方法的名稱 | #root.methodName |
method | root | 被調用的方法 | #root.method.name |
target | root | 被調用的目標對象 | #root.target |
targetClass | root | 被調用目標的類 | #root.targetClass |
args | root | 用於被調用目標的參數值(數組) | #root.args[0] |
caches | root | 執行當前方法緩存的集合 | #root.caches[0].name |
參數名稱 | 調用的方法 | 方法的任何參數名稱 | #iban或#a0 |
result | 調用的方法 | 僅用在unless,方法調用的結果(緩存值) | #result |
1.2、@CachePut註解
使用該註解標誌的方法,每次都會執行,並將結果存入指定的緩存中。其他方法可以直接從響應的緩存中讀取緩存數據,而不需要再去查詢資料庫。一般用在新增方法上。
1.3、@CacheEvict註解
使用該註解標誌的方法,會清空指定的緩存。一般用在更新或者刪除方法上 查看源碼,特殊屬性值如下:
屬性/方法名 | 解釋 |
---|---|
allEntries | 該參數指示是否需要在整個緩存範圍內逐出而不僅僅是基於Key的逐條逐出,預設為 false。如果指定為 true,則方法調用後將立即清空所有的緩存 |
beforeInvocation | 是否在方法執行前就清空,預設為 false。如果指定為 true,則在方法執行前就會清空緩存 |
1.4、@Caching註解
該註解可以實現同一個方法上同時使用多種註解。