最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Caffeine本地緩存核心原理與使用方法詳解(含與Spring?Cache集成)

 更新時間:2025年09月11日 10:40:43   作者:何蘇三月  
本文介紹Caffeine作為高性能本地緩存庫,具備智能淘汰策略和并發(fā)優(yōu)化,集成SpringCache實(shí)現(xiàn)緩存管理,探討緩存穿透、擊穿、雪崩防護(hù)及多級緩存(Caffeine+Redis)方案,適用于Java生態(tài)的緩存實(shí)踐,感興趣的朋友跟隨小編一起看看吧

一、介紹

JDK內(nèi)置的Map可作為緩存的一種實(shí)現(xiàn)方式,然而嚴(yán)格意義來講,其不能算作緩存的范疇。

原因如下:一是其存儲的數(shù)據(jù)不能主動過期;二是無任何緩存淘汰策略。

Caffeine是一個基于Java 8的高性能本地緩存庫,由Ben Manes開發(fā),旨在提供快速、靈活的緩存解決方案。作為Guava Cache的現(xiàn)代替代品,Caffeine在性能、功能和靈活性方面都有顯著提升。

Caffeine作為Spring體系中內(nèi)置的緩存之一,Spring Cache同樣提供調(diào)用接口支持。已成為Java生態(tài)中最受歡迎的本地緩存庫之一。

本文將全面介紹Caffeine的核心原理、使用方法和最佳實(shí)踐。

二、Caffeine核心原理與架構(gòu)設(shè)計(jì)

2.1 存儲引擎與數(shù)據(jù)結(jié)構(gòu)

Caffeine底層采用優(yōu)化的ConcurrentHashMap作為主要存儲結(jié)構(gòu),并在此基礎(chǔ)上進(jìn)行了多項(xiàng)創(chuàng)新:

  • ??分段存儲技術(shù)??:使用StripedBuffer實(shí)現(xiàn)無鎖化并發(fā)控制,將競爭分散到多個獨(dú)立緩沖區(qū),顯著提升并發(fā)吞吐量。
  • ??頻率統(tǒng)計(jì)機(jī)制??:采用Count-Min Sketch算法記錄訪問頻率,以93.75%的準(zhǔn)確率僅使用少量內(nèi)存空間。
  • ??時間輪管理??:通過TimerWheel數(shù)據(jù)結(jié)構(gòu)高效管理過期條目,實(shí)現(xiàn)納秒級精度的過期控制。

2.2 緩存淘汰策略

Caffeine采用了創(chuàng)新的Window-TinyLFU算法,結(jié)合了LRU和LFU的優(yōu)點(diǎn):

  • ??三區(qū)設(shè)計(jì)??:窗口區(qū)(20%)、試用區(qū)(1%)和主區(qū)(79%),各區(qū)使用LRU雙端隊(duì)列管理
  • ??動態(tài)調(diào)整??:根據(jù)訪問模式自動調(diào)整各區(qū)比例,最高可實(shí)現(xiàn)98%的緩存命中率
  • ??頻率衰減??:通過周期性衰減歷史頻率,防止舊熱點(diǎn)數(shù)據(jù)長期占據(jù)緩存

相比Guava Cache的LRU算法,Window-TinyLFU能更準(zhǔn)確地識別和保留真正的熱點(diǎn)數(shù)據(jù),避免"一次性訪問"污染緩存。

2.3 并發(fā)控制機(jī)制

Caffeine的并發(fā)控制體系設(shè)計(jì)精妙:

  • ??寫緩沖機(jī)制??:使用RingBuffer和MpscChunkedArrayQueue實(shí)現(xiàn)多生產(chǎn)者-單消費(fèi)者隊(duì)列
  • ??樂觀鎖優(yōu)化??:通過ReadAndWriteCounterRef等自定義原子引用降低CAS開銷
  • ??StampedLock應(yīng)用??:在關(guān)鍵路徑上使用Java 8的StampedLock替代傳統(tǒng)鎖,提升并發(fā)性能

三、入門案例

