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

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

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

一、介紹

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

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

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

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

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

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

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

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

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

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ì)列管理
  • ??動(dòng)態(tài)調(diào)整??:根據(jù)訪問(wèn)模式自動(dòng)調(diào)整各區(qū)比例,最高可實(shí)現(xiàn)98%的緩存命中率
  • ??頻率衰減??:通過(guò)周期性衰減歷史頻率,防止舊熱點(diǎn)數(shù)據(jù)長(zhǎng)期占據(jù)緩存

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

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

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

  • ??寫緩沖機(jī)制??:使用RingBuffer和MpscChunkedArrayQueue實(shí)現(xiàn)多生產(chǎn)者-單消費(fèi)者隊(duì)列
  • ??樂(lè)觀鎖優(yōu)化??:通過(guò)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 測(cè)試接口

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;
    }
}

 

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

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

解決辦法

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

@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;
    }
}

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

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

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ī)一動(dòng),多定義幾個(gè)bean吧,一個(gè)設(shè)置有效期,一個(gè)永不過(guò)期。

@Configuration
public class CacheConfig {
    @Bean("noLimit")
    public Cache<String, Object> buildCache() {
        return Caffeine.newBuilder().build();
    }
    @Bean("limited")
    public Cache<String, Object> buildLimitedCache() {
        // 設(shè)置過(guò)期時(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后加入的緩存也沒(méi)有了。

3.3 小結(jié)

通過(guò)這個(gè)案例,你似乎也覺(jué)察到了,Caffeine的基本使用方法

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

四、Caffeine常用方法詳解

4.1 getIfPresent

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

前面已經(jīng)演示過(guò)了,這里就不在舉例了。意思是如果存在則獲取,不存在就是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 -> {
        // 緩存不存在時(shí),執(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);

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

4.4 putAll

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

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

4.5 invalidate

手動(dòng)清除單個(gè)緩存

cache.invalidate("key1");

4.6 invalidateAll

手動(dòng)批量清除多個(gè)key

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

手動(dòng)清除所有緩存

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

??注意:

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

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

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

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

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

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

5.1、容量控制配置

(1)??initialCapacity(int)??

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

示例:.initialCapacity(100)表示初始能存儲(chǔ)100個(gè)緩存對(duì)象

(2)??maximumSize(long)?? 

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

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

(3)??maximumWeight(long)??

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

需要配合weigher()使用

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

注意:maximumSize和maximumWeight不能同時(shí)使用

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

5.2、過(guò)期策略配置

(1)expireAfterAccess(long, TimeUnit)??

設(shè)置最后訪問(wèn)后過(guò)期時(shí)間

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

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

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

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

?(3)?expireAfter(Expiry)??

自定義過(guò)期策略

可以基于創(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小時(shí)后過(guò)期
    }
    public long expireAfterUpdate(String key, Object value, long currentTime, long currentDuration) {
        return currentDuration; // 保持原過(guò)期時(shí)間
    }
    public long expireAfterRead(String key, Object value, long currentTime, long currentDuration) {
        return currentDuration; // 保持原過(guò)期時(shí)間
    }
})

5.3 注意事項(xiàng)

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

cache.cleanUp();

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

六、整合Spring Cache

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

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分鐘過(guò)期
            .expireAfterAccess(5, TimeUnit.MINUTES) // 訪問(wèn)后5分鐘過(guò)期
            .weakKeys()                   // 使用弱引用鍵
            .recordStats());              // 記錄統(tǒng)計(jì)信息
        return cacheManager;
    }
}

6.3 使用

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

springcache無(wú)非就是那幾個(gè)注解。這里淺淺舉例演示

@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控制臺(tái)也僅僅打印了一次---------------------------

說(shuō)明緩存生效了!

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

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

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

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

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

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

??緩存一致性??:

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

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

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

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

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

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

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

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

相關(guān)文章

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

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

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

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

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

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

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

    詳解spring security之httpSecurity使用示例

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

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

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

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

    在項(xiàng)目中,為了保證數(shù)據(jù)的安全,我們常常會(huì)對(duì)傳遞的數(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)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2018-11-11
  • 100行java寫的微信跳一跳輔助程序

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

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

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

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

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

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

最新評(píng)論

三都| 饶阳县| 普格县| 昭觉县| 特克斯县| 沁阳市| 水城县| 独山县| 赤城县| 探索| 衢州市| 江源县| 司法| 太和县| 通化市| 乐东| 阆中市| 涟水县| 治多县| 安远县| 谢通门县| 黄平县| 库伦旗| 资中县| 朝阳县| 清远市| 万荣县| 胶南市| 汶川县| 满城县| 苍溪县| 奇台县| 辽源市| 扬中市| 禹州市| 和田市| 观塘区| 普兰店市| 泗水县| 轮台县| 台湾省|