「性能提升」擴(kuò)展 Spring Cache 支持多級(jí)緩存
為什么多級(jí)緩存
緩存的引入是現(xiàn)在大部分系統(tǒng)所必須考慮的
redis 作為常用中間件,雖然我們一般業(yè)務(wù)系統(tǒng)(畢竟業(yè)務(wù)量有限)不會(huì)遇到如下圖 在隨著 data-size 的增大和數(shù)據(jù)結(jié)構(gòu)的復(fù)雜的造成性能下降,但網(wǎng)絡(luò) IO 消耗會(huì)成為整個(gè)調(diào)用鏈路中不可忽視的部分。尤其在 微服務(wù)架構(gòu)中,一次調(diào)用往往會(huì)涉及多次調(diào)用 例如pig oauth2.0 的 client 認(rèn)證[1]

Caffeine 來(lái)自未來(lái)的本地內(nèi)存緩存,性能比如常見(jiàn)的內(nèi)存緩存實(shí)現(xiàn)性能高出不少詳細(xì)對(duì)比[2]。

綜合所述:我們需要構(gòu)建 L1 Caffeine JVM 級(jí)別緩存 , L2 Redis 緩存。
設(shè)計(jì)難點(diǎn)
目前大部分應(yīng)用緩存都是基于 Spring Cache 實(shí)現(xiàn),基于注解(annotation)的緩存(cache)技術(shù),存在的問(wèn)題如下:
Spring Cache 僅支持 單一的緩存來(lái)源,即:只能選擇 Redis 實(shí)現(xiàn)或者 Caffeine 實(shí)現(xiàn),并不能同時(shí)使用。 數(shù)據(jù)一致性:各層緩存之間的數(shù)據(jù)一致性問(wèn)題,如應(yīng)用層緩存和分布式緩存之前的數(shù)據(jù)一致性問(wèn)題。 緩存過(guò)期:Spring Cache 不支持主動(dòng)的過(guò)期策略
業(yè)務(wù)流程

如何使用
引入依賴
<dependency>
????<groupId>com.pig4cloud.plugingroupId>
????<artifactId>multilevel-cache-spring-boot-starterartifactId>
????<version>0.0.1version>
dependency>
開(kāi)啟緩存支持
@EnableCaching
public?class?App?{
?public?static?void?main(String[]?args)?{
??SpringApplication.run(App.class,?args);
?}
}
目標(biāo)接口聲明 Spring Cache 注解
@Cacheable(value?=?"get",key?=?"#key")
@GetMapping("/get")
public?String?get(String?key){
????return?"success";
}
性能比較
為保證性能 redis 在 127.0.0.1 環(huán)路安裝
OS: macOS Mojave CPU: 2.3 GHz Intel Core i5 RAM: 8 GB 2133 MHz LPDDR3 JVM: corretto_11.jdk
| Benchmark | Mode | Cnt | Score | Units |
|---|---|---|---|---|
| 多級(jí)實(shí)現(xiàn) | thrpt | 2 | 2716.074 | ops/s |
| 默認(rèn) redis | thrpt | 2 | 1373.476 | ops/s |
代碼原理
自定義 CacheManager 多級(jí)緩存實(shí)現(xiàn)
public?class?RedisCaffeineCacheManager?implements?CacheManager?{
?@Override
?public?Cache?getCache(String?name)?{
??Cache?cache?=?cacheMap.get(name);
??if?(cache?!=?null)?{
???return?cache;
??}
??cache?=?new?RedisCaffeineCache(name,?stringKeyRedisTemplate,?caffeineCache(),?cacheConfigProperties);
??Cache?oldCache?=?cacheMap.putIfAbsent(name,?cache);
??log.debug("create?cache?instance,?the?cache?name?is?:?{}",?name);
??return?oldCache?==?null???cache?:?oldCache;
?}
}
多級(jí)讀取、過(guò)期策略實(shí)現(xiàn)
public?class?RedisCaffeineCache?extends?AbstractValueAdaptingCache?{
?protected?Object?lookup(Object?key)?{
??Object?cacheKey?=?getKey(key);
????//?1.?先調(diào)用?caffeine?查詢是否存在指定的值
??Object?value?=?caffeineCache.getIfPresent(key);
??if?(value?!=?null)?{
???log.debug("get?cache?from?caffeine,?the?key?is?:?{}",?cacheKey);
???return?value;
??}
????//?2.?調(diào)用?redis?查詢?cè)谥付ǖ闹?/span>
??value?=?stringKeyRedisTemplate.opsForValue().get(cacheKey);
??if?(value?!=?null)?{
???log.debug("get?cache?from?redis?and?put?in?caffeine,?the?key?is?:?{}",?cacheKey);
???caffeineCache.put(key,?value);
??}
??return?value;
?}
}
過(guò)期策略,所有更新操作都基于 redis pub/sub 消息機(jī)制更新
public?class?RedisCaffeineCache?extends?AbstractValueAdaptingCache?{
?@Override
?public?void?put(Object?key,?Object?value)?{
??push(new?CacheMessage(this.name,?key));
?}
?@Override
?public?ValueWrapper?putIfAbsent(Object?key,?Object?value)?{
????push(new?CacheMessage(this.name,?key));
?}
?@Override
?public?void?evict(Object?key)?{
??push(new?CacheMessage(this.name,?key));
?}
?@Override
?public?void?clear()?{
??push(new?CacheMessage(this.name,?null));
?}
?private?void?push(CacheMessage?message)?{
??stringKeyRedisTemplate.convertAndSend(topic,?message);
?}
}
MessageListener 刪除指定 Caffeine 的指定值
public?class?CacheMessageListener?implements?MessageListener?{
?private?final?RedisTemplate源碼地址
https://github.com/pig-mesh/multilevel-cache-spring-boot-starter
參考資料
pig oauth2.0 的 client 認(rèn)證: https://gitee.com/log4j/pig
[2]詳細(xì)對(duì)比: https://github.com/ben-manes/caffeine/wiki/Benchmarks
評(píng)論
圖片
表情