3.1 引入依賴

以springboot 2.3.x為例,

<!--  caffeine    -->
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
</dependency>

3.2 測試接口

package com.example.demo;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
@RestController
@RequestMapping("/api")
public class Controller {
    @GetMapping("writeCache")
    public String writeCache() {
        Cache<Object, Object> cache = Caffeine.newBuilder().build();
        cache.put("uuid", UUID.randomUUID());
        User user = new User("張三", "123456@qq.com", "abc123", 18);
        cache.put("user", user);
        return "寫入緩存成功";
    }
    @GetMapping("readCache")
    public String readCache() {
        Cache<Object, Object> cache = Caffeine.newBuilder().build();
        Object uuid = cache.getIfPresent("uuid");
        Object user = cache.getIfPresent("user");
        return "uuid: " + uuid + ", user: " + user;
    }
}

 

問題:明明調(diào)用接口寫入了緩存,為什么我們查詢的時候還是沒有呢?

細(xì)心的你可能已經(jīng)發(fā)現(xiàn)了,我們在每個接口都重新構(gòu)造了一個新的Cache實(shí)例。這兩個Cache實(shí)例是完全獨(dú)立的,數(shù)據(jù)不會自動共享。

解決辦法

所以,聰明的你可能就想著把它提取出來,成功公共變量吧

@RestController
@RequestMapping("/api")
public class Controller {
    Cache<Object, Object> cache = Caffeine.newBuilder().build();
    @GetMapping("writeCache")
    public String writeCache() {
        cache.put("uuid", UUID.randomUUID());
        User user = new User("張三", "123456@qq.com", "abc123", 18);
        cache.put("user", user);
        return "寫入緩存成功";
    }
    @GetMapping("readCache")
    public String readCache() {
        Object uuid = cache.getIfPresent("uuid");
        Object user = cache.getIfPresent("user");
        return "uuid: " + uuid + ", user: " + user;
    }
}

你看這不就有了!于是聰明的你,又想:“如果放在這個控制器類下面,那我其他類中要是想調(diào)用,是不是不太好?”

于是你又把它放在一個配置類下面,用于專門管理緩存。

package com.example.demo;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CacheConfig {
    @Bean
    public Cache<String, Object> buildCache() {
        return Caffeine.newBuilder().build();
    }
}
@RestController
@RequestMapping("/api")
public class Controller {
    @Resource
    private Cache<String, Object> cache;
    @GetMapping("writeCache")
    public String writeCache() {
        cache.put("uuid", UUID.randomUUID());
        User user = new User("張三", "123456@qq.com", "abc123", 18);
        cache.put("user", user);
        return "寫入緩存成功";
    }
    @GetMapping("readCache")
    public String readCache() {
        Object uuid = cache.getIfPresent("uuid");
        Object user = cache.getIfPresent("user");
        return "uuid: " + uuid + ", user: " + user;
    }
}

聰明的你,發(fā)現(xiàn)依然可以呀!真棒!

于是你又靈機(jī)一動,多定義幾個bean吧,一個設(shè)置有效期,一個永不過期。

@Configuration
public class CacheConfig {
    @Bean("noLimit")
    public Cache<String, Object> buildCache() {
        return Caffeine.newBuilder().build();
    }
    @Bean("limited")
    public Cache<String, Object> buildLimitedCache() {
        // 設(shè)置過期時間是30s
        return Caffeine.newBuilder().expireAfterWrite(30, TimeUnit.SECONDS).build();
    }
}
@RestController
@RequestMapping("/api")
public class Controller {
    @Resource(name = "limited")
    private Cache<String, Object> cache;
    @GetMapping("writeCache")
    public String writeCache() {
        cache.put("uuid", UUID.randomUUID());
        User user = new User("張三", "123456@qq.com", "abc123", 18);
        cache.put("user", user);
        return "寫入緩存成功";
    }
    @GetMapping("readCache")
    public String readCache() {
        Object uuid = cache.getIfPresent("uuid");
        Object user = cache.getIfPresent("user");
        return "uuid: " + uuid + ", user: " + user;
    }
}

