1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Caffeine Cache 高性能 Java 本地緩存組件

        共 16712字,需瀏覽 34分鐘

         ·

        2022-03-03 20:55

        不點藍字關(guān)注,我們哪來故事?


        一個指導(dǎo)程序員進入大公司/獨角獸?的精品社群,致力于分享職場達人的專業(yè)打法,包括「學(xué)習(xí)路線+簡歷模板+實習(xí)避坑+筆試面試+試用轉(zhuǎn)正+升職加薪+跳槽技巧」。

        點這里去了解,劍指大廠吧!








        來源:https://www.cnblogs.com/rickiyang/p/11074158.html

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

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

        |?Caffine Cache 在算法上的優(yōu)點-W-TinyLFU

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

        1. FIFO:先進先出,在這種淘汰算法中,先進入緩存的會先被淘汰,會導(dǎo)致命中率很低。
        2. LRU:最近最少使用算法,每次訪問數(shù)據(jù)都會將其放在我們的隊尾,如果需要淘汰數(shù)據(jù),就只需要淘汰隊首即可。仍然有個問題,如果有個數(shù)據(jù)在 1 分鐘訪問了 1000次,再后 1 分鐘沒有訪問這個數(shù)據(jù),但是有其他的數(shù)據(jù)訪問,就導(dǎo)致了我們這個熱點數(shù)據(jù)被淘汰。
        3. LFU:最近最少頻率使用,利用額外的空間記錄每個數(shù)據(jù)的使用頻率,然后選出頻率最低進行淘汰。這樣就避免了 LRU 不能處理時間段的問題。

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

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

        LRU的優(yōu)點和局限性?:LRU可以很好的應(yīng)對突發(fā)流量的情況,因為他不需要累計數(shù)據(jù)頻率。但LRU通過歷史數(shù)據(jù)來預(yù)測未來是局限的,它會認(rèn)為最后到來的數(shù)據(jù)是最可能被再次訪問的,從而給與它最高的優(yōu)先級。

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

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

        首先,它需要給每個記錄項維護頻率信息,每次訪問都需要更新,這是個巨大的開銷;

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

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

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

        一個是如何避免維護頻率信息的高開銷;

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

        首先來看前者,TinyLFU借助了數(shù)據(jù)流Sketching技術(shù),Count-Min Sketch顯然是解決這個問題的有效手段,它可以用小得多的空間存放頻率信息,而保證很低的False Positive Rate。但考慮到第二個問題,就要復(fù)雜許多了,因為我們知道,任何Sketching數(shù)據(jù)結(jié)構(gòu)如果要反應(yīng)時間變化都是一件困難的事情,在Bloom Filter方面,我們可以有Timing Bloom Filter,但對于CMSketch來說,如何做到Timing CMSketch就不那么容易了。TinyLFU采用了一種基于滑動窗口的時間衰減設(shè)計機制,借助于一種簡易的reset操作:每次添加一條記錄到Sketch的時候,都會給一個計數(shù)器上加1,當(dāng)計數(shù)器達到一個尺寸W的時候,把所有記錄的Sketch數(shù)值都除以2,該reset操作可以起到衰減的作用 。

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

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

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



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

        緩存填充策略

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

        手動加載

        在每次get key的時候指定一個同步的函數(shù),如果key不存在就調(diào)用這個函數(shù)生成一個值。

        /**
        ?????*?手動加載
        ?????*?@param?key
        ?????*?@return
        ?????*/

        public?Object?manulOperator(String?key)?{
        ????Cache?cache?=?Caffeine.newBuilder()
        ????????.expireAfterWrite(1,?TimeUnit.SECONDS)
        ????????.expireAfterAccess(1,?TimeUnit.SECONDS)
        ????????.maximumSize(10)
        ????????.build();
        ????//如果一個key不存在,那么會進入指定的函數(shù)生成value
        ????Object?value?=?cache.get(key,?t?->?setValue(key).apply(key));
        ????cache.put("hello",value);

        ????//判斷是否存在如果不存返回null
        ????Object?ifPresent?=?cache.getIfPresent(key);
        ????//移除一個key
        ????cache.invalidate(key);
        ????return?value;
        }

        public?Function?setValue(String?key){
        ????return?t?->?key?+?"value";
        }

        同步加載

        構(gòu)造Cache時候,build方法傳入一個CacheLoader實現(xiàn)類。實現(xiàn)load方法,通過key加載value。

        /**
        ?????*?同步加載
        ?????*?@param?key
        ?????*?@return
        ?????*/

        public?Object?syncOperator(String?key){
        ????LoadingCache?cache?=?Caffeine.newBuilder()
        ????????.maximumSize(100)
        ????????.expireAfterWrite(1,?TimeUnit.MINUTES)
        ????????.build(k?->?setValue(key).apply(key));
        ????return?cache.get(key);
        }

        public?Function?setValue(String?key){
        ????return?t?->?key?+?"value";
        }

        異步加載

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

        如果要以同步方式調(diào)用時,應(yīng)提供CacheLoader。要以異步表示時,應(yīng)該提供一個AsyncCacheLoader,并返回一個CompletableFuture。

        ?/**
        ?????*?異步加載
        ?????*
        ?????*?@param?key
        ?????*?@return
        ?????*/

        public?Object?asyncOperator(String?key){
        ????AsyncLoadingCache?cache?=?Caffeine.newBuilder()
        ????????.maximumSize(100)
        ????????.expireAfterWrite(1,?TimeUnit.MINUTES)
        ????????.buildAsync(k?->?setAsyncValue(key).get());

        ????return?cache.get(key);
        }

        public?CompletableFuture?setAsyncValue(String?key){
        ????return?CompletableFuture.supplyAsync(()?->?{
        ????????return?key?+?"value";
        ????});
        }

        回收策略

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

        基于大小的過期方式

        基于大小的回收策略有兩種方式:一種是基于緩存大小,一種是基于權(quán)重。

        //?根據(jù)緩存的計數(shù)進行驅(qū)逐
        LoadingCache?cache?=?Caffeine.newBuilder()
        ????.maximumSize(10000)
        ????.build(key?->?function(key));


        //?根據(jù)緩存的權(quán)重來進行驅(qū)逐(權(quán)重只是用于確定緩存大小,不會用于決定該緩存是否被驅(qū)逐)
        LoadingCache?cache1?=?Caffeine.newBuilder()
        ????.maximumWeight(10000)
        ????.weigher(key?->?function1(key))
        ????.build(key?->?function(key));

        maximumWeight與maximumSize不可以同時使用。
        基于時間的過期方式
        //?基于固定的到期策略進行退出
        LoadingCache?cache?=?Caffeine.newBuilder()
        ????.expireAfterAccess(5,?TimeUnit.MINUTES)
        ????.build(key?->?function(key));
        LoadingCache?cache1?=?Caffeine.newBuilder()
        ????.expireAfterWrite(10,?TimeUnit.MINUTES)
        ????.build(key?->?function(key));

        //?基于不同的到期策略進行退出
        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提供了三種定時驅(qū)逐策略:
        expireAfterAccess(long, TimeUnit):在最后一次訪問或者寫入后開始計時,在指定的時間后過期。假如一直有請求訪問該key,那么這個緩存將一直不會過期。expireAfterWrite(long, TimeUnit): 在最后一次寫入緩存后開始計時,在指定的時間后過期。expireAfter(Expiry): 自定義策略,過期時間由Expiry實現(xiàn)獨自計算。緩存的刪除策略使用的是惰性刪除和定時刪除。這兩個刪除策略的時間復(fù)雜度都是O(1)。
        基于引用的過期方式
        Java中四種引用類型
        引用類型
        被垃圾回收時間
        用途
        生存時間




        強引用 Strong Reference
        從來不會
        對象的一般狀態(tài)
        JVM停止運行時終止
        軟引用 Soft Reference
        在內(nèi)存不足時
        對象緩存
        內(nèi)存不足時終止
        弱引用 Weak Reference
        在垃圾回收時
        對象緩存
        gc運行后終止
        虛引用 Phantom Reference
        從來不會
        可以用虛引用來跟蹤對象被垃圾回收器回收的活動,當(dāng)一個虛引用關(guān)聯(lián)的對象被垃圾收集器回收之前會收到一條系統(tǒng)通知
        JVM停止運行時終止
        //?當(dāng)key和value都沒有引用時驅(qū)逐緩存
        LoadingCache?cache?=?Caffeine.newBuilder()
        ????.weakKeys()
        ????.weakValues()
        ????.build(key?->?function(key));

        //?當(dāng)垃圾收集器需要釋放內(nèi)存時驅(qū)逐
        LoadingCache?cache1?=?Caffeine.newBuilder()
        ????.softValues()
        ????.build(key?->?function(key));
        注意:AsyncLoadingCache不支持弱引用和軟引用。
        Caffeine.weakKeys():使用弱引用存儲key。如果沒有其他地方對該key有強引用,那么該緩存就會被垃圾回收器回收。由于垃圾回收器只依賴于身份(identity)相等,因此這會導(dǎo)致整個緩存使用身份 (==) 相等來比較 key,而不是使用 equals()。
        Caffeine.weakValues() :使用弱引用存儲value。如果沒有其他地方對該value有強引用,那么該緩存就會被垃圾回收器回收。由于垃圾回收器只依賴于身份(identity)相等,因此這會導(dǎo)致整個緩存使用身份 (==) 相等來比較 key,而不是使用 equals()。
        Caffeine.softValues() :使用軟引用存儲value。當(dāng)內(nèi)存滿了過后,軟引用的對象以將使用最近最少使用(least-recently-used ) 的方式進行垃圾回收。由于使用軟引用是需要等到內(nèi)存滿了才進行回收,所以我們通常建議給緩存配置一個使用內(nèi)存的最大值。softValues() 將使用身份相等(identity) (==) 而不是equals() 來比較值。
        Caffeine.weakValues()和Caffeine.softValues()不可以一起使用。
        移除事件監(jiān)聽
        Cache?cache?=?Caffeine.newBuilder()
        ????.removalListener((String?key,?Object?value,?RemovalCause?cause)?->
        ?????????????????????System.out.printf("Key?%s?was?removed?(%s)%n",?key,?cause))
        ????.build();

        寫入外部存儲

        CacheWriter 方法可以將緩存中所有的數(shù)據(jù)寫入到第三方。
        LoadingCache?cache2?=?Caffeine.newBuilder()
        ????.writer(new?CacheWriter()?{
        ????????@Override?public?void?write(String?key,?Object?value)?{
        ????????????//?寫入到外部存儲
        ????????}
        ????????@Override?public?void?delete(String?key,?Object?value,?RemovalCause?cause)?{
        ????????????//?刪除外部存儲
        ????????}
        ????})
        ????.build(key?->?function(key));
        如果你有多級緩存的情況下,這個方法還是很實用。
        注意:CacheWriter不能與弱鍵或AsyncLoadingCache一起使用。

        統(tǒng)計

        與Guava Cache的統(tǒng)計一樣。

        Cache?cache?=?Caffeine.newBuilder()
        ????.maximumSize(10_000)
        ????.recordStats()
        ????.build();
        通過使用Caffeine.recordStats(), 可以轉(zhuǎn)化成一個統(tǒng)計的集合. 通過 Cache.stats() 返回一個CacheStats。CacheStats提供以下統(tǒng)計方法:
        hitRate():?返回緩存命中率

        evictionCount():?緩存回收數(shù)量

        averageLoadPenalty():?加載新值的平均時間

        |?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)的緩存淘汰策略。
        下面我們來說在SpringBoot2.x版本中如何使用cache。
        引入依賴
        <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>

        添加注解開啟緩存支持

        添加@EnableCaching注解:
        @SpringBootApplication
        @EnableCaching
        public?class?SingleDatabaseApplication?{

        ????public?static?void?main(String[]?args)?{
        ????????SpringApplication.run(SingleDatabaseApplication.class,?args);
        ????}
        }

        配置文件的方式注入相關(guān)參數(shù)

        properties文件
        spring.cache.cache-names=cache1
        spring.cache.caffeine.spec=initialCapacity=50,maximumSize=500,expireAfterWrite=10s
        或Yaml文件
        spring:
        ??cache:
        ????type:?caffeine
        ????cache-names:
        ????-?userCache
        ????caffeine:
        ??????spec:?maximumSize=1024,refreshAfterWrite=60s
        如果使用refreshAfterWrite配置,必須指定一個CacheLoader.不用該配置則無需這個bean,如上所述,該CacheLoader將關(guān)聯(lián)被該緩存管理器管理的所有緩存,所以必須定義為CacheLoader,自動配置將忽略所有泛型類型。
        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:
        ?*/

        @Configuration
        public?class?CacheConfig?{

        ????/**
        ?????*?相當(dāng)于在構(gòu)建LoadingCache對象的時候?build()方法中指定過期之后的加載策略方法
        ?????*?必須要指定這個Bean,refreshAfterWrite=60s屬性才生效
        ?????*?@return
        ?????*/

        ????@Bean
        ????public?CacheLoader?cacheLoader()?{
        ????????CacheLoader?cacheLoader?=?new?CacheLoader()?{
        ????????????@Override
        ????????????public?Object?load(String?key)?throws?Exception?{
        ????????????????return?null;
        ????????????}
        ????????????//?重寫這個方法將oldValue值返回回去,進而刷新緩存
        ????????????@Override
        ????????????public?Object?reload(String?key,?Object?oldValue)?throws?Exception?{
        ????????????????return?oldValue;
        ????????????}
        ????????};
        ????????return?cacheLoader;
        ????}
        }
        Caffeine常用配置說明:
        initialCapacity=[integer]:?初始的緩存空間大小

        maximumSize=[long]:?緩存的最大條數(shù)

        maximumWeight=[long]:?緩存的最大權(quán)重

        expireAfterAccess=[duration]:?最后一次寫入或訪問后經(jīng)過固定時間過期

        expireAfterWrite=[duration]:?最后一次寫入后經(jīng)過固定時間過期

        refreshAfterWrite=[duration]:?創(chuàng)建緩存或者最近一次更新緩存后經(jīng)過固定的時間間隔,刷新緩存

        weakKeys:?打開key的弱引用

        weakValues:打開value的弱引用

        softValues:打開value的軟引用

        recordStats:開發(fā)統(tǒng)計功能

        注意:

        expireAfterWrite和expireAfterAccess同時存在時,以expireAfterWrite為準(zhǔn)。

        maximumSize和maximumWeight不可以同時使用

        weakValues和softValues不可以同時使用
        需要說明的是,使用配置文件的方式來進行緩存項配置,一般情況能滿足使用需求,但是靈活性不是很高,如果我們有很多緩存項的情況下寫起來會導(dǎo)致配置文件很長。所以一般情況下你也可以選擇使用bean的方式來初始化Cache實例。
        下面的演示使用bean的方式來注入:
        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:
        ?*/

        @Configuration
        public?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)建了一個SimpleCacheManager作為Cache的管理對象,然后初始化了兩個Cache對象,分別存儲user,dept類型的緩存。當(dāng)然構(gòu)建Cache的參數(shù)設(shè)置我寫的比較簡單,你在使用的時候酌情根據(jù)需要配置參數(shù)。
        使用注解來對 cache 增刪改查
        我們可以使用spring提供的?@Cacheable@CachePut、@CacheEvict等注解來方便的使用caffeine緩存。
        如果使用了多個cahce,比如redis、caffeine等,必須指定某一個CacheManage為@primary,在@Cacheable注解中沒指定 cacheManager 則使用標(biāo)記為primary的那個。
        cache方面的注解主要有以下5個:
        • @Cacheable 觸發(fā)緩存入口(這里一般放在創(chuàng)建和獲取的方法上,@Cacheable注解會先查詢是否已經(jīng)有緩存,有會使用緩存,沒有則會執(zhí)行方法并緩存)
        • @CacheEvict 觸發(fā)緩存的eviction(用于刪除的方法上)
        • @CachePut 更新緩存且不影響方法執(zhí)行(用于修改的方法上,該注解下的方法始終會被執(zhí)行)
        • @Caching 將多個緩存組合在一個方法上(該注解可以允許一個方法同時設(shè)置多個注解)
        • @CacheConfig 在類級別設(shè)置一些緩存相關(guān)的共同配置(與其它緩存配合使用)
        說一下@Cacheable?和?@CachePut的區(qū)別:
        @Cacheable:它的注解的方法是否被執(zhí)行取決于Cacheable中的條件,方法很多時候都可能不被執(zhí)行。
        @CachePut:這個注解不會影響方法的執(zhí)行,也就是說無論它配置的條件是什么,方法都會被執(zhí)行,更多的時候是被用到修改上。
        簡要說一下Cacheable類中各個方法的使用:
        public?@interface?Cacheable?{

        ????/**
        ?????*?要使用的cache的名字
        ?????*/

        ????@AliasFor("cacheNames")
        ????String[]?value()?default?{};

        ????/**
        ?????*?同value(),決定要使用那個/些緩存
        ?????*/

        ????@AliasFor("value")
        ????String[]?cacheNames()?default?{};

        ????/**
        ?????*?使用SpEL表達式來設(shè)定緩存的key,如果不設(shè)置默認(rèn)方法上所有參數(shù)都會作為key的一部分
        ?????*/

        ????String?key()?default?"";

        ????/**
        ?????*?用來生成key,與key()不可以共用
        ?????*/

        ????String?keyGenerator()?default?"";

        ????/**
        ?????*?設(shè)定要使用的cacheManager,必須先設(shè)置好cacheManager的bean,這是使用該bean的名字
        ?????*/

        ????String?cacheManager()?default?"";

        ????/**
        ?????*?使用cacheResolver來設(shè)定使用的緩存,用法同cacheManager,但是與cacheManager不可以同時使用
        ?????*/

        ????String?cacheResolver()?default?"";

        ????/**
        ?????*?使用SpEL表達式設(shè)定出發(fā)緩存的條件,在方法執(zhí)行前生效
        ?????*/

        ????String?condition()?default?"";

        ????/**
        ?????*?使用SpEL設(shè)置出發(fā)緩存的條件,這里是方法執(zhí)行完生效,所以條件中可以有方法執(zhí)行后的value
        ?????*/

        ????String?unless()?default?"";

        ????/**
        ?????*?用于同步的,在緩存失效(過期不存在等各種原因)的時候,如果多個線程同時訪問被標(biāo)注的方法
        ?????*?則只允許一個線程通過去執(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
        ?*/

        @Service
        public?class?UserCacheService?{


        ????/**
        ?????*?查找
        ?????*?先查緩存,如果查不到,會查數(shù)據(jù)庫并存入緩存
        ?????*?@param?id
        ?????*/

        ????@Cacheable(value?=?"userCache",?key?=?"#id",?sync?=?true)
        ????public?void?getUser(long?id){
        ????????//查找數(shù)據(jù)庫
        ????}

        ????/**
        ?????*?更新/保存
        ?????*?@param?user
        ?????*/

        ????@CachePut(value?=?"userCache",?key?=?"#user.id")
        ????public?void?saveUser(User?user){
        ????????//todo?保存數(shù)據(jù)庫
        ????}


        ????/**
        ?????*?刪除
        ?????*?@param?user
        ?????*/

        ????@CacheEvict(value?=?"userCache",key?=?"#user.id")
        ????public?void?delUser(User?user){
        ????????//todo?保存數(shù)據(jù)庫
        ????}
        }
        如果你不想使用注解的方式去操作緩存,也可以直接使用SimpleCacheManager獲取緩存的key進而進行操作。
        注意到上面的key使用了spEL 表達式。Spring Cache提供了一些供我們使用的SpEL上下文數(shù)據(jù),下表直接摘自Spring官方文檔:
        名稱
        位置
        描述
        示例




        methodName
        root對象
        當(dāng)前被調(diào)用的方法名
        #root.methodname
        method
        root對象
        當(dāng)前被調(diào)用的方法
        #root.method.name
        target
        root對象
        當(dāng)前被調(diào)用的目標(biāo)對象實例
        #root.target
        targetClass
        root對象
        當(dāng)前被調(diào)用的目標(biāo)對象的類
        #root.targetClass
        args
        root對象
        當(dāng)前被調(diào)用的方法的參數(shù)列表
        #root.args[0]
        caches
        root對象
        當(dāng)前方法調(diào)用使用的緩存列表
        #root.caches[0].name
        Argument Name
        執(zhí)行上下文
        當(dāng)前被調(diào)用的方法的參數(shù),如findArtisan(Artisan artisan),可以通過#artsian.id獲得參數(shù)
        #artsian.id
        result
        執(zhí)行上下文
        方法執(zhí)行后的返回值(僅當(dāng)方法執(zhí)行后的判斷有效,如 unless cacheEvict的beforeInvocation=false)
        #result
        注意:
        1.當(dāng)我們要使用root對象的屬性作為key時我們也可以將“#root”省略,因為Spring默認(rèn)使用的就是root對象的屬性。如
        @Cacheable(key?=?"targetClass?+?methodName?+#p0")
        2.使用方法參數(shù)時我們可以直接使用“#參數(shù)名”或者“#p參數(shù)index”。如:
        @Cacheable(value="userCache",?key="#id")
        @Cacheable(value="userCache",?key="#p0")
        SpEL提供了多種運算符
        類型
        運算符


        關(guān)系
        <,>,<=,>=,==,!=,lt,gt,le,ge,eq,ne
        算術(shù)
        +,- ,* ,/,%,^
        邏輯
        &&,||,!,and,or,not,between,instanceof
        條件
        ?: (ternary),?: (elvis)
        正則表達式
        matches
        其他類型
        ?.,?[…],![…],^[…],$[…]

        推薦


        歡迎加入我的知識星球,一起劍指大廠,不斷晉升加薪。

        劍指大廠不僅是一個獲取信息的圈子,還是一個規(guī)劃職業(yè)的導(dǎo)師。已在知識星球,更新如下點這里去了解,劍指大廠吧!或點擊下圖了解):


        //////?END?//////
        ↓ 點擊下方關(guān)注,看更多架構(gòu)分享?↓

        瀏覽 37
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

          <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            91手机看片 | 爆操逼逼 | 女人做爰全过程免费观看美女臀位 | 色播五月天婷婷 | 色情大片AAAAAA视频人与 | 91丨九色丨偷拍老熟女 | 日韩丝袜乱伦 | 欧美福利网站 | 中国美女美穴 | 国产亚洲精品久 |