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

Java 緩存框架 Caffeine 應(yīng)用場景解析

 更新時間:2025年09月24日 10:41:43   作者:祈禱蒼天賜我java之術(shù)  
文章介紹Caffeine作為高性能Java本地緩存框架,基于W-TinyLFU算法,支持異步加載、靈活過期策略、內(nèi)存安全機(jī)制及統(tǒng)計監(jiān)控,重點(diǎn)解析其核心組件、高級特性與使用場景,感興趣的朋友跟隨小編一起看看吧

一、Caffeine 簡介

1. 框架概述

Caffeine是由Google工程師Ben Manes開發(fā)的一款Java本地緩存框架,其初始版本發(fā)布于2014年。該框架的設(shè)計靈感來源于Guava Cache,但在性能和功能方面進(jìn)行了革命性的優(yōu)化。Caffeine基于"W-TinyLFU"(Window-Tiny Least Frequently Used)算法實(shí)現(xiàn),這是一種改進(jìn)的LFU緩存淘汰算法,結(jié)合了LFU的高命中率優(yōu)勢和LRU的時效性特點(diǎn)。

1.1 Caffeine的核心優(yōu)勢

1.1.1 超高性能

Caffeine在性能方面實(shí)現(xiàn)了質(zhì)的飛躍:

  • 基準(zhǔn)測試顯示,相比Guava Cache,Caffeine的讀性能提升約8-12倍,寫性能提升約5-10倍
  • 支持每秒數(shù)百萬次(典型值300-500萬QPS)的緩存操作
  • 采用無鎖并發(fā)設(shè)計,大幅減少線程競爭(如使用并發(fā)哈希表和非阻塞隊列)
  • 對JVM的內(nèi)存模型進(jìn)行了深度優(yōu)化,減少緩存行偽共享問題
1.1.2 靈活的過期策略

Caffeine提供三種核心過期策略:

  1. 寫入后過期:通過expireAfterWrite設(shè)置,例如:
    Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).build();
  2. 訪問后過期:通過expireAfterAccess設(shè)置,適合熱點(diǎn)數(shù)據(jù)場景
  3. 自定義過期:通過expireAfter方法實(shí)現(xiàn)基于業(yè)務(wù)邏輯的復(fù)雜過期判斷
1.1.3 異步支持

Caffeine提供完整的異步緩存(AsyncCache)支持:

  • 異步加載機(jī)制:通過AsyncLoadingCache實(shí)現(xiàn)非阻塞式數(shù)據(jù)加載
  • 支持CompletableFuture:可與Java8的異步編程模型完美結(jié)合
  • 典型應(yīng)用場景:高并發(fā)環(huán)境下的微服務(wù)接口緩存
1.1.4 豐富的監(jiān)聽器

Caffeine提供完善的監(jiān)控支持:

  • 移除監(jiān)聽器(RemovalListener):可監(jiān)聽緩存項的驅(qū)逐、失效或手動移除
  • 統(tǒng)計功能:通過recordStats()開啟命中率統(tǒng)計
  • 典型配置:
    cache.recordStats();
    CacheStats stats = cache.stats();
    double hitRate = stats.hitRate();
1.1.5 內(nèi)存安全

Caffeine提供多種內(nèi)存保護(hù)機(jī)制:

  1. 基于容量:通過maximumSize限制緩存項數(shù)量
  2. 基于時間:通過上述過期策略控制
  3. 基于引用:支持弱引用鍵/值(weakKeys/weakValues)和軟引用值(softValues)
  4. 權(quán)重控制:通過weighermaximumWeight實(shí)現(xiàn)基于對象大小的精確控制

典型內(nèi)存安全配置示例:

Caffeine.newBuilder()
    .maximumSize(10_000)
    .weigher((String key, String value) -> value.length())
    .maximumWeight(50_000_000) // ~50MB
    .build();

二、Caffeine 基礎(chǔ)

在使用 Caffeine 前,需先引入依賴,并了解其核心組件的作用。

2.1 依賴引入(Maven/Gradle)