你發(fā)現(xiàn)30s后加入的緩存也沒有了。

3.3 小結(jié)

通過這個案例,你似乎也覺察到了,Caffeine的基本使用方法

  1. 導(dǎo)入依賴
  2. 構(gòu)建公共緩存對象(expireAfterWrite方法可以設(shè)置寫入后多久過期)
  3. 使用 put() 方法添加緩存
  4. 使用 getIfPresent() 方法讀取緩存
  5. 一旦重啟項(xiàng)目,緩存就都消失了(基于本地內(nèi)存)!

四、Caffeine常用方法詳解

4.1 getIfPresent

@Nullable V getIfPresent(@CompatibleWith("K") @NonNull Object var1);

前面已經(jīng)演示過了,這里就不在舉例了。意思是如果存在則獲取,不存在就是null。

4.2 get

@Nullable V get(@NonNull K var1, @NonNull Function<? super K, ? extends V> var2);

@GetMapping("readCache")
public String readCache() {
    Object uuid = cache.getIfPresent("uuid");
    Object user = cache.get("user", item -> {
        // 緩存不存在時,執(zhí)行加載邏輯
        return new User("李四", "456789@qq.com", "def456", 20);
    });
    return "uuid: " + uuid + ", user: " + user;
}

4.3 put

 void put(@NonNull K var1, @NonNull V var2);

 入門案例也演示過了,就是添加緩存。使用方法和普通的map類似,都是key,value的形式。

4.4 putAll

void putAll(@NonNull Map<? extends @NonNull K, ? extends @NonNull V> var1);

putAll 顧名思義,就是可以批量寫入緩存。首先定義一個map對象,把要加入的緩存往map里面塞,然后把map作為參數(shù)傳遞給這個方法即可。

4.5 invalidate

手動清除單個緩存

cache.invalidate("key1");

4.6 invalidateAll

手動批量清除多個key

// 批量清除多個key
cache.invalidateAll(Arrays.asList("key1", "key2"));

手動清除所有緩存

// 清除所有緩存
cache.invalidateAll();

??注意:

這些方法會立即從緩存中移除指定的條目。

Caffeine除了手動清除外,也和Redis一樣,有自動清除策略。這些將在下一張集中講解。

五、構(gòu)建一個更加全面的緩存

前面我們演示時,通過Caffeine.newBuilder().build();就建完了緩存對象,頂多給它設(shè)置了一個過期時間。

但是關(guān)于這個緩存對象本身,還有很多東西是可以設(shè)置的,下面我們就詳細(xì)說說,還有哪些設(shè)置。

Caffeine.newBuilder() 提供了豐富的配置選項(xiàng),可以創(chuàng)建高性能、靈活的緩存實(shí)例。以下是主要的可配置內(nèi)容:

5.1、容量控制配置

(1)??initialCapacity(int)??

設(shè)置初始緩存容量

示例:.initialCapacity(100)表示初始能存儲100個緩存對象

(2)??maximumSize(long)?? 

按條目數(shù)量限制緩存大小

示例:.maximumSize(1000)表示最多緩存1000個條目

(3)??maximumWeight(long)??

按自定義權(quán)重總和限制緩存大小

需要配合weigher()使用

示例:.maximumWeight(10000).weigher((k,v) -> ((User)v).getSize())

注意:maximumSize和maximumWeight不能同時使用

當(dāng)緩存條目數(shù)超過最大設(shè)定值時,Caffeine會根據(jù)Window TinyLFU算法自動清除"最不常用"的條目

5.2、過期策略配置

(1)expireAfterAccess(long, TimeUnit)??

設(shè)置最后訪問后過期時間

示例:.expireAfterAccess(5, TimeUnit.MINUTES)

(2)??expireAfterWrite(long, TimeUnit)?

設(shè)置創(chuàng)建/更新后過期時間

示例:.expireAfterWrite(10, TimeUnit.MINUTES)

?(3)?expireAfter(Expiry)??

