多級緩存實(shí)現(xiàn)(本地緩存 Caffeine/Guava + 遠(yuǎn)程 Redis)

標(biāo)準(zhǔn)結(jié)構(gòu)

請求 → 本地緩存(Caffeine)
→ 有 → 直接返回
→ 無 → 查 Redis
→ 有 → 寫回本地緩存 → 返回
→ 無 → 查 DB → 寫 Redis + 寫本地緩存

實(shí)現(xiàn)步驟

1. 引入本地緩存(Caffeine 最強(qiáng))

<dependency>
    <groupId>com.github.benmanes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>2.9.2</version>
</dependency>

2. 構(gòu)建本地緩存

// 本地緩存:5分鐘過期,最大10000個(gè)key
public static final LoadingCache<String, Object> LOCAL_CACHE = Caffeine.newBuilder()
    .expireAfterWrite(5, TimeUnit.MINUTES)  // 本地緩存5分鐘
    .maximumSize(10000)
    .build(key -> null);

3.實(shí)現(xiàn)

public Object get(String key) {
    // ========== 1. 先讀本地緩存 ==========
    Object obj = LOCAL_CACHE.getIfPresent(key);
    if (obj != null) {
        return obj;
    }

    // ========== 2. 再讀 Redis ==========
    obj = redisTemplate.opsForValue().get(key);
    if (obj != null) {
        // 寫回本地緩存
        LOCAL_CACHE.put(key, obj);
        return obj;
    }

    // ========== 3. 互斥鎖,防止擊穿 ==========
    RLock lock = redissonClient.getLock("lock:" + key);
    try {
        lock.lock(3, TimeUnit.SECONDS);

        // 雙重檢查
        obj = redisTemplate.opsForValue().get(key);
        if (obj != null) {
            LOCAL_CACHE.put(key, obj);
            return obj;
        }

        // ========== 4. 查詢 DB ==========
        obj = dbMapper.selectById(key);

        // ========== 5. 防雪崩核心:TTL 加隨機(jī)值 ==========
        long ttl = 60 * 60 + new Random().nextInt(600); // 1小時(shí) ±10分鐘

        // 熱點(diǎn)key直接永不過期
        if (isHotKey(key)) {
            redisTemplate.opsForValue().set(key, obj);
        } else {
            redisTemplate.opsForValue().set(key, obj, ttl, TimeUnit.SECONDS);
        }

        // 寫本地緩存
        LOCAL_CACHE.put(key, obj);

    } finally {
        lock.unlock();
    }

    return obj;
}

數(shù)據(jù)庫緩存(默認(rèn)開啟)

以 MySQL 為例,主要包括:
InnoDB Buffer Pool(緩沖池)
查詢緩存(MySQL 8.0 已去掉)
磁盤數(shù)據(jù)頁緩存、索引緩存
它的特點(diǎn):
在 MySQL 進(jìn)程內(nèi)部
緩存的是:數(shù)據(jù)頁、索引頁
作用:減少磁盤 IO,讓重復(fù)查詢更快
對應(yīng)用透明,你代碼完全感知不到
只要你查庫,它就自動生效
此處注意與常規(guī)的數(shù)據(jù)庫持久化落盤區(qū)別。

此處甄別MyBatis 一級緩存,二級緩存

一級緩存 = SqlSession 級別的緩存
特點(diǎn):
在 你的 Java 應(yīng)用進(jìn)程內(nèi)
默認(rèn)開啟
同一個(gè) SqlSession(同一次請求 / 同一個(gè)事務(wù))
相同 SQL + 參數(shù) → 直接返回緩存,不查庫
事務(wù)提交 / SqlSession 關(guān)閉,緩存就清空
適用:
一次請求里多次查同一條數(shù)據(jù)
范圍極小,基本不會有臟數(shù)據(jù)問題
二級緩存 = Mapper 級別的全局緩存(跨 SqlSession,整個(gè)應(yīng)用共享)

特點(diǎn):
默認(rèn)關(guān)閉,需要手動配置開啟
緩存存在 應(yīng)用內(nèi)存
多個(gè)請求、多個(gè)線程共用
增刪改會自動清空該表緩存
問題很大:
分布式下極易臟數(shù)據(jù)
高并發(fā)非常不安全
實(shí)際項(xiàng)目基本不用

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容