Caffeine 的最新版本可在 Maven 中央倉庫查詢(https://mvnrepository.com/artifact/com.github.ben-manes.caffeine/caffeine)

Maven 配置示例(含注釋說明)

<!-- Caffeine核心依賴(必選) -->
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>3.2.7</version>  <!-- 截至2024年1月最新穩(wěn)定版 -->
    <!-- 建議通過dependencyManagement統(tǒng)一管理版本 -->
</dependency>
<!-- 異步支持依賴(可選) -->
<!-- 當(dāng)需要配合Java11+的HttpClient實(shí)現(xiàn)異步緩存加載時添加 -->
<dependency>
    <groupId>java.net.http</groupId>
    <artifactId>http-client</artifactId>
    <version>11.0.1</version>  <!-- 最低要求JDK11 -->
    <scope>runtime</scope>    <!-- 通常只需運(yùn)行時依賴 -->
</dependency>

Gradle 配置示例(Kotlin DSL)

dependencies {
    // 核心實(shí)現(xiàn)(必選)
    implementation("com.github.ben-manes.caffeine:caffeine:3.2.7")
    // 異步支持(可選)
    runtimeOnly("java.net.http:http-client:11.0.1") {
        because("For async cache loading with HTTP requests")
    }
}

2.2 核心組件解析

Caffeine 的核心組件采用分層設(shè)計,主要分為基礎(chǔ)緩存接口和功能擴(kuò)展接口兩大類:

1.基礎(chǔ)緩存接口層次結(jié)構(gòu)

Cache (基本功能)
├── LoadingCache (同步加載)
└── AsyncCache (異步基礎(chǔ))
    └── AsyncLoadingCache (異步加載)

2.關(guān)鍵組件詳細(xì)說明(含典型應(yīng)用場景)

組件作用說明典型使用場景示例代碼片段
Cache<K,V>手動管理緩存,需顯式處理緩存未命中簡單緩存場景,數(shù)據(jù)源訪問成本較低cache.get(key, k -> fetchFromDB(k))
LoadingCache<K,V>自動加載緩存,內(nèi)置CacheLoader高頻訪問且加載邏輯固定的場景LoadingCache.from(this::loadFromAPI)
AsyncCache<K,V>返回CompletableFuture的異步接口配合非阻塞IO或遠(yuǎn)程調(diào)用cache.get(key).thenAccept(value -> ...)
AsyncLoadingCache<K,V>異步自動加載緩存微服務(wù)間數(shù)據(jù)緩存AsyncLoadingCache.from(this::asyncLoad)
CacheLoader<K,V>定義加載邏輯的函數(shù)式接口統(tǒng)一數(shù)據(jù)加載策略new CacheLoader<>() { @Override public V load(K key)... }
RemovalListener<K,V>移除事件監(jiān)聽器緩存一致性維護(hù)、監(jiān)控統(tǒng)計listener((key,value,reason) -> logRemoval())
Expiry<K,V>細(xì)粒度過期控制動態(tài)TTL場景(如會話緩存)expireAfter((key,value,currentTime) -> customTTL)

3.高級特性支持

  • 權(quán)重計算:通過weigher接口實(shí)現(xiàn)基于緩存對象大小的淘汰策略
  • 刷新機(jī)制refreshAfterWrite配合CacheLoader.reload實(shí)現(xiàn)后臺刷新
  • 統(tǒng)計監(jiān)控recordStats()啟用命中率等統(tǒng)計指標(biāo)
  • 線程模型:默認(rèn)使用ForkJoinPool.commonPool(),可通過executor自定義

4.最佳實(shí)踐提示:

  1. 對于長時間加載操作,優(yōu)先選擇AsyncLoadingCache避免阻塞
  2. 移除監(jiān)聽器不要執(zhí)行耗時操作,否則會影響緩存性能
  3. 在Spring環(huán)境中建議通過@Bean配置全局緩存管理器
  4. 生產(chǎn)環(huán)境務(wù)必啟用統(tǒng)計功能(recordStats)進(jìn)行監(jiān)控

三、Caffeine 核心用法

Caffeine 的使用流程遵循 "構(gòu)建器模式配置 → 創(chuàng)建緩存實(shí)例 → 讀寫緩存" 的邏輯,下面分場景講解具體用法。

3.1 基礎(chǔ)緩存(Cache):手動控制讀寫

Cache 是最基礎(chǔ)的緩存類型,需手動處理緩存未命中(未命中時返回 null),適合緩存邏輯簡單的場景。

3.1.1 創(chuàng)建 Cache 實(shí)例

通過 Caffeine.newBuilder() 配置緩存規(guī)則,常見配置包括:

  • 容量控制
    • maximumSize(long):設(shè)置緩存最大容量(條目數(shù)),超過后按 W-TinyLFU 算法淘汰。
    • maximumWeight(long) + weigher(Weigher):基于權(quán)重控制緩存大小,適合不同條目占用不同內(nèi)存的場景。
  • 過期策略
    • expireAfterWrite(Duration):寫入后過期(如 10 分鐘未更新則過期),適合數(shù)據(jù)變更頻繁的場景。
    • expireAfterAccess(Duration):訪問后過期(如 5 分鐘未訪問則過期),適合熱點(diǎn)數(shù)據(jù)緩存。
    • expireAfter(Expiry):自定義過期時間計算邏輯,可實(shí)現(xiàn)基于業(yè)務(wù)規(guī)則的過期。
  • 監(jiān)聽器
    • removalListener(RemovalListener):設(shè)置緩存移除監(jiān)聽器,可記錄日志或觸發(fā)后續(xù)操作。
  • 其他特性
    • weakKeys()/weakValues():使用弱引用,允許被垃圾回收。
    • softValues():使用軟引用,在內(nèi)存不足時被回收。
    • recordStats():啟用統(tǒng)計信息收集。
import com.github.ben-manes.caffeine.cache.Caffeine;
import com.github.ben-manes.caffeine.cache.Cache;
import java.util.concurrent.TimeUnit;
public class CaffeineBasicDemo {
    public static void main(String[] args) {
        // 1. 配置并創(chuàng)建Cache實(shí)例(帶詳細(xì)注釋)
        Cache<String, String> userCache = Caffeine.newBuilder()
            .maximumSize(1000) // 最大容量1000條
            .expireAfterWrite(10, TimeUnit.MINUTES) // 寫入后10分鐘過期
            .expireAfterAccess(5, TimeUnit.MINUTES) // 訪問后5分鐘過期(優(yōu)先級低于expireAfterWrite)
            .removalListener((key, value, cause) -> { // 緩存移除監(jiān)聽器
                System.out.printf("緩存移除:key=%s, value=%s, 原因=%s%n",
                    key, value, cause.toString());
                // 原因可能是:EXPLICIT(手動刪除)、REPLACED(值被替換)、
                // COLLECTED(垃圾回收)、EXPIRED(過期)、SIZE(超過容量限制)
            })
            .recordStats() // 啟用統(tǒng)計
            .build(); // 構(gòu)建Cache實(shí)例
        // 2. 寫入緩存(多種方式)
        userCache.put("user:1001", "張三"); // 常規(guī)put
        userCache.asMap().putIfAbsent("user:1002", "李四"); // 線程安全寫入
        // 3. 讀取緩存(未命中返回null)
        String user1 = userCache.getIfPresent("user:1001");
        System.out.println("讀取user:1001:" + user1); // 輸出:張三
        // 4. 讀取并計算(未命中時執(zhí)行函數(shù)邏輯,但不自動存入緩存)
        String user3 = userCache.get("user:1003", key -> {
            // 模擬從數(shù)據(jù)庫查詢數(shù)據(jù)(僅當(dāng)緩存未命中時執(zhí)行)
            System.out.println("緩存未命中,查詢DB:" + key);
            return "王五"; // 此結(jié)果不會自動存入緩存
        });
        System.out.println("讀取user:1003:" + user3); // 輸出:王五
        // 5. 緩存維護(hù)操作
        userCache.invalidate("user:1002"); // 單個刪除
        userCache.invalidateAll(List.of("user:1001", "user:1003")); // 批量刪除
        userCache.cleanUp(); // 手動觸發(fā)清理過期條目
        userCache.invalidateAll(); // 清空所有緩存
        // 6. 查看統(tǒng)計信息(需先啟用recordStats)
        System.out.println("命中率:" + userCache.stats().hitRate());
    }
}

3.1.2 應(yīng)用場景示例

  • 簡單KV緩存
    • 緩存用戶Session信息
    • 緩存系統(tǒng)配置項
    • 臨時數(shù)據(jù)存儲(如驗(yàn)證碼)
  • 配合Spring Cache
@Configuration
@EnableCaching
public class CacheConfig {
    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager manager = new CaffeineCacheManager();
        manager.setCaffeine(Caffeine.newBuilder()
            .maximumSize(1000)
            .expireAfterWrite(10, TimeUnit.MINUTES));
        return manager;
    }
}