自定義過期策略

可以基于創(chuàng)建、更新、讀取事件分別設(shè)置

.expireAfter(new Expiry<String, Object>() {
    public long expireAfterCreate(String key, Object value, long currentTime) {
        return TimeUnit.HOURS.toNanos(1); // 創(chuàng)建1小時后過期
    }
    public long expireAfterUpdate(String key, Object value, long currentTime, long currentDuration) {
        return currentDuration; // 保持原過期時間
    }
    public long expireAfterRead(String key, Object value, long currentTime, long currentDuration) {
        return currentDuration; // 保持原過期時間
    }
})

5.3 注意事項(xiàng)

Caffeine的清除操作通常是異步執(zhí)行的,如果需要立即清理所有過期條目,可以調(diào)用:

cache.cleanUp();

這個方法會觸發(fā)一次完整的緩存清理,包括所有符合條件的過期條目。

六、整合Spring Cache

前面介紹時說了,Caffeine作為Spring體系中內(nèi)置的緩存之一,Spring Cache同樣提供調(diào)用接口支持。所以接下來,我們詳細(xì)實(shí)現(xiàn)整合過程。

6.1 引入依賴

<!--  caffeine    -->
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
</dependency>
<!-- cache -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

6.2 配置文件

@Configuration
public class CacheConfig {
    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setCaffeine(Caffeine.newBuilder()
            .initialCapacity(100)          // 初始容量
            .maximumSize(500)             // 最大緩存條目數(shù)
            .expireAfterWrite(10, TimeUnit.MINUTES) // 寫入后10分鐘過期
            .expireAfterAccess(5, TimeUnit.MINUTES) // 訪問后5分鐘過期
            .weakKeys()                   // 使用弱引用鍵
            .recordStats());              // 記錄統(tǒng)計(jì)信息
        return cacheManager;
    }
}

6.3 使用

具體使用方法可以參考前面寫的這篇文章Spring Cache用法很簡單,但你知道這中間的坑嗎?

springcache無非就是那幾個注解。這里淺淺舉例演示

@RestController
@RequestMapping("/api")
public class Controller {
    @GetMapping("test")
    @Cacheable(value = "demo")
    public User test() {
        System.out.println("-----------------------");
        return new User("張三", "123456@qq.com", "abc123", 18);
    }
}

多次刷新,idea控制臺也僅僅打印了一次---------------------------

說明緩存生效了!

七、生產(chǎn)環(huán)境注意事項(xiàng)

提到緩存,那就是老生常談的:緩存穿透、緩存擊穿和緩存雪崩等問題。

