快取管理器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注解
該注解可以實作同一個方法上同時使用多種注解,
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/539394.html
標籤:其他
上一篇:糟糕,資料庫例外不可用怎么辦?
