基于Spring?Cache實(shí)現(xiàn)Caffeine+Redis二級(jí)緩存
本文主要介紹了基于Spring Cache實(shí)現(xiàn)二級(jí)緩存(Caffeine+Redis),具體如下:

一、聊聊什么是硬編碼使用緩存?
在學(xué)習(xí)Spring Cache之前,筆者經(jīng)常會(huì)硬編碼的方式使用緩存。
我們來(lái)舉個(gè)實(shí)際中的例子,為了提升用戶(hù)信息的查詢(xún)效率,我們對(duì)用戶(hù)信息使用了緩存,示例代碼如下:
@Autowire
private UserMapper userMapper;
@Autowire
private RedisCache redisCache;
//查詢(xún)用戶(hù)
public User getUserById(Long userId) {
//定義緩存key
String cacheKey = "userId_" + userId;
//先查詢(xún)r(jià)edis緩存
User user = redisCache.get(cacheKey);
//如果緩存中有就直接返回,不再查詢(xún)數(shù)據(jù)庫(kù)
if (user != null) {
return user;
}
//沒(méi)有再查詢(xún)數(shù)據(jù)庫(kù)
user = userMapper.getUserById(userId);
//數(shù)據(jù)存入緩存,這樣下次查詢(xún)就能到緩存中獲取
if (user != null) {
stringCommand.set(cacheKey, user);
}
return user;
}
相信很多同學(xué)都寫(xiě)過(guò)類(lèi)似風(fēng)格的代碼,這種風(fēng)格符合面向過(guò)程的編程思維,非常容易理解。但它也有一些缺點(diǎn):
代碼不夠優(yōu)雅。業(yè)務(wù)邏輯有四個(gè)典型動(dòng)作:存儲(chǔ),讀取,修改,刪除。每次操作都需要定義緩存Key ,調(diào)用緩存命令的API,產(chǎn)生較多的重復(fù)代碼;
緩存操作和業(yè)務(wù)邏輯之間的代碼耦合度高,對(duì)業(yè)務(wù)邏輯有較強(qiáng)的侵入性。侵入性主要體現(xiàn)如下兩點(diǎn):
開(kāi)發(fā)聯(lián)調(diào)階段,需要去掉緩存,只能注釋或者臨時(shí)刪除緩存操作代碼,也容易出錯(cuò);
某些場(chǎng)景下,需要更換緩存組件,每個(gè)緩存組件有自己的API,更換成本頗高。
如果說(shuō)是下面這樣的,是不是就優(yōu)雅多了。
@Mapper
public interface UserMapper {
/**
* 根據(jù)用戶(hù)id獲取用戶(hù)信息
*
* 如果緩存中有直接返回緩存數(shù)據(jù),如果沒(méi)有那么就去數(shù)據(jù)庫(kù)查詢(xún),查詢(xún)完再插入緩存中,這里緩存的key前綴為cache_user_id_,+傳入的用戶(hù)ID
*/
@Cacheable(key = "'cache_user_id_' + #userId")
User getUserById(Long userId);
}
再看實(shí)現(xiàn)類(lèi)
@Autowire
private UserMapper userMapper;
//查詢(xún)用戶(hù)
public User getUserById(Long userId) {
return userMapper.getUserById(userId);
}
這么一看是不是完全和緩存分離開(kāi)來(lái),如果開(kāi)發(fā)聯(lián)調(diào)階段,需要去掉緩存那么直接注釋掉注解就好了,是不是非常完美。
而且這一整套實(shí)現(xiàn)都不要自己手動(dòng)寫(xiě),Spring Cache就已經(jīng)幫我定義好相關(guān)注解和接口,我們可以輕易實(shí)現(xiàn)上面的功能。
二、Spring Cache簡(jiǎn)介
Spring Cache是Spring-context包中提供的基于注解方式使用的緩存組件,定義了一些標(biāo)準(zhǔn)接口,通過(guò)實(shí)現(xiàn)這些接口,就可以通過(guò)在
方法上增加注解來(lái)實(shí)現(xiàn)緩存。這樣就能夠避免緩存代碼與業(yè)務(wù)處理耦合在一起的問(wèn)題。
Spring Cache核心的接口就兩個(gè):Cache和CacheManager
1、Cache接口
該接口定義提供緩存的具體操作,比如緩存的放入、讀取、清理:
package org.Springframework.cache;
import java.util.concurrent.Callable;
public interface Cache {
// cacheName,緩存的名字,默認(rèn)實(shí)現(xiàn)中一般是CacheManager創(chuàng)建Cache的bean時(shí)傳入cacheName
String getName();
//得到底層使用的緩存,如Ehcache
Object getNativeCache();
// 通過(guò)key獲取緩存值,注意返回的是ValueWrapper,為了兼容存儲(chǔ)空值的情況,將返回值包裝了一層,通過(guò)get方法獲取實(shí)際值
ValueWrapper get(Object key);
// 通過(guò)key獲取緩存值,返回的是實(shí)際值,即方法的返回值類(lèi)型
<T> T get(Object key, Class<T> type);
// 通過(guò)key獲取緩存值,可以使用valueLoader.call()來(lái)調(diào)使用@Cacheable注解的方法。當(dāng)@Cacheable注解的sync屬性配置為true時(shí)使用此方法。
// 因此方法內(nèi)需要保證回源到數(shù)據(jù)庫(kù)的同步性。避免在緩存失效時(shí)大量請(qǐng)求回源到數(shù)據(jù)庫(kù)
<T> T get(Object key, Callable<T> valueLoader);
// 將@Cacheable注解方法返回的數(shù)據(jù)放入緩存中
void put(Object key, Object value);
// 當(dāng)緩存中不存在key時(shí)才放入緩存。返回值是當(dāng)key存在時(shí)原有的數(shù)據(jù)
ValueWrapper putIfAbsent(Object key, Object value);
// 刪除緩存
void evict(Object key);
// 清空緩存
void clear();
// 緩存返回值的包裝
interface ValueWrapper {
// 返回實(shí)際緩存的對(duì)象
Object get();
}
}
2、CacheManager接口
主要提供Cache實(shí)現(xiàn)bean的創(chuàng)建,每個(gè)應(yīng)用里可以通過(guò)cacheName來(lái)對(duì)Cache進(jìn)行隔離,每個(gè)cacheName對(duì)應(yīng)一個(gè)Cache實(shí)現(xiàn)。
package org.Springframework.cache;
import java.util.Collection;
public interface CacheManager {
// 通過(guò)cacheName創(chuàng)建Cache的實(shí)現(xiàn)bean,具體實(shí)現(xiàn)中需要存儲(chǔ)已創(chuàng)建的Cache實(shí)現(xiàn)bean,避免重復(fù)創(chuàng)建,也避免內(nèi)存緩存
//對(duì)象(如Caffeine)重新創(chuàng)建后原來(lái)緩存內(nèi)容丟失的情況
Cache getCache(String name);
// 返回所有的cacheName
Collection<String> getCacheNames();
}
3、常用注解說(shuō)明
@Cacheable:主要應(yīng)用到查詢(xún)數(shù)據(jù)的方法上。
public @interface Cacheable {
// cacheNames,CacheManager就是通過(guò)這個(gè)名稱(chēng)創(chuàng)建對(duì)應(yīng)的Cache實(shí)現(xiàn)bean
@AliasFor("cacheNames")
String[] value() default {};
@AliasFor("value")
String[] cacheNames() default {};
// 緩存的key,支持SpEL表達(dá)式。默認(rèn)是使用所有參數(shù)及其計(jì)算的hashCode包裝后的對(duì)象(SimpleKey)
String key() default "";
// 緩存key生成器,默認(rèn)實(shí)現(xiàn)是SimpleKeyGenerator
String keyGenerator() default "";
// 指定使用哪個(gè)CacheManager,如果只有一個(gè)可以不用指定
String cacheManager() default "";
// 緩存解析器
String cacheResolver() default "";
// 緩存的條件,支持SpEL表達(dá)式,當(dāng)達(dá)到滿(mǎn)足的條件時(shí)才緩存數(shù)據(jù)。在調(diào)用方法前后都會(huì)判斷
String condition() default "";
// 滿(mǎn)足條件時(shí)不更新緩存,支持SpEL表達(dá)式,只在調(diào)用方法后判斷
String unless() default "";
// 回源到實(shí)際方法獲取數(shù)據(jù)時(shí),是否要保持同步,如果為false,調(diào)用的是Cache.get(key)方法;如果為true,調(diào)用的是Cache.get(key, Callable)方法
boolean sync() default false;
}
@CacheEvict:清除緩存,主要應(yīng)用到刪除數(shù)據(jù)的方法上。相比Cacheable多了兩個(gè)屬性
public @interface CacheEvict {
// ...相同屬性說(shuō)明請(qǐng)參考@Cacheable中的說(shuō)明
// 是否要清除所有緩存的數(shù)據(jù),為false時(shí)調(diào)用的是Cache.evict(key)方法;為true時(shí)調(diào)用的是Cache.clear()方法
boolean allEntries() default false;
// 調(diào)用方法之前或之后清除緩存
boolean beforeInvocation() default false;
}
@CachePut:放入緩存,主要用到對(duì)數(shù)據(jù)有更新的方法上。屬性說(shuō)明參考@Cacheable
@Caching:用于在一個(gè)方法上配置多種注解
@EnableCaching:?jiǎn)⒂肧pring cache緩存,作為總的開(kāi)關(guān),在SpringBoot的啟動(dòng)類(lèi)或配置類(lèi)上需要加上此注解才會(huì)生效
三、使用二級(jí)緩存需要思考的一些問(wèn)題?
我們知道關(guān)系數(shù)據(jù)庫(kù)(Mysql)數(shù)據(jù)最終存儲(chǔ)在磁盤(pán)上,如果每次都從數(shù)據(jù)庫(kù)里去讀取,會(huì)因?yàn)榇疟P(pán)本身的IO影響讀取速度,所以就有了
像redis這種的內(nèi)存緩存。
通過(guò)內(nèi)存緩存確實(shí)能夠很大程度的提高查詢(xún)速度,但如果同一查詢(xún)并發(fā)量非常的大,頻繁的查詢(xún)r(jià)edis,也會(huì)有明顯的網(wǎng)絡(luò)IO上的消耗,
那我們針對(duì)這種查詢(xún)非常頻繁的數(shù)據(jù)(熱點(diǎn)key),我們是不是可以考慮存到應(yīng)用內(nèi)緩存,如:caffeine。
當(dāng)應(yīng)用內(nèi)緩存有符合條件的數(shù)據(jù)時(shí),就可以直接使用,而不用通過(guò)網(wǎng)絡(luò)到redis中去獲取,這樣就形成了兩級(jí)緩存。
應(yīng)用內(nèi)緩存叫做一級(jí)緩存,遠(yuǎn)程緩存(如redis)叫做二級(jí)緩存。
整個(gè)流程如下