緩存穿透防護(hù)??:

  • 對null值進(jìn)行適當(dāng)緩存(使用unless = "#result == null"
  • 考慮使用Bloom過濾器

??緩存雪崩防護(hù)??:

  • 為不同緩存設(shè)置不同的過期時間
  • 添加隨機(jī)抖動因子到過期時間

??緩存一致性??:

  • 重要數(shù)據(jù)建議配合數(shù)據(jù)庫事務(wù)
  • 考慮使用@CachePut更新策略

??內(nèi)存管理??:

  • 合理設(shè)置maximumSize防止OOM
  • 對大對象考慮使用weakValues()softValues()

??分布式環(huán)境??:

  • 本地緩存需要配合消息總線實(shí)現(xiàn)多節(jié)點(diǎn)同步
  • 或考慮使用多級緩存(本地+Redis)

八、實(shí)現(xiàn)Caffeine與Redis多級緩存完整策略(待完善)

在現(xiàn)代高并發(fā)系統(tǒng)中,多級緩存架構(gòu)已成為提升系統(tǒng)性能的關(guān)鍵手段。Spring Cache通過抽象緩存接口,結(jié)合Caffeine(一級緩存)和Redis(二級緩存),可以構(gòu)建高效的多級緩存解決方案。

到此這篇關(guān)于Caffeine本地緩存核心原理與使用方法詳解(含與Spring Cache集成)的文章就介紹到這了,更多相關(guān)Caffeine本地緩存內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 簡單快速對@RequestParam聲明的參數(shù)作校驗(yàn)操作

    簡單快速對@RequestParam聲明的參數(shù)作校驗(yàn)操作

    這篇文章主要介紹了簡單快速對@RequestParam聲明的參數(shù)作校驗(yàn)操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • Mybatis update數(shù)據(jù)庫死鎖之獲取數(shù)據(jù)庫連接池等待

    Mybatis update數(shù)據(jù)庫死鎖之獲取數(shù)據(jù)庫連接池等待

    這篇文章主要介紹了Mybatis update數(shù)據(jù)庫死鎖之獲取數(shù)據(jù)庫連接池等待的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2016-07-07
  • kettle中使用js調(diào)用java類的方法

    kettle中使用js調(diào)用java類的方法

    這篇文章主要介紹了kettle中使用js調(diào)用java類的方法,本文講解了注意事項(xiàng)和調(diào)用語法,需要的朋友可以參考下
    2015-05-05
  • 詳解spring security之httpSecurity使用示例

    詳解spring security之httpSecurity使用示例

    這篇文章主要介紹了詳解spring security之httpSecurity使用示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-08-08
  • 詳解Java設(shè)計(jì)模式之橋接模式

    詳解Java設(shè)計(jì)模式之橋接模式

    橋接,顧名思義,就是用來連接兩個部分,使得兩個部分可以互相通訊。橋接模式將系統(tǒng)的抽象部分與實(shí)現(xiàn)部分分離解耦,使他們可以獨(dú)立的變化。本文通過示例詳細(xì)介紹了橋接模式的原理與使用,需要的可以參考一下
    2022-10-10
  • SpringBoot實(shí)現(xiàn)api加密的示例代碼

    SpringBoot實(shí)現(xiàn)api加密的示例代碼

    在項(xiàng)目中,為了保證數(shù)據(jù)的安全,我們常常會對傳遞的數(shù)據(jù)進(jìn)行加密。本文主要介紹了SpringBoot實(shí)現(xiàn)api加密的示例代碼,感興趣的小伙伴們可以參考一下
    2021-07-07
  • Spring中事務(wù)用法示例及實(shí)現(xiàn)原理詳解

    Spring中事務(wù)用法示例及實(shí)現(xiàn)原理詳解

    這篇文章主要給大家介紹了關(guān)于Spring中事務(wù)用法示例及實(shí)現(xiàn)原理的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-11-11
  • 100行java寫的微信跳一跳輔助程序

    100行java寫的微信跳一跳輔助程序

    本篇文章給大家分享了用java寫的一個微信跳一跳輔助腳本程序,有興趣的朋友參考學(xué)習(xí)下。
    2018-01-01
  • Java簡單模擬實(shí)現(xiàn)一個線程池

    Java簡單模擬實(shí)現(xiàn)一個線程池

    本文主要介紹了Java簡單模擬實(shí)現(xiàn)一個線程池,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-01-01
  • 一個合格JAVA軟件工程師應(yīng)該具備什么

    一個合格JAVA軟件工程師應(yīng)該具備什么

    一個合格JAVA軟件工程師應(yīng)該具備哪些專業(yè)技能,面試技巧是什么?本文為大家分享了2016版最新Java軟件工程師就業(yè)思維圖,感興趣的小伙伴們可以參考一下
    2016-11-11

最新評論

定兴县| 石狮市| 天柱县| 克山县| 交城县| 芦山县| 新密市| 库尔勒市| 泗阳县| 同德县| 浠水县| 库尔勒市| 武安市| 广宗县| 镇远县| 扶余县| 会东县| 永定县| 布拖县| 承德县| 林周县| 故城县| 芜湖县| 博爱县| 乡宁县| 济阳县| 喀喇| 虎林市| 伊吾县| 丰宁| 介休市| 陵川县| 北安市| 凤阳县| 商丘市| 尼勒克县| 新平| 旌德县| 长治县| 石景山区| 永寿县|