多級緩存

// 作為本地緩存與Redis組成二級緩存
public class MultiLevelCache {
    private final Cache<String, Object> localCache;
    private final RedisTemplate<String, Object> redisTemplate;
    public Object get(String key) {
        Object value = localCache.getIfPresent(key);
        if (value == null) {
            value = redisTemplate.opsForValue().get(key);
            if (value != null) {
                localCache.put(key, value);
            }
        }
        return value;
    }
}

3.2 加載緩存(LoadingCache):自動加載未命中數(shù)據(jù)

LoadingCache 是 Cache 的子類,通過實(shí)現(xiàn) CacheLoader 接口,實(shí)現(xiàn) "緩存未命中時自動加載數(shù)據(jù)并存入緩存",適合緩存數(shù)據(jù)需從數(shù)據(jù)源(如 DB、Redis)加載的場景。

3.2.1 創(chuàng)建 LoadingCache 實(shí)例

import com.github.ben-manes.caffeine.cache.Caffeine;
import com.github.ben-manes.caffeine.cache.LoadingCache;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.List;
public class CaffeineLoadingDemo {
    public static void main(String[] args) throws ExecutionException {
        // 1. 實(shí)現(xiàn)CacheLoader:定義緩存未命中時的加載邏輯
        LoadingCache<String, String> productCache = Caffeine.newBuilder()
            .maximumSize(500)
            .expireAfterWrite(30, TimeUnit.MINUTES)
            .refreshAfterWrite(10, TimeUnit.MINUTES) // 10分鐘后刷新(不阻塞讀?。?
            .recordStats()
            .build(new CacheLoader<String, String>() {
                @Override
                public String load(String key) throws Exception {
                    // 模擬從數(shù)據(jù)庫加載數(shù)據(jù)(緩存未命中時自動執(zhí)行)
                    System.out.println("緩存未命中,從DB加載:" + key);
                    if (key.startsWith("prod:")) {
                        return "商品-" + key.substring(5); // 如key=prod:101 → 商品-101
                    }
                    throw new IllegalArgumentException("Invalid key format");
                }
                // 可選:實(shí)現(xiàn)批量加載(提升getAll性能)
                @Override
                public Map<String, String> loadAll(Iterable<? extends String> keys) {
                    System.out.println("批量加載keys:" + keys);
                    // 實(shí)際應(yīng)從DB批量查詢
                    Map<String, String> result = new HashMap<>();
                    for (String key : keys) {
                        result.put(key, "商品-" + key.substring(5));
                    }
                    return result;
                }
            });
        // 2. 讀取緩存(未命中時自動調(diào)用load()加載并存入緩存)
        String product1 = productCache.get("prod:101"); // 首次:加載并返回
        System.out.println("讀取prod:101:" + product1); // 輸出:商品-101
        // 3. 批量讀?。╣etAll())
        Map<String, String> products = productCache.getAll(List.of("prod:102", "prod:103"));
        System.out.println("批量讀取結(jié)果:" + products);
        // 4. 主動刷新(異步)
        productCache.refresh("prod:101"); // 后臺刷新,舊值仍可用
        // 5. 統(tǒng)計信息
        System.out.println("加載次數(shù):" + productCache.stats().loadCount());
    }
}

3.2.2 關(guān)鍵特性:刷新(Refresh)與過期(Expire)的區(qū)別

