標(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)目基本不用