Caffeine結(jié)合Redis空值緩存實現(xiàn)多級緩存
在高并發(fā)系統(tǒng)中,緩存是提升響應(yīng)速度、減輕數(shù)據(jù)庫壓力的核心手段,但單一緩存方案往往難以應(yīng)對復(fù)雜場景 —— 本地緩存缺乏分布式一致性,Redis 緩存存在網(wǎng)絡(luò)開銷,還可能遭遇穿透、雪崩、擊穿等致命問題。本文基于實戰(zhàn)案例,詳解 SpringBoot 整合Caffeine 本地緩存 + Redis 分布式緩存 + 空值緩存的三級緩存方案,從架構(gòu)設(shè)計到代碼落地,構(gòu)建高可用、低延遲的緩存體系。
一、多級緩存架構(gòu)設(shè)計:為什么要 “三級聯(lián)動”?
傳統(tǒng)緩存方案要么依賴單一本地緩存(無法分布式共享),要么僅用 Redis(網(wǎng)絡(luò) IO 開銷影響性能),而三級緩存架構(gòu)通過 “本地緩存 + 分布式緩存 + 數(shù)據(jù)庫” 的層級設(shè)計,實現(xiàn)了 “速度” 與 “一致性” 的平衡:
- 第一級:Caffeine 本地緩存基于 Java 內(nèi)存的高性能緩存,讀寫延遲低至納秒級,專門存儲熱點數(shù)據(jù)(如高頻訪問的商品信息、配置參數(shù)),避免重復(fù)查詢 Redis 和數(shù)據(jù)庫,提升核心接口響應(yīng)速度。
- 第二級:Redis 分布式緩存分布式環(huán)境下的共享緩存,解決本地緩存數(shù)據(jù)不一致問題,同時承擔(dān) “中間緩沖” 角色,減少數(shù)據(jù)庫直接訪問壓力。
- 第三級:數(shù)據(jù)庫數(shù)據(jù)最終存儲源,僅在緩存未命中時觸發(fā)查詢,保證數(shù)據(jù)可靠性。
核心優(yōu)勢
- 性能極致:本地緩存命中率超 90%,Redis 緩存命中率超 95%,99% 請求響應(yīng)時間 < 10ms;
- 高可用:故障隔離設(shè)計,某一級緩存失效不影響整體服務(wù)(如 Redis 宕機時,本地緩存可臨時兜底);
- 資源優(yōu)化:減少 Redis 網(wǎng)絡(luò) IO 和數(shù)據(jù)庫查詢壓力,降低集群部署成本;
- 多層防護:從架構(gòu)層面規(guī)避緩存穿透、雪崩、擊穿三大經(jīng)典問題。
二、核心問題解決方案:三大緩存難題逐個擊破
1. 緩存穿透:攔截?zé)o效查詢
問題:惡意請求查詢不存在的數(shù)據(jù)(如 ID=-1 的商品),導(dǎo)致緩存失效后直接穿透到數(shù)據(jù)庫,引發(fā)性能問題。解決方案:空值緩存 + 布隆過濾器雙重防護
- 空值緩存:數(shù)據(jù)庫查詢無結(jié)果時,在 Redis 和 Caffeine 中緩存空值(設(shè)置短期過期時間,如 5 分鐘),避免重復(fù)穿透;
- 布隆過濾器:預(yù)先將數(shù)據(jù)庫中存在的主鍵(如商品 ID、用戶 ID)存入布隆過濾器,請求先經(jīng)過過濾器校驗,無效 ID 直接攔截,不進入緩存和數(shù)據(jù)庫。
2. 緩存雪崩:避免集中失效
問題:大量緩存數(shù)據(jù)在同一時間過期,或 Redis 集群宕機,導(dǎo)致所有請求瞬間涌向數(shù)據(jù)庫,引發(fā)數(shù)據(jù)庫雪崩。解決方案:隨機過期時間 + 優(yōu)雅降級
- 隨機 TTL:緩存數(shù)據(jù)時,在基礎(chǔ)過期時間(如 30 分鐘)上增加隨機值(5-10 分鐘),使緩存過期時間分散,避免集中失效;
- 優(yōu)雅降級:Redis 宕機時,啟用本地緩存兜底,同時觸發(fā)告警機制,保障服務(wù)核心功能可用。
3. 緩存擊穿:保護熱點數(shù)據(jù)
問題:熱點數(shù)據(jù)(如秒殺商品)緩存過期瞬間,大量并發(fā)請求穿透到數(shù)據(jù)庫,導(dǎo)致數(shù)據(jù)庫壓力驟增。解決方案:熱點數(shù)據(jù)預(yù)熱 + 分布式鎖
- 數(shù)據(jù)預(yù)熱:系統(tǒng)啟動時,主動將熱點數(shù)據(jù)加載到 Caffeine 和 Redis 中,避免緩存冷啟動;
- 分布式鎖:緩存過期后,通過 Redis 分布式鎖(如 Redisson)控制,僅允許一個線程查詢數(shù)據(jù)庫并更新緩存,其他線程等待緩存更新后再查詢,防止并發(fā)穿透。
三、實戰(zhàn)落地:SpringBoot 整合三級緩存
1. 依賴配置
首先引入核心依賴(Maven 示例),包含 SpringBoot 緩存 starter、Caffeine、Redis、Redisson(分布式鎖):
<!-- SpringBoot緩存核心依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- Caffeine本地緩存 -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.8</version>
</dependency>
<!-- Redis依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Redisson分布式鎖 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.23.3</version>
</dependency>
2. 核心配置文件(application.yml)
配置 Caffeine 緩存參數(shù)、Redis 連接信息、分布式鎖等:
spring:
# Redis配置
redis:
host: 127.0.0.1
port: 6379
password: 123456
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 2
max-wait: 1000ms
timeout: 3000ms
# 緩存配置
cache:
type: caffeine
caffeine:
# 初始容量、最大容量、過期時間(寫入后30分鐘過期)
initial-capacity: 100
maximum-size: 1000
expire-after-write: 30m
# 自定義緩存配置
cache:
# 空值緩存過期時間(5分鐘)
null-value-expire: 5m
# 熱點數(shù)據(jù)預(yù)熱key前綴
hot-data-prefix: "hot:"
# 分布式鎖前綴
lock-prefix: "cache:lock:"
3. 核心代碼實現(xiàn)
(1)緩存配置類:初始化 Caffeine 和 Redis 緩存
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class CacheConfig {
// Caffeine緩存管理器(本地緩存)
@Bean
public CacheManager caffeineCacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
// 配置Caffeine緩存參數(shù):初始容量100,最大容量1000,寫入后30分鐘過期
cacheManager.setCaffeine(Caffeine.newBuilder()
.initialCapacity(100)
.maximumSize(1000)
.expireAfterWrite(Duration.ofMinutes(30)));
return cacheManager;
}
// Redis緩存管理器(分布式緩存)
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory connectionFactory) {
// 序列化配置(避免Redis存儲亂碼)
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30)) // 默認過期時間30分鐘
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()));
// 自定義不同緩存的過期時間(如空值緩存5分鐘)
Map<String, RedisCacheConfiguration> cacheConfigs = new HashMap<>();
cacheConfigs.put("nullValueCache", config.entryTtl(Duration.ofMinutes(5)));
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.withInitialCacheConfigurations(cacheConfigs)
.build();
}
}
(2)緩存工具類:封裝三級緩存查詢邏輯
核心邏輯:先查 Caffeine→再查 Redis→最后查數(shù)據(jù)庫,同時處理空值緩存、分布式鎖、緩存更新:
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@Component
public class CacheUtil {
@Resource
private CacheManager caffeineCacheManager;
@Resource
private RedisCacheManager redisCacheManager;
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Resource
private RedissonClient redissonClient;
@Resource
private BloomFilterUtil bloomFilterUtil; // 布隆過濾器工具類
// 緩存查詢核心方法:key-緩存鍵,clazz-返回類型,dbLoader-數(shù)據(jù)庫查詢邏輯
public <T> T getCache(String key, Class<T> clazz, DataLoader<T> dbLoader) {
// 1. 布隆過濾器校驗:無效key直接返回null
if (!bloomFilterUtil.contains(key)) {
return null;
}
// 2. 查詢Caffeine本地緩存
Cache caffeineCache = caffeineCacheManager.getCache("localCache");
T localValue = caffeineCache.get(key, clazz);
if (localValue != null) {
return localValue;
}
// 3. 查詢Redis分布式緩存
Cache redisCache = redisCacheManager.getCache("redisCache");
T redisValue = redisCache.get(key, clazz);
if (redisValue != null) {
// Redis命中,同步到本地緩存
caffeineCache.put(key, redisValue);
return redisValue;
}
// 4. 緩存未命中,分布式鎖控制數(shù)據(jù)庫查詢
RLock lock = redissonClient.getLock("cache:lock:" + key);
try {
// 嘗試獲取鎖,最多等待3秒,持有鎖10秒
if (lock.tryLock(3, 10, TimeUnit.SECONDS)) {
// 再次查詢Redis(防止其他線程已更新緩存)
redisValue = redisCache.get(key, clazz);
if (redisValue != null) {
caffeineCache.put(key, redisValue);
return redisValue;
}
// 5. 查詢數(shù)據(jù)庫
T dbValue = dbLoader.load();
if (dbValue != null) {
// 數(shù)據(jù)庫有結(jié)果,更新各級緩存
redisCache.put(key, dbValue);
caffeineCache.put(key, dbValue);
} else {
// 數(shù)據(jù)庫無結(jié)果,緩存空值(5分鐘過期)
Cache nullValueCache = redisCacheManager.getCache("nullValueCache");
nullValueCache.put(key, null);
caffeineCache.put(key, null);
}
return dbValue;
} else {
// 獲取鎖失敗,返回默認值或拋出異常
throw new RuntimeException("緩存更新繁忙,請稍后重試");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
} finally {
// 釋放鎖
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
// 數(shù)據(jù)加載函數(shù)式接口(封裝數(shù)據(jù)庫查詢邏輯)
@FunctionalInterface
public interface DataLoader<T> {
T load();
}
}
(3)緩存更新與清除:保障數(shù)據(jù)一致性
當(dāng)數(shù)據(jù)庫數(shù)據(jù)發(fā)生變更(新增、修改、刪除)時,需同步清除各級緩存,避免數(shù)據(jù)不一致:
// 緩存清除方法(用于數(shù)據(jù)庫更新后)
public void clearCache(String key) {
// 1. 清除本地緩存
Cache caffeineCache = caffeineCacheManager.getCache("localCache");
caffeineCache.evict(key);
// 2. 清除Redis緩存
Cache redisCache = redisCacheManager.getCache("redisCache");
redisCache.evict(key);
// 3. 清除空值緩存
Cache nullValueCache = redisCacheManager.getCache("nullValueCache");
nullValueCache.evict(key);
}
(4)熱點數(shù)據(jù)預(yù)熱:系統(tǒng)啟動時加載
通過CommandLineRunner實現(xiàn)系統(tǒng)啟動時預(yù)熱熱點數(shù)據(jù),避免緩存冷啟動:
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
@Component
public class HotDataPreloader implements CommandLineRunner {
@Resource
private CacheUtil cacheUtil;
@Resource
private ProductMapper productMapper; // 數(shù)據(jù)庫DAO層
@Override
public void run(String... args) throws Exception {
// 加載熱點商品數(shù)據(jù)(如銷量前100的商品)
List<Product> hotProducts = productMapper.selectHotProducts(100);
for (Product product : hotProducts) {
String key = "product:" + product.getId();
// 存入本地緩存和Redis
cacheUtil.caffeineCacheManager.getCache("localCache").put(key, product);
cacheUtil.redisCacheManager.getCache("redisCache").put(key, product);
}
System.out.println("熱點數(shù)據(jù)預(yù)熱完成,共加載" + hotProducts.size() + "條數(shù)據(jù)");
}
}
四、優(yōu)化與監(jiān)控:讓緩存體系更穩(wěn)定
1. 配置優(yōu)化建議
- Caffeine 參數(shù):初始容量設(shè)為預(yù)期熱點數(shù)據(jù)量的 80%,最大容量避免超過 JVM 內(nèi)存的 30%(防止 OOM);
- Redis 優(yōu)化:開啟持久化(AOF+RDB),配置主從復(fù)制,避免單點故障;調(diào)整連接池參數(shù)適配并發(fā)量;
- 過期時間:根據(jù)數(shù)據(jù)更新頻率調(diào)整,高頻更新數(shù)據(jù)(如庫存)過期時間設(shè)為 5-10 分鐘,低頻數(shù)據(jù)設(shè)為 1-2 小時。
2. 監(jiān)控與告警
- 緩存命中率:通過 Spring Boot Actuator 暴露緩存指標(biāo),監(jiān)控 Caffeine 和 Redis 命中率(目標(biāo):均≥90%);
- 響應(yīng)時間:統(tǒng)計接口緩存命中 / 未命中的響應(yīng)時間,超過閾值(如 50ms)觸發(fā)告警;
- 異常監(jiān)控:監(jiān)控 Redis 連接異常、分布式鎖獲取失敗等情況,及時排查問題。
3. 注意事項
- 數(shù)據(jù)一致性:緩存清除需與數(shù)據(jù)庫事務(wù)同步(建議用事務(wù)提交后異步清除,避免阻塞業(yè)務(wù));
- 內(nèi)存管理:Caffeine 緩存避免存儲大對象,定期清理過期數(shù)據(jù);Redis 啟用內(nèi)存淘汰策略(如 LRU);
- 敏感數(shù)據(jù):緩存中不存儲明文敏感數(shù)據(jù)(如密碼、手機號),需加密后存儲;
- 降級策略:Redis 集群故障時,關(guān)閉 Redis 緩存讀取,僅用本地緩存 + 數(shù)據(jù)庫兜底,保障核心功能可用。
五、總結(jié)
SpringBoot+Caffeine+Redis + 空值緩存的三級緩存方案,通過 “本地緩存提效、分布式緩存保一致、空值緩存防穿透” 的設(shè)計,完美解決了高并發(fā)場景下的緩存核心難題。該方案不僅能將接口響應(yīng)時間壓縮至毫秒級,還能大幅降低數(shù)據(jù)庫壓力,同時具備故障隔離、優(yōu)雅降級的高可用特性,適用于電商、支付、社交等各類高并發(fā)系統(tǒng)。
實際落地時,可根據(jù)業(yè)務(wù)場景靈活調(diào)整緩存參數(shù)(如過期時間、最大容量)和預(yù)熱策略,結(jié)合監(jiān)控工具持續(xù)優(yōu)化,讓緩存體系真正成為系統(tǒng)的 “性能加速器”。
到此這篇關(guān)于Caffeine結(jié)合Redis空值緩存實現(xiàn)多級緩存的文章就介紹到這了,更多相關(guān)Caffeine結(jié)合Redis實現(xiàn)多級緩存內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺談Redis高并發(fā)緩存架構(gòu)性能優(yōu)化實戰(zhàn)
本文主要介紹了淺談Redis高并發(fā)緩存架構(gòu)性能優(yōu)化實戰(zhàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05
redis-cli創(chuàng)建redis集群的實現(xiàn)
本文主要介紹了redis-cli創(chuàng)建redis集群的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-06-06
Redis優(yōu)化token校驗主動失效的實現(xiàn)方案
在普通的token頒發(fā)和校驗中 當(dāng)用戶發(fā)現(xiàn)自己賬號和密碼被暴露了時修改了登錄密碼后舊的token仍然可以通過系統(tǒng)校驗直至token到達失效時間,所以系統(tǒng)需要token主動失效的一種能力,所以本文給大家介紹了Redis優(yōu)化token校驗主動失效的實現(xiàn)方案,需要的朋友可以參考下2024-03-03
多維度深入分析Redis的5種基本數(shù)據(jù)結(jié)構(gòu)
此篇文章主要對Redis的5種基本數(shù)據(jù)類型,即字符串(String)、列表(List)、散列(Hash)、集合(Set)、有序集合(Sorted?Set),從使用場景和底層結(jié)構(gòu)出發(fā),進行多維度深入分析。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-11-11
redis事務(wù)_動力節(jié)點Java學(xué)院整理
這篇文章主要介紹了redis事務(wù),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-08-08
一文搞懂Redis中的慢查詢?nèi)罩竞捅O(jiān)視器
我們都知道MySQL有慢查詢?nèi)罩?但Redis也有慢查詢?nèi)罩?可用于監(jiān)視和優(yōu)化查詢,本文給大家詳細介紹了Redis中的慢查詢?nèi)罩竞捅O(jiān)視器,文章通過代碼示例講解的非常詳細,需要的朋友可以參考下2024-04-04