特性刷新(Refresh)過期(Expire)
觸發(fā)時機(jī)刷新時間到后過期時間到后
讀取行為異步刷新,立即返回舊值同步重新加載,可能阻塞請求
適用場景數(shù)據(jù)允許短暫不一致(如商品詳情)數(shù)據(jù)強(qiáng)一致要求(如訂單狀態(tài))
實(shí)現(xiàn)方式需配置refreshAfterWrite配置expireAfterWrite/AfterAccess

典型使用模式

// 商品詳情緩存:10分鐘強(qiáng)制過期,5分鐘自動刷新
LoadingCache<String, Product> productCache = Caffeine.newBuilder()
    .maximumSize(10000)
    .expireAfterWrite(10, TimeUnit.MINUTES) // 強(qiáng)制過期時間
    .refreshAfterWrite(5, TimeUnit.MINUTES) // 自動刷新時間
    .build(this::loadProductFromDB);

3.3 異步緩存(AsyncCache/AsyncLoadingCache):非阻塞讀寫

在高并發(fā)場景下,同步緩存的 load() 可能會阻塞線程,而 AsyncCache 通過返回 CompletableFuture 實(shí)現(xiàn)非阻塞操作,所有 IO 操作均在異步線程池中執(zhí)行。

3.3.1 創(chuàng)建 AsyncLoadingCache 實(shí)例

import com.github.ben-manes.caffeine.cache.AsyncLoadingCache;
import com.github.ben-manes.caffeine.cache.Caffeine;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class CaffeineAsyncDemo {
    public static void main(String[] args) throws Exception {
        // 1. 自定義線程池(生產(chǎn)環(huán)境建議使用有界隊列和拒絕策略)
        Executor executor = Executors.newFixedThreadPool(5, r -> {
            Thread thread = new Thread(r);
            thread.setName("caffeine-async-" + thread.getId());
            return thread;
        });
        // 2. 創(chuàng)建AsyncLoadingCache實(shí)例
        AsyncLoadingCache<String, String> orderCache = Caffeine.newBuilder()
            .maximumSize(1000)
            .expireAfterWrite(15, TimeUnit.MINUTES)
            .executor(executor) // 指定異步線程池
            .buildAsync(key -> {
                // 模擬耗時操作(如RPC調(diào)用,耗時200ms)
                TimeUnit.MILLISECONDS.sleep(200);
                System.out.println(Thread.currentThread().getName() + " 加載訂單:" + key);
                return "訂單-" + key.substring(6); // 如key=order:2024 → 訂單-2024
            });
        // 3. 異步讀取(推薦方式)
        CompletableFuture<String> future = orderCache.get("order:2024");
        future.thenApplyAsync(order -> {
            System.out.println("處理訂單數(shù)據(jù):" + order);
            return order.toUpperCase();
        }, executor); // 使用相同線程池處理結(jié)果
        // 4. 批量讀?。ǚ祷豈ap<Key, CompletableFuture>)
        Map<String, CompletableFuture<String>> futures = 
            orderCache.getAll(List.of("order:2025", "order:2026"));
        // 5. 同步獲?。▋H測試用,實(shí)際應(yīng)避免)
        String order = orderCache.get("order:2027").get();
        System.out.println("同步獲取結(jié)果:" + order);
    }
}

3.3.2 最佳實(shí)踐

線程池配置

// 更完善的線程池配置
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    5, // 核心線程數(shù)
    10, // 最大線程數(shù)
    60, TimeUnit.SECONDS, // 空閑線程存活時間
    new LinkedBlockingQueue<>(1000), // 有界隊列
    new ThreadFactoryBuilder().setNameFormat("cache-loader-%d").build(),
    new ThreadPoolExecutor.CallerRunsPolicy() // 拒絕策略
);

異常處理

orderCache.get("badKey").exceptionally(ex -> {
    System.err.println("加載失敗: " + ex.getMessage());
    return "defaultValue";
});

結(jié)合Spring使用

@Cacheable(value = "orders", cacheManager = "asyncCacheManager")
public CompletableFuture<Order> getOrderAsync(String orderId) {
    return CompletableFuture.supplyAsync(() -> orderService.loadOrder(orderId));
}

性能監(jiān)控

CacheStats stats = orderCache.synchronous().stats();
System.out.println("平均加載時間:" + stats.averageLoadPenalty() + "ns");

四、Caffeine 高級特性

4.1 緩存統(tǒng)計(Cache Statistics)

緩存統(tǒng)計功能是優(yōu)化緩存性能的重要工具。通過開啟緩存統(tǒng)計,可以實(shí)時監(jiān)控以下關(guān)鍵指標(biāo):

  1. 命中率(Hit Rate):反映緩存有效性,計算公式為:命中次數(shù)/(命中次數(shù)+未命中次數(shù))
  2. 加載耗時(Load Penalty):統(tǒng)計從數(shù)據(jù)源加載數(shù)據(jù)的平均耗時
  3. 移除次數(shù)(Eviction Count):因容量或過期策略導(dǎo)致的緩存移除次數(shù)
  4. 加載失敗率(Load Failure Rate):數(shù)據(jù)源加載失敗的比例

典型應(yīng)用場景:

  • 評估緩存配置是否合理
  • 識別熱點(diǎn)數(shù)據(jù)
  • 監(jiān)控緩存性能瓶頸
