<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          真正的緩存之王,Google Guava 只是弟弟

          共 15947字,需瀏覽 32分鐘

           ·

          2021-12-28 01:32

          點(diǎn)擊關(guān)注公眾號(hào),Java干貨及時(shí)送達(dá)

          作者:rickiyang

          來(lái)源:www.cnblogs.com/rickiyang/p/11074158.html



          前面剛說(shuō)到Guava Cache,他的優(yōu)點(diǎn)是封裝了get,put操作;提供線程安全的緩存操作;提供過(guò)期策略;提供回收策略;緩存監(jiān)控。當(dāng)緩存的數(shù)據(jù)超過(guò)最大值時(shí),使用LRU算法替換。這一篇我們將要談到一個(gè)新的本地緩存框架:Caffeine Cache。它也是站在巨人的肩膀上-Guava Cache,借著他的思想優(yōu)化了算法發(fā)展而來(lái)。

          本篇博文主要介紹Caffine Cache 的使用方式,以及Caffine Cache在SpringBoot中的使用。

          # Caffine Cache 在算法上的優(yōu)點(diǎn)-W-TinyLFU


          說(shuō)到優(yōu)化,Caffine Cache到底優(yōu)化了什么呢?我們剛提到過(guò)LRU,常見(jiàn)的緩存淘汰算法還有FIFO,LFU:

          • FIFO:先進(jìn)先出,在這種淘汰算法中,先進(jìn)入緩存的會(huì)先被淘汰,會(huì)導(dǎo)致命中率很低。

          • LRU:最近最少使用算法,每次訪問(wèn)數(shù)據(jù)都會(huì)將其放在我們的隊(duì)尾,如果需要淘汰數(shù)據(jù),就只需要淘汰隊(duì)首即可。仍然有個(gè)問(wèn)題,如果有個(gè)數(shù)據(jù)在 1 分鐘訪問(wèn)了 1000次,再后 1 分鐘沒(méi)有訪問(wèn)這個(gè)數(shù)據(jù),但是有其他的數(shù)據(jù)訪問(wèn),就導(dǎo)致了我們這個(gè)熱點(diǎn)數(shù)據(jù)被淘汰。

          • LFU:最近最少頻率使用,利用額外的空間記錄每個(gè)數(shù)據(jù)的使用頻率,然后選出頻率最低進(jìn)行淘汰。這樣就避免了 LRU 不能處理時(shí)間段的問(wèn)題。


          上面三種策略各有利弊,實(shí)現(xiàn)的成本也是一個(gè)比一個(gè)高,同時(shí)命中率也是一個(gè)比一個(gè)好。Guava Cache雖然有這么多的功能,但是本質(zhì)上還是對(duì)LRU的封裝,如果有更優(yōu)良的算法,并且也能提供這么多功能,相比之下就相形見(jiàn)絀了。

          LFU的局限性:在 LFU 中只要數(shù)據(jù)訪問(wèn)模式的概率分布隨時(shí)間保持不變時(shí),其命中率就能變得非常高。比如有部新劇出來(lái)了,我們使用 LFU 給他緩存下來(lái),這部新劇在這幾天大概訪問(wèn)了幾億次,這個(gè)訪問(wèn)頻率也在我們的 LFU 中記錄了幾億次。但是新劇總會(huì)過(guò)氣的,比如一個(gè)月之后這個(gè)新劇的前幾集其實(shí)已經(jīng)過(guò)氣了,但是他的訪問(wèn)量的確是太高了,其他的電視劇根本無(wú)法淘汰這個(gè)新劇,所以在這種模式下是有局限性。

          LRU的優(yōu)點(diǎn)和局限性:LRU可以很好的應(yīng)對(duì)突發(fā)流量的情況,因?yàn)樗恍枰塾?jì)數(shù)據(jù)頻率。但LRU通過(guò)歷史數(shù)據(jù)來(lái)預(yù)測(cè)未來(lái)是局限的,它會(huì)認(rèn)為最后到來(lái)的數(shù)據(jù)是最可能被再次訪問(wèn)的,從而給與它最高的優(yōu)先級(jí)。

          在現(xiàn)有算法的局限性下,會(huì)導(dǎo)致緩存數(shù)據(jù)的命中率或多或少的受損,而命中略又是緩存的重要指標(biāo)。HighScalability網(wǎng)站刊登了一篇文章,由前Google工程師發(fā)明的W-TinyLFU——一種現(xiàn)代的緩存 。Caffine Cache就是基于此算法而研發(fā)。Caffeine 因使用?Window TinyLfu?回收策略,提供了一個(gè)近乎最佳的命中率。

          當(dāng)數(shù)據(jù)的訪問(wèn)模式不隨時(shí)間變化的時(shí)候,LFU的策略能夠帶來(lái)最佳的緩存命中率。然而LFU有兩個(gè)缺點(diǎn):


          首先,它需要給每個(gè)記錄項(xiàng)維護(hù)頻率信息,每次訪問(wèn)都需要更新,這是個(gè)巨大的開(kāi)銷;


          其次,如果數(shù)據(jù)訪問(wèn)模式隨時(shí)間有變,LFU的頻率信息無(wú)法隨之變化,因此早先頻繁訪問(wèn)的記錄可能會(huì)占據(jù)緩存,而后期訪問(wèn)較多的記錄則無(wú)法被命中。


          因此,大多數(shù)的緩存設(shè)計(jì)都是基于LRU或者其變種來(lái)進(jìn)行的。相比之下,LRU并不需要維護(hù)昂貴的緩存記錄元信息,同時(shí)也能夠反應(yīng)隨時(shí)間變化的數(shù)據(jù)訪問(wèn)模式。然而,在許多負(fù)載之下,LRU依然需要更多的空間才能做到跟LFU一致的緩存命中率。因此,一個(gè)“現(xiàn)代”的緩存,應(yīng)當(dāng)能夠綜合兩者的長(zhǎng)處。


          TinyLFU維護(hù)了近期訪問(wèn)記錄的頻率信息,作為一個(gè)過(guò)濾器,當(dāng)新記錄來(lái)時(shí),只有滿足TinyLFU要求的記錄才可以被插入緩存。如前所述,作為現(xiàn)代的緩存,它需要解決兩個(gè)挑戰(zhàn):

          一個(gè)是如何避免維護(hù)頻率信息的高開(kāi)銷;

          另一個(gè)是如何反應(yīng)隨時(shí)間變化的訪問(wèn)模式。

          首先來(lái)看前者,TinyLFU借助了數(shù)據(jù)流Sketching技術(shù),Count-Min Sketch顯然是解決這個(gè)問(wèn)題的有效手段,它可以用小得多的空間存放頻率信息,而保證很低的False Positive Rate。但考慮到第二個(gè)問(wèn)題,就要復(fù)雜許多了,因?yàn)槲覀冎溃魏蜸ketching數(shù)據(jù)結(jié)構(gòu)如果要反應(yīng)時(shí)間變化都是一件困難的事情,在Bloom Filter方面,我們可以有Timing Bloom Filter,但對(duì)于CMSketch來(lái)說(shuō),如何做到Timing CMSketch就不那么容易了。


          TinyLFU采用了一種基于滑動(dòng)窗口的時(shí)間衰減設(shè)計(jì)機(jī)制,借助于一種簡(jiǎn)易的reset操作:每次添加一條記錄到Sketch的時(shí)候,都會(huì)給一個(gè)計(jì)數(shù)器上加1,當(dāng)計(jì)數(shù)器達(dá)到一個(gè)尺寸W的時(shí)候,把所有記錄的Sketch數(shù)值都除以2,該reset操作可以起到衰減的作用 。

          W-TinyLFU主要用來(lái)解決一些稀疏的突發(fā)訪問(wèn)元素。在一些數(shù)目很少但突發(fā)訪問(wèn)量很大的場(chǎng)景下,TinyLFU將無(wú)法保存這類元素,因?yàn)樗鼈儫o(wú)法在給定時(shí)間內(nèi)積累到足夠高的頻率。因此W-TinyLFU就是結(jié)合LFU和LRU,前者用來(lái)應(yīng)對(duì)大多數(shù)場(chǎng)景,而LRU用來(lái)處理突發(fā)流量。

          在處理頻率記錄的方案中,你可能會(huì)想到用hashMap去存儲(chǔ),每一個(gè)key對(duì)應(yīng)一個(gè)頻率值。那如果數(shù)據(jù)量特別大的時(shí)候,是不是這個(gè)hashMap也會(huì)特別大呢。由此可以聯(lián)想到 Bloom Filter,對(duì)于每個(gè)key,用n個(gè)byte每個(gè)存儲(chǔ)一個(gè)標(biāo)志用來(lái)判斷key是否在集合中。原理就是使用k個(gè)hash函數(shù)來(lái)將key散列成一個(gè)整數(shù)。

          在W-TinyLFU中使用Count-Min Sketch記錄我們的訪問(wèn)頻率,而這個(gè)也是布隆過(guò)濾器的一種變種。如下圖所示:

          如果需要記錄一個(gè)值,那我們需要通過(guò)多種Hash算法對(duì)其進(jìn)行處理hash,然后在對(duì)應(yīng)的hash算法的記錄中+1,為什么需要多種hash算法呢?由于這是一個(gè)壓縮算法必定會(huì)出現(xiàn)沖突,比如我們建立一個(gè)byte的數(shù)組,通過(guò)計(jì)算出每個(gè)數(shù)據(jù)的hash的位置。比如張三和李四,他們兩有可能hash值都是相同,比如都是1那byte[1]這個(gè)位置就會(huì)增加相應(yīng)的頻率,張三訪問(wèn)1萬(wàn)次,李四訪問(wèn)1次那byte[1]這個(gè)位置就是1萬(wàn)零1,如果取李四的訪問(wèn)評(píng)率的時(shí)候就會(huì)取出是1萬(wàn)零1,但是李四命名只訪問(wèn)了1次啊,為了解決這個(gè)問(wèn)題,所以用了多個(gè)hash算法可以理解為long[][]二維數(shù)組的一個(gè)概念,比如在第一個(gè)算法張三和李四沖突了,但是在第二個(gè),第三個(gè)中很大的概率不沖突,比如一個(gè)算法大概有1%的概率沖突,那四個(gè)算法一起沖突的概率是1%的四次方。通過(guò)這個(gè)模式我們?nèi)±钏牡脑L問(wèn)率的時(shí)候取所有算法中,李四訪問(wèn)最低頻率的次數(shù)。所以他的名字叫Count-Min Sketch。

          # 使用


          Caffeine Cache 的github地址:
          https://github.com/ben-manes/caffeine

          目前的最新版本是:

          <dependency> <groupId>com.github.ben-manes.caffeinegroupId> <artifactId>caffeineartifactId> <version>2.6.2version>dependency>

          1.緩存填充策略


          Caffeine Cache提供了三種緩存填充策略:手動(dòng)、同步加載和異步加載。

          1.手動(dòng)加載


          在每次get key的時(shí)候指定一個(gè)同步的函數(shù),如果key不存在就調(diào)用這個(gè)函數(shù)生成一個(gè)值。
          /**     * 手動(dòng)加載     * @param key     * @return     */public Object manulOperator(String key) {    Cache<String, Object> cache = Caffeine.newBuilder()        .expireAfterWrite(1, TimeUnit.SECONDS)        .expireAfterAccess(1, TimeUnit.SECONDS)        .maximumSize(10)        .build();    //如果一個(gè)key不存在,那么會(huì)進(jìn)入指定的函數(shù)生成value    Object value = cache.get(key, t -> setValue(key).apply(key));????cache.put("hello",value);    //判斷是否存在如果不存返回null    Object ifPresent = cache.getIfPresent(key);    //移除一個(gè)key    cache.invalidate(key);    return value;}public Function setValue(String key){    return t -> key + "value";}

          2. 同步加載


          構(gòu)造Cache時(shí)候,build方法傳入一個(gè)CacheLoader實(shí)現(xiàn)類。實(shí)現(xiàn)load方法,通過(guò)key加載value。
          /**     * 同步加載     * @param key     * @return     */public Object syncOperator(String key){    LoadingCache<String, Object> cache = Caffeine.newBuilder()        .maximumSize(100)        .expireAfterWrite(1, TimeUnit.MINUTES)        .build(k -> setValue(key).apply(key));    return cache.get(key);}public Function<String, Object> setValue(String key){    return t -> key + "value";}

          3. 異步加載


          AsyncLoadingCache是繼承自LoadingCache類的,異步加載使用Executor去調(diào)用方法并返回一個(gè)CompletableFuture。異步加載緩存使用了響應(yīng)式編程模型。

          如果要以同步方式調(diào)用時(shí),應(yīng)提供CacheLoader。要以異步表示時(shí),應(yīng)該提供一個(gè)AsyncCacheLoader,并返回一個(gè)CompletableFuture。
          /**     * 異步加載     *     * @param key     * @return     */public Object asyncOperator(String key){    AsyncLoadingCache<String, Object> cache = Caffeine.newBuilder()        .maximumSize(100)        .expireAfterWrite(1, TimeUnit.MINUTES)        .buildAsync(k -> setAsyncValue(key).get());????return?cache.get(key);}public CompletableFuture<Object> setAsyncValue(String key){    return CompletableFuture.supplyAsync(() -> {        return key + "value";    });}

          2.回收策略


          Caffeine提供了3種回收策略:基于大小回收,基于時(shí)間回收,基于引用回收。

          1. 基于大小的過(guò)期方式


          基于大小的回收策略有兩種方式:一種是基于緩存大小,一種是基于權(quán)重。
          // 根據(jù)緩存的計(jì)數(shù)進(jìn)行驅(qū)逐LoadingCache cache = Caffeine.newBuilder()    .maximumSize(10000)    .build(key -> function(key));// 根據(jù)緩存的權(quán)重來(lái)進(jìn)行驅(qū)逐(權(quán)重只是用于確定緩存大小,不會(huì)用于決定該緩存是否被驅(qū)逐)LoadingCache cache1 = Caffeine.newBuilder()    .maximumWeight(10000)    .weigher(key -> function1(key))    .build(key -> function(key));

          maximumWeight與maximumSize不可以同時(shí)使用。

          2.基于時(shí)間的過(guò)期方式
          // 基于固定的到期策略進(jìn)行退出LoadingCache cache = Caffeine.newBuilder()    .expireAfterAccess(5, TimeUnit.MINUTES)    .build(key -> function(key));LoadingCache cache1 = Caffeine.newBuilder()    .expireAfterWrite(10, TimeUnit.MINUTES)    .build(key -> function(key));// 基于不同的到期策略進(jìn)行退出LoadingCache cache2 = Caffeine.newBuilder()    .expireAfter(new Expiry() {        @Override        public long expireAfterCreate(String key, Object value, long currentTime) {            return TimeUnit.SECONDS.toNanos(seconds);        }????????@Override        public long expireAfterUpdate(@Nonnull String s, @Nonnull Object o, long l, long l1) {            return 0;        }????????@Override        public long expireAfterRead(@Nonnull String s, @Nonnull Object o, long l, long l1) {            return 0;        }    }).build(key -> function(key));

          Caffeine提供了三種定時(shí)驅(qū)逐策略:

          expireAfterAccess(long, TimeUnit):在最后一次訪問(wèn)或者寫(xiě)入后開(kāi)始計(jì)時(shí),在指定的時(shí)間后過(guò)期。假如一直有請(qǐng)求訪問(wèn)該key,那么這個(gè)緩存將一直不會(huì)過(guò)期。
          expireAfterWrite(long, TimeUnit): 在最后一次寫(xiě)入緩存后開(kāi)始計(jì)時(shí),在指定的時(shí)間后過(guò)期。


          expireAfter(Expiry): 自定義策略,過(guò)期時(shí)間由Expiry實(shí)現(xiàn)獨(dú)自計(jì)算。


          緩存的刪除策略使用的是惰性刪除和定時(shí)刪除。這兩個(gè)刪除策略的時(shí)間復(fù)雜度都是O(1)。

          3. 基于引用的過(guò)期方式


          Java中四種引用類型

          // 當(dāng)key和value都沒(méi)有引用時(shí)驅(qū)逐緩存LoadingCache cache = Caffeine.newBuilder()    .weakKeys()    .weakValues()    .build(key -> function(key));//?當(dāng)垃圾收集器需要釋放內(nèi)存時(shí)驅(qū)逐LoadingCache cache1 = Caffeine.newBuilder()    .softValues()    .build(key -> function(key));

          注意:AsyncLoadingCache不支持弱引用和軟引用。

          Caffeine.weakKeys():使用弱引用存儲(chǔ)key。如果沒(méi)有其他地方對(duì)該key有強(qiáng)引用,那么該緩存就會(huì)被垃圾回收器回收。由于垃圾回收器只依賴于身份(identity)相等,因此這會(huì)導(dǎo)致整個(gè)緩存使用身份 (==) 相等來(lái)比較 key,而不是使用 equals()。

          Caffeine.weakValues() :使用弱引用存儲(chǔ)value。如果沒(méi)有其他地方對(duì)該value有強(qiáng)引用,那么該緩存就會(huì)被垃圾回收器回收。由于垃圾回收器只依賴于身份(identity)相等,因此這會(huì)導(dǎo)致整個(gè)緩存使用身份 (==) 相等來(lái)比較 key,而不是使用 equals()。

          Caffeine.softValues() :使用軟引用存儲(chǔ)value。當(dāng)內(nèi)存滿了過(guò)后,軟引用的對(duì)象以將使用最近最少使用(least-recently-used ) 的方式進(jìn)行垃圾回收。由于使用軟引用是需要等到內(nèi)存滿了才進(jìn)行回收,所以我們通常建議給緩存配置一個(gè)使用內(nèi)存的最大值。softValues() 將使用身份相等(identity) (==) 而不是equals() 來(lái)比較值。

          Caffeine.weakValues()和Caffeine.softValues()不可以一起使用。

          # 移除事件監(jiān)聽(tīng)
          Cache<String, Object> cache = Caffeine.newBuilder() .removalListener((String key, Object value, RemovalCause cause) -> System.out.printf("Key %s was removed (%s)%n", key, cause)) .build();

          # 寫(xiě)入外部存儲(chǔ)


          CacheWriter 方法可以將緩存中所有的數(shù)據(jù)寫(xiě)入到第三方。
          LoadingCache<String, Object> cache2 = Caffeine.newBuilder() .writer(new CacheWriter<String, Object>() { @Override public void write(String key, Object value) { // 寫(xiě)入到外部存儲(chǔ) } @Override public void delete(String key, Object value, RemovalCause cause) { // 刪除外部存儲(chǔ) } }) .build(key -> function(key));

          如果你有多級(jí)緩存的情況下,這個(gè)方法還是很實(shí)用。

          注意:CacheWriter不能與弱鍵或AsyncLoadingCache一起使用。

          # 統(tǒng)計(jì)


          與Guava Cache的統(tǒng)計(jì)一樣。
          Cache<String, Object> cache = Caffeine.newBuilder() .maximumSize(10_000) .recordStats() .build();

          通過(guò)使用Caffeine.recordStats(), 可以轉(zhuǎn)化成一個(gè)統(tǒng)計(jì)的集合. 通過(guò) Cache.stats() 返回一個(gè)CacheStats。CacheStats提供以下統(tǒng)計(jì)方法:
          hitRate(): 返回緩存命中率evictionCount():?緩存回收數(shù)量averageLoadPenalty():?加載新值的平均時(shí)間

          # SpringBoot 中默認(rèn)Cache-Caffine Cache


          SpringBoot 1.x版本中的默認(rèn)本地cache是Guava Cache。在2.x(Spring Boot 2.0(spring 5) )版本中已經(jīng)用Caffine Cache取代了Guava Cache。畢竟有了更優(yōu)的緩存淘汰策略。

          下面我們來(lái)說(shuō)在SpringBoot2.x版本中如何使用cache。

          1. 引入依賴:
          <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-cacheartifactId>dependency><dependency> <groupId>com.github.ben-manes.caffeinegroupId> <artifactId>caffeineartifactId> <version>2.6.2version>dependency>

          2. 添加注解開(kāi)啟緩存支持
          添加@EnableCaching注解:
          @SpringBootApplication@EnableCachingpublic?class?SingleDatabaseApplication?{    public static void main(String[] args) {        SpringApplication.run(SingleDatabaseApplication.class, args);    }}
          3. 配置文件的方式注入相關(guān)參數(shù)

          properties文件
          spring.cache.cache-names=cache1spring.cache.caffeine.spec=initialCapacity=50,maximumSize=500,expireAfterWrite=10s

          或Yaml文件
          spring: cache: type: caffeine cache-names: - userCache caffeine: spec: maximumSize=1024,refreshAfterWrite=60s

          如果使用refreshAfterWrite配置,必須指定一個(gè)CacheLoader.不用該配置則無(wú)需這個(gè)bean,如上所述,該CacheLoader將關(guān)聯(lián)被該緩存管理器管理的所有緩存,所以必須定義為CacheLoader,自動(dòng)配置將忽略所有泛型類型。
          import com.github.benmanes.caffeine.cache.CacheLoader;import org.springframework.context.annotation.Bean;import?org.springframework.context.annotation.Configuration;/** * @author: rickiyang * @date: 2019/6/15 * @description: */@Configurationpublic?class?CacheConfig?{    /**     * 相當(dāng)于在構(gòu)建LoadingCache對(duì)象的時(shí)候 build()方法中指定過(guò)期之后的加載策略方法     * 必須要指定這個(gè)Bean,refreshAfterWrite=60s屬性才生效     * @return     */    @Bean    public CacheLoader<String, Object> cacheLoader() {        CacheLoader<String, Object> cacheLoader = new CacheLoader<String, Object>() {            @Override            public Object load(String key) throws Exception {                return null;            }            // 重寫(xiě)這個(gè)方法將oldValue值返回回去,進(jìn)而刷新緩存            @Override            public Object reload(String key, Object oldValue) throws Exception {                return oldValue;            }        };        return cacheLoader;    }}

          Caffeine常用配置說(shuō)明:
          initialCapacity=[integer]:?初始的緩存空間大小maximumSize=[long]:?緩存的最大條數(shù)maximumWeight=[long]:?緩存的最大權(quán)重expireAfterAccess=[duration]:?最后一次寫(xiě)入或訪問(wèn)后經(jīng)過(guò)固定時(shí)間過(guò)期expireAfterWrite=[duration]:?最后一次寫(xiě)入后經(jīng)過(guò)固定時(shí)間過(guò)期refreshAfterWrite=[duration]:?創(chuàng)建緩存或者最近一次更新緩存后經(jīng)過(guò)固定的時(shí)間間隔,刷新緩存weakKeys:?打開(kāi)key的弱引用weakValues:打開(kāi)value的弱引用softValues:打開(kāi)value的軟引用recordStats:開(kāi)發(fā)統(tǒng)計(jì)功能注意:expireAfterWrite和expireAfterAccess同時(shí)存在時(shí),以expireAfterWrite為準(zhǔn)。maximumSize和maximumWeight不可以同時(shí)使用weakValues和softValues不可以同時(shí)使用

          需要說(shuō)明的是,使用配置文件的方式來(lái)進(jìn)行緩存項(xiàng)配置,一般情況能滿足使用需求,但是靈活性不是很高,如果我們有很多緩存項(xiàng)的情況下寫(xiě)起來(lái)會(huì)導(dǎo)致配置文件很長(zhǎng)。所以一般情況下你也可以選擇使用bean的方式來(lái)初始化Cache實(shí)例。

          下面的演示使用bean的方式來(lái)注入:
          package?com.rickiyang.learn.cache;import com.github.benmanes.caffeine.cache.CacheLoader;import com.github.benmanes.caffeine.cache.Caffeine;import org.apache.commons.compress.utils.Lists;import org.springframework.cache.CacheManager;import org.springframework.cache.caffeine.CaffeineCache;import org.springframework.cache.support.SimpleCacheManager;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import?org.springframework.context.annotation.Primary;import java.util.ArrayList;import java.util.List;import?java.util.concurrent.TimeUnit;/** * @author: rickiyang * @date: 2019/6/15 * @description: */@Configurationpublic class CacheConfig {    /**     * 創(chuàng)建基于Caffeine的Cache Manager     * 初始化一些key存入     * @return     */    @Bean    @Primary    public CacheManager caffeineCacheManager() {        SimpleCacheManager cacheManager = new SimpleCacheManager();        ArrayList caches = Lists.newArrayList();        List list = setCacheBean();        for(CacheBean cacheBean : list){            caches.add(new CaffeineCache(cacheBean.getKey(),                    Caffeine.newBuilder().recordStats()                            .expireAfterWrite(cacheBean.getTtl(), TimeUnit.SECONDS)                            .maximumSize(cacheBean.getMaximumSize())                            .build()));        }        cacheManager.setCaches(caches);        return cacheManager;????}    /**     * 初始化一些緩存的 key     * @return     */    private List setCacheBean(){        List list = Lists.newArrayList();        CacheBean userCache = new CacheBean();        userCache.setKey("userCache");        userCache.setTtl(60);????????userCache.setMaximumSize(10000);        CacheBean deptCache = new CacheBean();        deptCache.setKey("userCache");        deptCache.setTtl(60);????????deptCache.setMaximumSize(10000);        list.add(userCache);????????list.add(deptCache);        return list;????}    class CacheBean {        private String key;        private long ttl;????????private?long?maximumSize;        public String getKey() {            return key;????????}        public void setKey(String key) {            this.key = key;????????}        public long getTtl() {            return ttl;????????}        public void setTtl(long ttl) {            this.ttl = ttl;????????}        public long getMaximumSize() {            return maximumSize;????????}        public void setMaximumSize(long maximumSize) {            this.maximumSize = maximumSize;        }????}}

          創(chuàng)建了一個(gè)SimpleCacheManager作為Cache的管理對(duì)象,然后初始化了兩個(gè)Cache對(duì)象,分別存儲(chǔ)user,dept類型的緩存。當(dāng)然構(gòu)建Cache的參數(shù)設(shè)置我寫(xiě)的比較簡(jiǎn)單,你在使用的時(shí)候酌情根據(jù)需要配置參數(shù)。

          # 使用注解來(lái)對(duì) cache 增刪改查


          我們可以使用spring提供的 @Cacheable、@CachePut、@CacheEvict等注解來(lái)方便的使用caffeine緩存。

          如果使用了多個(gè)cahce,比如redis、caffeine等,必須指定某一個(gè)CacheManage為@primary,在@Cacheable注解中沒(méi)指定 cacheManager 則使用標(biāo)記為primary的那個(gè)。

          cache方面的注解主要有以下5個(gè):

          • @Cacheable 觸發(fā)緩存入口(這里一般放在創(chuàng)建和獲取的方法上,@Cacheable注解會(huì)先查詢是否已經(jīng)有緩存,有會(huì)使用緩存,沒(méi)有則會(huì)執(zhí)行方法并緩存)

          • @CacheEvict 觸發(fā)緩存的eviction(用于刪除的方法上)

          • @CachePut 更新緩存且不影響方法執(zhí)行(用于修改的方法上,該注解下的方法始終會(huì)被執(zhí)行)

          • @Caching 將多個(gè)緩存組合在一個(gè)方法上(該注解可以允許一個(gè)方法同時(shí)設(shè)置多個(gè)注解)

          • @CacheConfig 在類級(jí)別設(shè)置一些緩存相關(guān)的共同配置(與其它緩存配合使用)


          說(shuō)一下@Cacheable 和 @CachePut的區(qū)別:

          • @Cacheable:它的注解的方法是否被執(zhí)行取決于Cacheable中的條件,方法很多時(shí)候都可能不被執(zhí)行。


          • @CachePut:這個(gè)注解不會(huì)影響方法的執(zhí)行,也就是說(shuō)無(wú)論它配置的條件是什么,方法都會(huì)被執(zhí)行,更多的時(shí)候是被用到修改上。


          簡(jiǎn)要說(shuō)一下Cacheable類中各個(gè)方法的使用:
          public?@interface?Cacheable?{    /**     * 要使用的cache的名字     */    @AliasFor("cacheNames")    String[] value() default {};    /**     * 同value(),決定要使用那個(gè)/些緩存     */    @AliasFor("value")    String[] cacheNames() default {};    /**     * 使用SpEL表達(dá)式來(lái)設(shè)定緩存的key,如果不設(shè)置默認(rèn)方法上所有參數(shù)都會(huì)作為key的一部分     */    String key() default "";    /**     * 用來(lái)生成key,與key()不可以共用     */????String?keyGenerator()?default?"";    /**     * 設(shè)定要使用的cacheManager,必須先設(shè)置好cacheManager的bean,這是使用該bean的名字     */????String?cacheManager()?default?"";    /**     * 使用cacheResolver來(lái)設(shè)定使用的緩存,用法同cacheManager,但是與cacheManager不可以同時(shí)使用     */    String cacheResolver() default "";    /**     * 使用SpEL表達(dá)式設(shè)定出發(fā)緩存的條件,在方法執(zhí)行前生效     */    String condition() default "";    /**     * 使用SpEL設(shè)置出發(fā)緩存的條件,這里是方法執(zhí)行完生效,所以條件中可以有方法執(zhí)行后的value     */    String unless() default "";    /**     * 用于同步的,在緩存失效(過(guò)期不存在等各種原因)的時(shí)候,如果多個(gè)線程同時(shí)訪問(wèn)被標(biāo)注的方法     * 則只允許一個(gè)線程通過(guò)去執(zhí)行方法     */????boolean?sync()?default?false;}

          基于注解的使用方法:
          package?com.rickiyang.learn.cache;import com.rickiyang.learn.entity.User;import org.springframework.cache.annotation.CacheEvict;import org.springframework.cache.annotation.CachePut;import org.springframework.cache.annotation.Cacheable;import org.springframework.stereotype.Service;/** * @author: rickiyang * @date: 2019/6/15 * @description: 本地cache */@Servicepublic?class?UserCacheService?{    /**     * 查找     * 先查緩存,如果查不到,會(huì)查數(shù)據(jù)庫(kù)并存入緩存     * @param id     */    @Cacheable(value = "userCache", key = "#id", sync = true)    public void getUser(long id){        //查找數(shù)據(jù)庫(kù)    }    /**     * 更新/保存     * @param user     */    @CachePut(value = "userCache", key = "#user.id")    public void saveUser(User user){        //todo 保存數(shù)據(jù)庫(kù)????}    /**     * 刪除     * @param user     */    @CacheEvict(value = "userCache",key = "#user.id")    public void delUser(User user){        //todo 保存數(shù)據(jù)庫(kù)    }}

          如果你不想使用注解的方式去操作緩存,也可以直接使用SimpleCacheManager獲取緩存的key進(jìn)而進(jìn)行操作。

          注意到上面的key使用了spEL 表達(dá)式。Spring Cache提供了一些供我們使用的SpEL上下文數(shù)據(jù),下表直接摘自Spring官方文檔:

          注意:

          1.當(dāng)我們要使用root對(duì)象的屬性作為key時(shí)我們也可以將“#root”省略,因?yàn)镾pring默認(rèn)使用的就是root對(duì)象的屬性。如
          @Cacheable(key = "targetClass + methodName +#p0")

          2.使用方法參數(shù)時(shí)我們可以直接使用“#參數(shù)名”或者“#p參數(shù)index”。如:
          @Cacheable(value="userCache", key="#id")@Cacheable(value="userCache", key="#p0")

          SpEL提供了多種運(yùn)算符


          1、Log4j2維護(hù)者吐槽沒(méi)工資還要挨罵,GO安全負(fù)責(zé)人建議開(kāi)源作者向公司收費(fèi)
          2、太難了!讓程序員崩潰的8個(gè)瞬間
          3、2021年程序員們都在用的神級(jí)數(shù)據(jù)庫(kù)
          4、Windows重要功能被閹割,全球用戶怒噴數(shù)月后微軟終于悔改
          5、牛逼!國(guó)產(chǎn)開(kāi)源的遠(yuǎn)程桌面火了,只有9MB 支持自建中繼器!
          6、摔到老三的 Java,未來(lái)在哪?
          7、真香!用 IDEA 神器看源碼,效率真高!

          點(diǎn)分享

          點(diǎn)收藏

          點(diǎn)點(diǎn)贊

          點(diǎn)在看

          瀏覽 65
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产伦精品一区二区三区妓女原神 | 竹菊影视一区二区三区四区 | 奥门伊人在线 | 无码a√中文字幕 | 亚洲中文字幕一二三无码欧美 |