流程看著是很清新,但其實(shí)二級(jí)緩存需要考慮的點(diǎn)還很多。
1.如何保證分布式節(jié)點(diǎn)一級(jí)緩存的一致性?
我們說(shuō)一級(jí)緩存是應(yīng)用內(nèi)緩存,那么當(dāng)你的項(xiàng)目部署在多個(gè)節(jié)點(diǎn)的時(shí)候,如何保證當(dāng)你對(duì)某個(gè)key進(jìn)行修改刪除操作時(shí),使其它節(jié)點(diǎn)
的一級(jí)緩存一致呢?
2.是否允許存儲(chǔ)空值?
這個(gè)確實(shí)是需要考慮的點(diǎn)。因?yàn)槿绻硞€(gè)查詢(xún)緩存和數(shù)據(jù)庫(kù)中都沒(méi)有,那么就會(huì)導(dǎo)致頻繁查詢(xún)數(shù)據(jù)庫(kù),導(dǎo)致數(shù)據(jù)庫(kù)Down,這也是我們
常說(shuō)的緩存穿透。
但如果存儲(chǔ)空值呢,因?yàn)榭赡軙?huì)存儲(chǔ)大量的空值,導(dǎo)致緩存變大,所以這個(gè)最好是可配置,按照業(yè)務(wù)來(lái)決定是否開(kāi)啟。
3.是否需要緩存預(yù)熱?
也就是說(shuō),我們會(huì)覺(jué)得某些key一開(kāi)始就會(huì)非常的熱,也就是熱點(diǎn)數(shù)據(jù),那么我們是否可以一開(kāi)始就先存儲(chǔ)到緩存中,避免緩存擊穿。
4.一級(jí)緩存存儲(chǔ)數(shù)量上限的考慮?
既然一級(jí)緩存是應(yīng)用內(nèi)緩存,那你是否考慮一級(jí)緩存存儲(chǔ)的數(shù)據(jù)給個(gè)限定最大值,避免存儲(chǔ)太多的一級(jí)緩存導(dǎo)致OOM。
5.一級(jí)緩存過(guò)期策略的考慮?
我們說(shuō)redis作為二級(jí)緩存,redis是淘汰策略來(lái)管理的。具體可參考redis的8種淘汰策略。那你的一級(jí)緩存策略呢?就好比你設(shè)置一級(jí)緩存
數(shù)量最大為5000個(gè),那當(dāng)?shù)?001個(gè)進(jìn)來(lái)的時(shí)候,你是怎么處理呢?是直接不保存,還是說(shuō)自定義LRU或者LFU算法去淘汰之前的數(shù)據(jù)?
6.一級(jí)緩存過(guò)期了如何清除?
我們說(shuō)redis作為二級(jí)緩存,我們有它的緩存過(guò)期策略(定時(shí)、定期、惰性),那你的一級(jí)緩存呢,過(guò)期如何清除呢?
這里4、5、6小點(diǎn)如果說(shuō)用我們傳統(tǒng)的Map顯然實(shí)現(xiàn)是很費(fèi)勁的,但現(xiàn)在有更好用的一級(jí)緩存庫(kù)那就是Caffeine。
四、Caffeine 簡(jiǎn)介
Caffeine,一個(gè)用于Java的高性能緩存庫(kù)。
緩存和Map之間的一個(gè)根本區(qū)別是緩存會(huì)清理存儲(chǔ)的項(xiàng)目。
1、寫(xiě)入緩存策略
Caffeine有三種緩存寫(xiě)入策略:手動(dòng)、同步加載和異步加載。
2、緩存值的清理策略
Caffeine有三種緩存值的清理策略:基于大小、基于時(shí)間和基于引用。
基于容量:當(dāng)緩存大小超過(guò)配置的大小限制時(shí)會(huì)發(fā)生回收。
基于時(shí)間:
- 寫(xiě)入后到期策略。
- 訪(fǎng)問(wèn)后過(guò)期策略。
- 到期時(shí)間由 Expiry 實(shí)現(xiàn)獨(dú)自計(jì)算。
基于引用:?jiǎn)⒂没诰彺骀I值的垃圾回收。
- Java種有四種引用:強(qiáng)引用,軟引用,弱引用和虛引用,caffeine可以將值封裝成弱引用或軟引用。
- 軟引用:如果一個(gè)對(duì)象只具有軟引用,則內(nèi)存空間足夠,垃圾回收器就不會(huì)回收它;如果內(nèi)存空間不足了,就會(huì)回收這些對(duì)象的內(nèi)存。
- 弱引用:在垃圾回收器線(xiàn)程掃描它所管轄的內(nèi)存區(qū)域的過(guò)程中,一旦發(fā)現(xiàn)了只具有弱引用的對(duì)象,不管當(dāng)前內(nèi)存空間足夠與否,都會(huì)
回收它的內(nèi)存。
3、統(tǒng)計(jì)
Caffeine提供了一種記錄緩存使用統(tǒng)計(jì)信息的方法,可以實(shí)時(shí)監(jiān)控緩存當(dāng)前的狀態(tài),以評(píng)估緩存的健康程度以及緩存命中率等,方便后
續(xù)調(diào)整參數(shù)。
4、高效的緩存淘汰算法
緩存淘汰算法的作用是在有限的資源內(nèi),盡可能識(shí)別出哪些數(shù)據(jù)在短時(shí)間會(huì)被重復(fù)利用,從而提高緩存的命中率。常用的緩存淘汰算法有
LRU、LFU、FIFO等。
FIFO:先進(jìn)先出。選擇最先進(jìn)入的數(shù)據(jù)優(yōu)先淘汰。 LRU:最近最少使用。選擇最近最少使用的數(shù)據(jù)優(yōu)先淘汰。 LFU:最不經(jīng)常使用。選擇在一段時(shí)間內(nèi)被使用次數(shù)最少的數(shù)據(jù)優(yōu)先淘汰。
LRU(Least Recently Used)算法認(rèn)為最近訪(fǎng)問(wèn)過(guò)的數(shù)據(jù)將來(lái)被訪(fǎng)問(wèn)的幾率也更高。
LRU通常使用鏈表來(lái)實(shí)現(xiàn),如果數(shù)據(jù)添加或者被訪(fǎng)問(wèn)到則把數(shù)據(jù)移動(dòng)到鏈表的頭部,鏈表的頭部為熱數(shù)據(jù),鏈表的尾部如冷數(shù)據(jù),當(dāng)
數(shù)據(jù)滿(mǎn)時(shí),淘汰尾部的數(shù)據(jù)。
LFU(Least Frequently Used)算法根據(jù)數(shù)據(jù)的歷史訪(fǎng)問(wèn)頻率來(lái)淘汰數(shù)據(jù),其核心思想是“如果數(shù)據(jù)過(guò)去被訪(fǎng)問(wèn)多次,那么將來(lái)被訪(fǎng)問(wèn)
的頻率也更高”。根據(jù)LFU的思想,如果想要實(shí)現(xiàn)這個(gè)算法,需要額外的一套存儲(chǔ)用來(lái)存每個(gè)元素的訪(fǎng)問(wèn)次數(shù),會(huì)造成內(nèi)存資源的浪費(fèi)。
Caffeine采用了一種結(jié)合LRU、LFU優(yōu)點(diǎn)的算法:W-TinyLFU,其特點(diǎn):高命中率、低內(nèi)存占用。
5、其他說(shuō)明
Caffeine的底層數(shù)據(jù)存儲(chǔ)采用ConcurrentHashMap。因?yàn)镃affeine面向JDK8,在jdk8中ConcurrentHashMap增加了紅黑樹(shù),在hash沖突
嚴(yán)重時(shí)也能有良好的讀性能。
五、基于Spring Cache實(shí)現(xiàn)二級(jí)緩存(Caffeine+Redis)
前面說(shuō)了,使用了redis緩存,也會(huì)存在一定程度的網(wǎng)絡(luò)傳輸上的消耗,所以會(huì)考慮應(yīng)用內(nèi)緩存,但有點(diǎn)很重要的要記住:
應(yīng)用內(nèi)緩存可以理解成比redis緩存更珍惜的資源,所以,caffeine 不適用于數(shù)據(jù)量大,并且緩存命中率極低的業(yè)務(wù)場(chǎng)景,如用戶(hù)維度的緩存。
當(dāng)前項(xiàng)目針對(duì)應(yīng)用都部署了多個(gè)節(jié)點(diǎn),一級(jí)緩存是在應(yīng)用內(nèi)的緩存,所以當(dāng)對(duì)數(shù)據(jù)更新和清除時(shí),需要通知所有節(jié)點(diǎn)進(jìn)行清理緩存的操作。
可以有多種方式來(lái)實(shí)現(xiàn)這種效果,比如:zookeeper、MQ等,但是既然用了redis緩存,redis本身是有支持訂閱/發(fā)布功能的,所以就
不依賴(lài)其他組件了,直接使用redis的通道來(lái)通知其他節(jié)點(diǎn)進(jìn)行清理緩存的操作。
當(dāng)某個(gè)key進(jìn)行更新刪除操作時(shí),通過(guò)發(fā)布訂閱的方式通知其它節(jié)點(diǎn)進(jìn)行刪除該key本地的一級(jí)緩存就可以了。
具體具體項(xiàng)目代碼這里就不再粘貼出來(lái)了,這樣只粘貼如何引用這個(gè)starter包。
1、maven引入使用
<dependency>
<groupId>com.jincou</groupId>
<artifactId>redis-caffeine-cache-starter</artifactId>
<version>1.0.0</version>
</dependency>
2、application.yml
添加二級(jí)緩存相關(guān)配置
# 二級(jí)緩存配置
# 注:caffeine 不適用于數(shù)據(jù)量大,并且緩存命中率極低的業(yè)務(wù)場(chǎng)景,如用戶(hù)維度的緩存。請(qǐng)慎重選擇。
l2cache:
config:
# 是否存儲(chǔ)空值,默認(rèn)true,防止緩存穿透
allowNullValues: true
# 組合緩存配置
composite:
# 是否全部啟用一級(jí)緩存,默認(rèn)false
l1AllOpen: false
# 是否手動(dòng)啟用一級(jí)緩存,默認(rèn)false
l1Manual: true
# 手動(dòng)配置走一級(jí)緩存的緩存key集合,針對(duì)單個(gè)key維度
l1ManualKeySet:
- userCache:user01
- userCache:user02
- userCache:user03
# 手動(dòng)配置走一級(jí)緩存的緩存名字集合,針對(duì)cacheName維度
l1ManualCacheNameSet:
- userCache
- goodsCache
# 一級(jí)緩存
caffeine:
# 是否自動(dòng)刷新過(guò)期緩存 true 是 false 否
autoRefreshExpireCache: false
# 緩存刷新調(diào)度線(xiàn)程池的大小
refreshPoolSize: 2
# 緩存刷新的頻率(秒)
refreshPeriod: 10
# 寫(xiě)入后過(guò)期時(shí)間(秒)
expireAfterWrite: 180
# 訪(fǎng)問(wèn)后過(guò)期時(shí)間(秒)
expireAfterAccess: 180
# 初始化大小
initialCapacity: 1000
# 最大緩存對(duì)象個(gè)數(shù),超過(guò)此數(shù)量時(shí)之前放入的緩存將失效
maximumSize: 3000
# 二級(jí)緩存
redis:
# 全局過(guò)期時(shí)間,單位毫秒,默認(rèn)不過(guò)期
defaultExpiration: 300000
# 每個(gè)cacheName的過(guò)期時(shí)間,單位毫秒,優(yōu)先級(jí)比defaultExpiration高
expires: {userCache: 300000,goodsCache: 50000}
# 緩存更新時(shí)通知其他節(jié)點(diǎn)的topic名稱(chēng) 默認(rèn) cache:redis:caffeine:topic
topic: cache:redis:caffeine:topic
3、啟動(dòng)類(lèi)上增加@EnableCaching
/**
* 啟動(dòng)類(lèi)
*/
@EnableCaching
@SpringBootApplication
public class CacheApplication {
public static void main(String[] args) {
SpringApplication.run(CacheApplication.class, args);
}
}
4、在需要緩存的方法上增加@Cacheable注解
/**
* 測(cè)試
*/
@Service
public class CaffeineCacheService {
private final Logger logger = LoggerFactory.getLogger(CaffeineCacheService.class);
/**
* 用于模擬db
*/
private static Map<String, UserDTO> userMap = new HashMap<>();
{
userMap.put("user01", new UserDTO("1", "張三"));
userMap.put("user02", new UserDTO("2", "李四"));
userMap.put("user03", new UserDTO("3", "王五"));
userMap.put("user04", new UserDTO("4", "趙六"));
}
/**
* 獲取或加載緩存項(xiàng)
*/
@Cacheable(key = "'cache_user_id_' + #userId", value = "userCache")
public UserDTO queryUser(String userId) {
UserDTO userDTO = userMap.get(userId);
try {
Thread.sleep(1000);// 模擬加載數(shù)據(jù)的耗時(shí)
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("加載數(shù)據(jù):{}", userDTO);
return userDTO;
}
/**
* 獲取或加載緩存項(xiàng)
* <p>
* 注:因底層是基于caffeine來(lái)實(shí)現(xiàn)一級(jí)緩存,所以利用的caffeine本身的同步機(jī)制來(lái)實(shí)現(xiàn)
* sync=true 則表示并發(fā)場(chǎng)景下同步加載緩存項(xiàng),
* sync=true,是通過(guò)get(Object key, Callable<T> valueLoader)來(lái)獲取或加載緩存項(xiàng),此時(shí)valueLoader(加載緩存項(xiàng)的具體邏輯)會(huì)被緩存起來(lái),所以CaffeineCache在定時(shí)刷新過(guò)期緩存時(shí),緩存項(xiàng)過(guò)期則會(huì)重新加載。
* sync=false,是通過(guò)get(Object key)來(lái)獲取緩存項(xiàng),由于沒(méi)有valueLoader(加載緩存項(xiàng)的具體邏輯),所以CaffeineCache在定時(shí)刷新過(guò)期緩存時(shí),緩存項(xiàng)過(guò)期則會(huì)被淘汰。
* <p>
*/
@Cacheable(value = "userCache", key = "#userId", sync = true)
public List<UserDTO> queryUserSyncList(String userId) {
UserDTO userDTO = userMap.get(userId);
List<UserDTO> list = new ArrayList();
list.add(userDTO);
logger.info("加載數(shù)據(jù):{}", list);
return list;
}
/**
* 更新緩存
*/
@CachePut(value = "userCache", key = "#userId")
public UserDTO putUser(String userId, UserDTO userDTO) {
return userDTO;
}
/**
* 淘汰緩存
*/
@CacheEvict(value = "userCache", key = "#userId")
public String evictUserSync(String userId) {
return userId;
}
}
項(xiàng)目源碼: https://github.com/yudiandemingzi/springboot-redis-caffeine-cache
推薦相關(guān)二級(jí)緩存相關(guān)項(xiàng)目
1.阿里巴巴jetcache: https://github.com/alibaba/jetcache
2.J2Cache: https://gitee.com/ld/J2Cache
3.l2cache: https://github.com/ck-jesse/l2cache(感謝)
這幾個(gè)現(xiàn)在業(yè)界比較常用的二級(jí)緩存項(xiàng)目,功能更加強(qiáng)大,而且性能更高效,使用也非常方便只要引入jar包,添加配置注解就可以。
到此這篇關(guān)于基于Spring Cache實(shí)現(xiàn)二級(jí)緩存(Caffeine+Redis)的文章就介紹到這了,更多相關(guān)Spring Cache二級(jí)緩存內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
idea項(xiàng)目debug模式無(wú)法啟動(dòng)的解決
這篇文章主要介紹了idea項(xiàng)目debug模式無(wú)法啟動(dòng)的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02
mybatis的dtd約束文件及配置文件xml自動(dòng)提示操作
這篇文章主要介紹了mybatis的dtd約束文件及配置文件xml自動(dòng)提示操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12
教你從頭開(kāi)始用JAVA創(chuàng)建一個(gè)自己的簡(jiǎn)單API并實(shí)現(xiàn)第三方調(diào)用
在日常開(kāi)發(fā)的時(shí)候,經(jīng)常會(huì)遇到需要調(diào)用別人的接口的場(chǎng)景,下面這篇文章主要給大家介紹了關(guān)于如何從頭開(kāi)始用JAVA創(chuàng)建一個(gè)自己的簡(jiǎn)單API并實(shí)現(xiàn)第三方調(diào)用的相關(guān)資料,需要的朋友可以參考下2023-12-12
Mybatis Plus 字段為空值時(shí)執(zhí)行更新方法未更新解決方案
這篇文章主要介紹了Mybatis Plus 字段為空值時(shí)執(zhí)行更新方法未更新解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09
SpringBoot集成swagger-ui以及swagger分組顯示操作
這篇文章主要介紹了SpringBoot集成swagger-ui以及swagger分組顯示操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09