import com.github.ben-manes.caffeine.cache.CacheStats;
public class CaffeineStatsDemo {
    public static void main(String[] args) {
        LoadingCache<String, String> statsCache = Caffeine.newBuilder()
            .maximumSize(100)
            .recordStats() // 必須顯式開啟統(tǒng)計功能
            .build(key -> {
                // 模擬耗時加載
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                return "統(tǒng)計測試:" + key;
            });
        // 模擬讀寫操作
        statsCache.get("key1");  // 第一次加載(未命中)
        statsCache.get("key1");  // 命中已有緩存
        statsCache.get("key2");  // 新鍵加載
        statsCache.invalidate("key1"); // 手動失效
        // 獲取統(tǒng)計結(jié)果
        CacheStats stats = statsCache.stats();
        System.out.println("緩存命中率:" + stats.hitRate());  // 50%(1次命中/2次查詢)
        System.out.println("加載成功次數(shù):" + stats.loadSuccessCount()); // 2次加載
        System.out.println("移除次數(shù):" + stats.evictionCount()); // 0(未達(dá)到容量上限)
        System.out.println("平均加載耗時(ns):" + stats.averageLoadPenalty()); // 約100ms
        System.out.println("加載失敗率:" + stats.loadFailureRate()); // 0.0
    }
}

4.2 自定義過期策略(Expiry)

標(biāo)準(zhǔn)的TTL(Time-To-Live)過期策略對所有緩存條目采用統(tǒng)一設(shè)置,而自定義過期策略允許基于業(yè)務(wù)特性實(shí)現(xiàn)精細(xì)化控制。

常見應(yīng)用場景:

  • 不同優(yōu)先級數(shù)據(jù)設(shè)置不同有效期(如熱點(diǎn)數(shù)據(jù)短時效,冷數(shù)據(jù)長時效)
  • 讀寫操作影響過期時間(如讀操作續(xù)期)
  • 動態(tài)調(diào)整過期時間(如根據(jù)數(shù)據(jù)價值計算)
import com.github.ben-manes.caffeine.cache.Caffeine;
import com.github.ben-manes.caffeine.cache.Expiry;
import com.github.ben-manes.caffeine.cache.LoadingCache;
import java.util.concurrent.TimeUnit;
class CustomExpiry implements Expiry<String, String> {
    @Override
    public long expireAfterCreate(String key, String value, long currentTime) {
        // 創(chuàng)建時過期策略
        if (key.startsWith("flash:")) {    // 閃存數(shù)據(jù):30秒過期
            return TimeUnit.SECONDS.toNanos(30);
        } else if (key.startsWith("hot:")) { // 熱門數(shù)據(jù):5分鐘
            return TimeUnit.MINUTES.toNanos(5);
        } else {                            // 普通數(shù)據(jù):30分鐘
            return TimeUnit.MINUTES.toNanos(30);
        }
    }
    @Override
    public long expireAfterUpdate(String key, String value, 
            long currentTime, long currentDuration) {
        // 更新策略:保持原有過期時間(默認(rèn))
        return currentDuration;
        // 或者重置為創(chuàng)建時間:return expireAfterCreate(key, value, currentTime);
    }
    @Override
    public long expireAfterRead(String key, String value, 
            long currentTime, long currentDuration) {
        // 讀取時策略:熱門數(shù)據(jù)讀取后續(xù)期5分鐘
        if (key.startsWith("hot:")) {
            return TimeUnit.MINUTES.toNanos(5);
        }
        return currentDuration;
    }
}
public class CaffeineCustomExpiryDemo {
    public static void main(String[] args) {
        LoadingCache<String, String> customExpiryCache = Caffeine.newBuilder()
            .expireAfter(new CustomExpiry())
            .build(key -> "自定義過期:" + key);
        customExpiryCache.get("flash:news:2023");   // 30秒過期
        customExpiryCache.get("hot:product:101");   // 5分鐘且讀取續(xù)期
        customExpiryCache.get("normal:user:201");   // 30分鐘過期
    }
}

4.3 弱引用與軟引用:避免內(nèi)存溢出

Java引用類型與緩存回收策略:

引用類型GC行為適用場景Caffeine配置
強(qiáng)引用永不回收默認(rèn)方式-
軟引用內(nèi)存不足時回收緩存大對象.softValues()
弱引用下次GC時回收臨時性緩存.weakKeys()/.weakValues()

注意事項:

  1. 使用weakKeys()時,key比較基于==而非equals()
  2. softValues()可能導(dǎo)致GC壓力增大
  3. 引用回收與顯式失效策略共同作用
// 弱引用Key+Value的緩存(適合臨時性數(shù)據(jù))
Cache<String, byte[]> weakCache = Caffeine.newBuilder()
    .weakKeys()    // Key無強(qiáng)引用時回收
    .weakValues()  // Value無強(qiáng)引用時回收
    .maximumSize(10_000)  // 仍保持容量限制
    .build();
// 軟引用Value的緩存(適合大對象)
Cache<String, byte[]> softCache = Caffeine.newBuilder()
    .softValues()  // 內(nèi)存不足時回收Value
    .expireAfterWrite(1, TimeUnit.HOURS)  // 配合顯式過期
    .build();
// 典型使用場景
void processLargeData(String dataId) {
    byte[] data = softCache.get(dataId, id -> {
        // 從數(shù)據(jù)庫加載大對象(如圖片、文件等)
        return loadLargeDataFromDB(id); 
    });
    // 使用數(shù)據(jù)...
}

五、Caffeine 注意事項

在實(shí)際開發(fā)中,若使用不當(dāng),Caffeine 可能出現(xiàn)緩存穿透、內(nèi)存溢出、線程阻塞等問題,以下是核心注意事項:

5.1 區(qū)分 "刷新(Refresh)" 與 "過期(Expire)"

刷新(refreshAfterWrite):

  • 工作機(jī)制:當(dāng)緩存條目超過指定時間未被寫入時,下次讀取會觸發(fā)異步刷新,但在此期間仍會返回舊值
  • 適用場景:對數(shù)據(jù)一致性要求不高,可接受短暫延遲的場景
    • 商品詳情頁的評論數(shù)統(tǒng)計
    • 新聞資訊的閱讀量統(tǒng)計
    • 排行榜數(shù)據(jù)的更新

過期(expireAfterWrite/expireAfterAccess):

  • expireAfterWrite:從寫入開始計時
  • expireAfterAccess:從最后一次訪問開始計時
  • 工作機(jī)制:過期后緩存條目立即失效,讀取時會同步阻塞直到重新加載完成
  • 適用場景:對數(shù)據(jù)一致性要求高的核心業(yè)務(wù)
    • 用戶賬戶余額
    • 訂單支付狀態(tài)
    • 庫存數(shù)量

?? 典型誤用場景:

將用戶余額這類強(qiáng)一致性數(shù)據(jù)配置為refreshAfterWrite(5s).可能導(dǎo)致:

  1. 用戶A看到余額100元
  2. 用戶B完成扣款50元
  3. 5秒內(nèi)用戶A仍看到100元(舊值)
  4. 直到下次讀取才刷新為50元

5.2 避免緩存穿透:空值緩存與布隆過濾器

緩存穿透的典型特征:

  • 查詢一個必然不存在的數(shù)據(jù)(如不存在的用戶ID)
  • 每次請求都穿透到數(shù)據(jù)庫
  • 可能被惡意攻擊者利用,造成數(shù)據(jù)庫壓力

解決方案1:空值緩存

LoadingCache<String, String> cache = Caffeine.newBuilder()
    .expireAfterWrite(1, TimeUnit.MINUTES)  // 空值緩存1分鐘
    .build(key -> {
        String value = queryFromDB(key);
        // 特殊空值標(biāo)記,避免與真實(shí)空值混淆
        return value != null ? value : "NULL_VALUE";  
    });

解決方案2:布隆過濾器(適合千萬級key場景)

// 初始化布隆過濾器
BloomFilter<String> bloomFilter = BloomFilter.create(
    Funnels.stringFunnel(Charset.defaultCharset()), 
    1000000,  // 預(yù)期元素數(shù)量
    0.01      // 誤判率
);
// 查詢流程
if (!bloomFilter.mightContain(key)) {
    return null;  // 肯定不存在
} else {
    return cache.get(key);  // 可能存在
}

5.3 緩存鍵(Key)必須重寫 hashCode() 和 equals()

常見錯誤案例:

class CompositeKey {
    private Long id;
    private String category;
    // 缺少hashCode/equals實(shí)現(xiàn)
}
// 實(shí)際使用中
CompositeKey key1 = new CompositeKey(1L, "A");
CompositeKey key2 = new CompositeKey(1L, "A");
cache.put(key1, "value");
// 將返回null,因?yàn)閗ey2被視為不同key
cache.getIfPresent(key2); 

正確實(shí)現(xiàn)要點(diǎn):

  1. 使用Objects工具類自動生成
  2. 保證不可變(final字段)
  3. 實(shí)現(xiàn)Serializable接口(分布式緩存需要)
class CompositeKey implements Serializable {
    private final Long id;
    private final String category;
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof CompositeKey)) return false;
        CompositeKey that = (CompositeKey) o;
        return Objects.equals(id, that.id) && 
               Objects.equals(category, that.category);
    }
    @Override
    public int hashCode() {
        return Objects.hash(id, category);
    }
}

5.4 異步緩存(AsyncCache)的線程池選擇

默認(rèn)線程池的問題:

  • ForkJoinPool.commonPool()是JVM全局共享的
  • 可能被CompletableFuture等其他組件占用
  • 在容器環(huán)境中可能線程數(shù)不足

推薦配置:

ExecutorService executor = Executors.newFixedThreadPool(
    Runtime.getRuntime().availableProcessors() * 2,
    new ThreadFactoryBuilder()
        .setNameFormat("caffeine-loader-%d")
        .setDaemon(true)
        .build()
);
AsyncLoadingCache<String, String> cache = Caffeine.newBuilder()
    .executor(executor)  // 指定專屬線程池
    .buildAsync(key -> loadExpensiveValue(key));

5.5 避免內(nèi)存溢出:合理配置容量與過期時間

典型配置示例:

Caffeine.newBuilder()
    .maximumSize(10_000)  // 基于條目數(shù)限制
    .expireAfterWrite(30, TimeUnit.MINUTES)  // 寫入后30分鐘過期
    .expireAfterAccess(10, TimeUnit.MINUTES) // 10分鐘無訪問過期
    .weigher((String key, String value) -> value.length()) // 按value大小計算權(quán)重
    .maximumWeight(50_000_000)  // 約50MB內(nèi)存限制

監(jiān)控建議:

  1. 通過cache.stats()獲取命中率
  2. 使用JMX監(jiān)控緩存大小
  3. 設(shè)置告警閾值(如內(nèi)存使用>80%)

5.6 CacheLoader 的異常處理

完整異常處理方案:

LoadingCache<String, String> cache = Caffeine.newBuilder()
    .build(new CacheLoader<String, String>() {
        @Override
        public String load(String key) {
            try {
                return queryDB(key);
            } catch (SQLException e) {
                // 記錄詳細(xì)日志
                log.error("DB查詢失敗, key: {}", key, e);
                // 返回降級值
                return "DEFAULT_VALUE";
                // 或者拋出特定異常
                // throw new CacheLoadException(e);
            }
        }
    });
// 使用時的異常處理
try {
    return cache.get(key);
} catch (CacheLoaderException e) {
    // 處理加載失敗
    return processFallback(key);
} catch (Exception e) {
    // 兜底處理
    return "SYSTEM_ERROR";
}

六、常見問題

Q1:Caffeine 與 Guava Cache 的詳細(xì)區(qū)別

性能比較

Caffeine 采用了創(chuàng)新的 W-TinyLFU 緩存淘汰算法,該算法結(jié)合了 TinyLFU 和 LRU 的優(yōu)勢:

  • 使用 Count-Min Sketch 數(shù)據(jù)結(jié)構(gòu)高效統(tǒng)計訪問頻率
  • 適應(yīng)不同工作負(fù)載模式(突發(fā)性和長期性訪問)
  • 在基準(zhǔn)測試中,Caffeine 的讀寫性能比 Guava Cache 高出 10-20 倍

功能特性對比

特性CaffeineGuava Cache
異步加載支持 AsyncLoadingCache僅同步加載
過期策略支持基于大小、時間、引用等多種策略僅基本過期策略
自動刷新支持 refreshAfterWrite不支持
權(quán)重計算支持自定義權(quán)重支持但性能較差
監(jiān)聽器支持移除監(jiān)聽器支持移除監(jiān)聽器
統(tǒng)計提供命中率等詳細(xì)統(tǒng)計提供基本統(tǒng)計

兼容性與遷移

Caffeine 在設(shè)計時特別考慮了與 Guava Cache 的兼容性:

  • 90%以上的 API 可以直接替換
  • 主要差異在于構(gòu)建方式(Caffeine.newBuilder() vs CacheBuilder.newBuilder())
  • 遷移示例:
// Guava Cache
LoadingCache<Key, Value> cache = CacheBuilder.newBuilder()
    .maximumSize(1000)
    .build(new CacheLoader<Key, Value>() {
        public Value load(Key key) {
            return createValue(key);
        }
    });
// 遷移到 Caffeine
LoadingCache<Key, Value> cache = Caffeine.newBuilder()
    .maximumSize(1000)
    .build(key -> createValue(key));

Q2:Caffeine 的分布式緩存支持與多級緩存架構(gòu)

本地緩存特性

Caffeine 作為本地緩存的核心特點(diǎn):

  • 僅作用于單個 JVM 進(jìn)程內(nèi)
  • 不同服務(wù)器節(jié)點(diǎn)間的緩存數(shù)據(jù)不共享
  • 適用于高頻訪問、低變化率的數(shù)據(jù)

二級緩存架構(gòu)實(shí)現(xiàn)

典型的生產(chǎn)級緩存架構(gòu)組合:

  • 第一層:Caffeine 本地緩存(納秒級響應(yīng))
    • 設(shè)置合理的過期時間(如30秒)
    • 適合極端熱點(diǎn)數(shù)據(jù)
  • 第二層:Redis 分布式緩存(毫秒級響應(yīng))
    • 設(shè)置較長的過期時間(如5分鐘)
    • 使用Redis集群保證高可用
  • 數(shù)據(jù)源:數(shù)據(jù)庫/服務(wù)(秒級響應(yīng))
    • 最終數(shù)據(jù)一致性保障

實(shí)現(xiàn)示例

public class TwoLevelCacheService {
    private final Cache<String, Object> localCache = Caffeine.newBuilder()
        .maximumSize(10_000)
        .expireAfterWrite(30, TimeUnit.SECONDS)
        .build();
    private final RedisTemplate<String, Object> redisTemplate;
    public Object getData(String key) {
        // 1. 嘗試從本地緩存獲取
        Object value = localCache.getIfPresent(key);
        if (value != null) {
            return value;
        }
        // 2. 嘗試從Redis獲取
        value = redisTemplate.opsForValue().get(key);
        if (value != null) {
            localCache.put(key, value);
            return value;
        }
        // 3. 回源查詢
        value = queryDatabase(key);
        redisTemplate.opsForValue().set(key, value, 5, TimeUnit.MINUTES);
        localCache.put(key, value);
        return value;
    }
}

Q3:緩存擊穿解決方案的深入分析

互斥鎖方案詳解

實(shí)現(xiàn)要點(diǎn)

  1. 使用 key.intern() 獲取字符串規(guī)范表示,確保相同key鎖定同一對象
  2. 采用雙重檢查鎖定模式減少鎖競爭
  3. 設(shè)置合理的鎖等待超時時間

增強(qiáng)版實(shí)現(xiàn)

public Object getDataWithLock(String key) {
    Object value = cache.getIfPresent(key);
    if (value != null) {
        return value;
    }
    synchronized (key.intern()) {
        // 雙重檢查
        value = cache.getIfPresent(key);
        if (value != null) {
            return value;
        }
        try {
            value = queryDataSource(key);
            cache.put(key, value);
        } finally {
            // 釋放資源
        }
    }
    return value;
}

熱點(diǎn)Key永不過期方案

實(shí)現(xiàn)模式

主動更新:后臺線程定期(如每分鐘)刷新熱點(diǎn)數(shù)據(jù)

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
    List<String> hotKeys = getHotKeyList();
    hotKeys.forEach(key -> {
        Object value = queryDataSource(key);
        cache.put(key, value);
    });
}, 0, 1, TimeUnit.MINUTES);

被動更新:獲取數(shù)據(jù)時異步刷新

LoadingCache<String, Object> cache = Caffeine.newBuilder()
    .maximumSize(10_000)
    .refreshAfterWrite(1, TimeUnit.MINUTES)
    .build(key -> queryDataSource(key));

其他防護(hù)策略

  1. 布隆過濾器:前置過濾不存在的key請求
  2. 緩存預(yù)熱:系統(tǒng)啟動時加載熱點(diǎn)數(shù)據(jù)
  3. 隨機(jī)過期時間:對相同類型key設(shè)置不同的過期時間偏移量
    int baseExpire = 3600; // 基礎(chǔ)1小時
    int randomOffset = ThreadLocalRandom.current().nextInt(600); // 0-10分鐘隨機(jī)
    cache.put(key, value, baseExpire + randomOffset, TimeUnit.SECONDS);
    

相關(guān)文章

  • 解決idea爆紅 cant resolve symbol String的問題解析

    解決idea爆紅 cant resolve symbol String的問題解析

    連著出差幾個禮拜沒有使用idea開發(fā)工具,突然一天打開電腦發(fā)現(xiàn)idea里的代碼全部爆紅,懵逼不如所措,很多朋友建議我按住Alt+回車設(shè)置jdk就能解決,但是仍然報錯,經(jīng)過幾個小時的倒騰最終解決,遇到此問題的朋友參考下本文吧
    2021-06-06
  • form表單數(shù)據(jù)通過@RequestBody解析成java實(shí)體類的實(shí)現(xiàn)過程

    form表單數(shù)據(jù)通過@RequestBody解析成java實(shí)體類的實(shí)現(xiàn)過程

    解決@RequestBody請求無法接收前端數(shù)據(jù)問題:前端通過axios提交數(shù)據(jù),后端使用fastJSON解析時出現(xiàn)NULL,通過在實(shí)體類上添加@JSONField注解指定屬性對應(yīng)關(guān)系,可以成功接收數(shù)據(jù)
    2025-12-12
  • 詳解springcloud Feign的Hystrix支持

    詳解springcloud Feign的Hystrix支持

    這篇文章主要介紹了詳解springcloud Feign的Hystrix支持,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-01-01
  • spring注入在有常量的情況下使用@AllArgsConstructor操作

    spring注入在有常量的情況下使用@AllArgsConstructor操作

    這篇文章主要介紹了spring注入在有常量的情況下使用@AllArgsConstructor操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • 關(guān)于IDEA使用jsp可以訪問頁面轉(zhuǎn)換為html彈出頁面為404的問題

    關(guān)于IDEA使用jsp可以訪問頁面轉(zhuǎn)換為html彈出頁面為404的問題

    這篇文章主要介紹了關(guān)于IDEA使用jsp可以訪問頁面轉(zhuǎn)換為html彈出頁面為404的問題及解決方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-12-12
  • Spring后處理器詳細(xì)介紹

    Spring后處理器詳細(xì)介紹

    Bean后置處理器允許在調(diào)用初始化方法前后對Bean進(jìn)行額外的處理??梢栽?Spring容器通過插入一個或多個BeanPostProcessor的實(shí)現(xiàn)來完成實(shí)例化,配置和初始化一個?bean?之后實(shí)現(xiàn)一些自定義邏輯回調(diào)方法
    2023-02-02
  • springboot讀取自定義配置文件時出現(xiàn)亂碼解決方案

    springboot讀取自定義配置文件時出現(xiàn)亂碼解決方案

    這篇文章主要介紹了springboot讀取自定義配置文件時出現(xiàn)亂碼解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • Java將字符串寫入文本文件代碼示例

    Java將字符串寫入文本文件代碼示例

    這篇文章主要介紹了Java將字符串寫入文本文件代碼示例,具有一定借鑒價值,需要的朋友可以參考下。
    2017-12-12
  • java中如何停止一個正在運(yùn)行的線程

    java中如何停止一個正在運(yùn)行的線程

    文章介紹了三種停止正在運(yùn)行線程的方法:設(shè)置標(biāo)志位、利用中斷標(biāo)志位和利用InterruptedException異常,這些方法可以幫助我們有效地控制線程的執(zhí)行
    2025-01-01
  • java 內(nèi)嵌Groovy動態(tài)腳本操作

    java 內(nèi)嵌Groovy動態(tài)腳本操作

    這篇文章主要介紹了java 內(nèi)嵌Groovy動態(tài)腳本操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07

最新評論

疏附县| 略阳县| 大方县| 体育| 凌海市| 双桥区| 巩留县| 启东市| 界首市| 铜山县| 绥棱县| 滦南县| 阿坝| 板桥市| 定结县| 德庆县| 高唐县| 泰宁县| 利辛县| 怀远县| 永康市| 桓仁| 湄潭县| 龙井市| 鄱阳县| 山阴县| 府谷县| 武城县| 疏勒县| 枝江市| 新宾| 衡南县| 安丘市| 张家川| 罗田县| 苏尼特左旗| 平塘县| 嘉善县| 通江县| 伊金霍洛旗| 云龙县|