Caffeine結(jié)合Redis空值緩存實(shí)現(xiàn)多級(jí)緩存
在高并發(fā)系統(tǒng)中,緩存是提升響應(yīng)速度、減輕數(shù)據(jù)庫(kù)壓力的核心手段,但單一緩存方案往往難以應(yīng)對(duì)復(fù)雜場(chǎng)景 —— 本地緩存缺乏分布式一致性,Redis 緩存存在網(wǎng)絡(luò)開(kāi)銷(xiāo),還可能遭遇穿透、雪崩、擊穿等致命問(wèn)題。本文基于實(shí)戰(zhàn)案例,詳解 SpringBoot 整合Caffeine 本地緩存 + Redis 分布式緩存 + 空值緩存的三級(jí)緩存方案,從架構(gòu)設(shè)計(jì)到代碼落地,構(gòu)建高可用、低延遲的緩存體系。
一、多級(jí)緩存架構(gòu)設(shè)計(jì):為什么要 “三級(jí)聯(lián)動(dòng)”?
傳統(tǒng)緩存方案要么依賴(lài)單一本地緩存(無(wú)法分布式共享),要么僅用 Redis(網(wǎng)絡(luò) IO 開(kāi)銷(xiāo)影響性能),而三級(jí)緩存架構(gòu)通過(guò) “本地緩存 + 分布式緩存 + 數(shù)據(jù)庫(kù)” 的層級(jí)設(shè)計(jì),實(shí)現(xiàn)了 “速度” 與 “一致性” 的平衡:
- 第一級(jí):Caffeine 本地緩存基于 Java 內(nèi)存的高性能緩存,讀寫(xiě)延遲低至納秒級(jí),專(zhuān)門(mén)存儲(chǔ)熱點(diǎn)數(shù)據(jù)(如高頻訪問(wèn)的商品信息、配置參數(shù)),避免重復(fù)查詢(xún) Redis 和數(shù)據(jù)庫(kù),提升核心接口響應(yīng)速度。
- 第二級(jí):Redis 分布式緩存分布式環(huán)境下的共享緩存,解決本地緩存數(shù)據(jù)不一致問(wèn)題,同時(shí)承擔(dān) “中間緩沖” 角色,減少數(shù)據(jù)庫(kù)直接訪問(wèn)壓力。
- 第三級(jí):數(shù)據(jù)庫(kù)數(shù)據(jù)最終存儲(chǔ)源,僅在緩存未命中時(shí)觸發(fā)查詢(xún),保證數(shù)據(jù)可靠性。
核心優(yōu)勢(shì)
- 性能極致:本地緩存命中率超 90%,Redis 緩存命中率超 95%,99% 請(qǐng)求響應(yīng)時(shí)間 < 10ms;
- 高可用:故障隔離設(shè)計(jì),某一級(jí)緩存失效不影響整體服務(wù)(如 Redis 宕機(jī)時(shí),本地緩存可臨時(shí)兜底);
- 資源優(yōu)化:減少 Redis 網(wǎng)絡(luò) IO 和數(shù)據(jù)庫(kù)查詢(xún)壓力,降低集群部署成本;
- 多層防護(hù):從架構(gòu)層面規(guī)避緩存穿透、雪崩、擊穿三大經(jīng)典問(wèn)題。
二、核心問(wèn)題解決方案:三大緩存難題逐個(gè)擊破
1. 緩存穿透:攔截?zé)o效查詢(xún)
問(wèn)題:惡意請(qǐng)求查詢(xún)不存在的數(shù)據(jù)(如 ID=-1 的商品),導(dǎo)致緩存失效后直接穿透到數(shù)據(jù)庫(kù),引發(fā)性能問(wèn)題。解決方案:空值緩存 + 布隆過(guò)濾器雙重防護(hù)
- 空值緩存:數(shù)據(jù)庫(kù)查詢(xún)無(wú)結(jié)果時(shí),在 Redis 和 Caffeine 中緩存空值(設(shè)置短期過(guò)期時(shí)間,如 5 分鐘),避免重復(fù)穿透;
- 布隆過(guò)濾器:預(yù)先將數(shù)據(jù)庫(kù)中存在的主鍵(如商品 ID、用戶(hù) ID)存入布隆過(guò)濾器,請(qǐng)求先經(jīng)過(guò)過(guò)濾器校驗(yàn),無(wú)效 ID 直接攔截,不進(jìn)入緩存和數(shù)據(jù)庫(kù)。
2. 緩存雪崩:避免集中失效
問(wèn)題:大量緩存數(shù)據(jù)在同一時(shí)間過(guò)期,或 Redis 集群宕機(jī),導(dǎo)致所有請(qǐng)求瞬間涌向數(shù)據(jù)庫(kù),引發(fā)數(shù)據(jù)庫(kù)雪崩。解決方案:隨機(jī)過(guò)期時(shí)間 + 優(yōu)雅降級(jí)
- 隨機(jī) TTL:緩存數(shù)據(jù)時(shí),在基礎(chǔ)過(guò)期時(shí)間(如 30 分鐘)上增加隨機(jī)值(5-10 分鐘),使緩存過(guò)期時(shí)間分散,避免集中失效;
- 優(yōu)雅降級(jí):Redis 宕機(jī)時(shí),啟用本地緩存兜底,同時(shí)觸發(fā)告警機(jī)制,保障服務(wù)核心功能可用。
3. 緩存擊穿:保護(hù)熱點(diǎn)數(shù)據(jù)
問(wèn)題:熱點(diǎn)數(shù)據(jù)(如秒殺商品)緩存過(guò)期瞬間,大量并發(fā)請(qǐng)求穿透到數(shù)據(jù)庫(kù),導(dǎo)致數(shù)據(jù)庫(kù)壓力驟增。解決方案:熱點(diǎn)數(shù)據(jù)預(yù)熱 + 分布式鎖
- 數(shù)據(jù)預(yù)熱:系統(tǒng)啟動(dòng)時(shí),主動(dòng)將熱點(diǎn)數(shù)據(jù)加載到 Caffeine 和 Redis 中,避免緩存冷啟動(dòng);
- 分布式鎖:緩存過(guò)期后,通過(guò) Redis 分布式鎖(如 Redisson)控制,僅允許一個(gè)線(xiàn)程查詢(xún)數(shù)據(jù)庫(kù)并更新緩存,其他線(xiàn)程等待緩存更新后再查詢(xún),防止并發(fā)穿透。
三、實(shí)戰(zhàn)落地:SpringBoot 整合三級(jí)緩存
1. 依賴(lài)配置
首先引入核心依賴(lài)(Maven 示例),包含 SpringBoot 緩存 starter、Caffeine、Redis、Redisson(分布式鎖):
<!-- SpringBoot緩存核心依賴(lài) -->
<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依賴(lài) -->
<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:
# 初始容量、最大容量、過(guò)期時(shí)間(寫(xiě)入后30分鐘過(guò)期)
initial-capacity: 100
maximum-size: 1000
expire-after-write: 30m
# 自定義緩存配置
cache:
# 空值緩存過(guò)期時(shí)間(5分鐘)
null-value-expire: 5m
# 熱點(diǎn)數(shù)據(jù)預(yù)熱key前綴
hot-data-prefix: "hot:"
# 分布式鎖前綴
lock-prefix: "cache:lock:"
3. 核心代碼實(shí)現(xiàn)
(1)緩存配置類(lèi):初始化 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,寫(xiě)入后30分鐘過(guò)期
cacheManager.setCaffeine(Caffeine.newBuilder()
.initialCapacity(100)
.maximumSize(1000)
.expireAfterWrite(Duration.ofMinutes(30)));
return cacheManager;
}
// Redis緩存管理器(分布式緩存)
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory connectionFactory) {
// 序列化配置(避免Redis存儲(chǔ)亂碼)
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30)) // 默認(rèn)過(guò)期時(shí)間30分鐘
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()));
// 自定義不同緩存的過(guò)期時(shí)間(如空值緩存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)緩存工具類(lèi):封裝三級(jí)緩存查詢(xún)邏輯
核心邏輯:先查 Caffeine→再查 Redis→最后查數(shù)據(jù)庫(kù),同時(shí)處理空值緩存、分布式鎖、緩存更新:
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; // 布隆過(guò)濾器工具類(lèi)
// 緩存查詢(xún)核心方法:key-緩存鍵,clazz-返回類(lèi)型,dbLoader-數(shù)據(jù)庫(kù)查詢(xún)邏輯
public <T> T getCache(String key, Class<T> clazz, DataLoader<T> dbLoader) {
// 1. 布隆過(guò)濾器校驗(yàn):無(wú)效key直接返回null
if (!bloomFilterUtil.contains(key)) {
return null;
}
// 2. 查詢(xún)Caffeine本地緩存
Cache caffeineCache = caffeineCacheManager.getCache("localCache");
T localValue = caffeineCache.get(key, clazz);
if (localValue != null) {
return localValue;
}
// 3. 查詢(xún)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ù)庫(kù)查詢(xún)
RLock lock = redissonClient.getLock("cache:lock:" + key);
try {
// 嘗試獲取鎖,最多等待3秒,持有鎖10秒
if (lock.tryLock(3, 10, TimeUnit.SECONDS)) {
// 再次查詢(xún)Redis(防止其他線(xiàn)程已更新緩存)
redisValue = redisCache.get(key, clazz);
if (redisValue != null) {
caffeineCache.put(key, redisValue);
return redisValue;
}
// 5. 查詢(xún)數(shù)據(jù)庫(kù)
T dbValue = dbLoader.load();
if (dbValue != null) {
// 數(shù)據(jù)庫(kù)有結(jié)果,更新各級(jí)緩存
redisCache.put(key, dbValue);
caffeineCache.put(key, dbValue);
} else {
// 數(shù)據(jù)庫(kù)無(wú)結(jié)果,緩存空值(5分鐘過(guò)期)
Cache nullValueCache = redisCacheManager.getCache("nullValueCache");
nullValueCache.put(key, null);
caffeineCache.put(key, null);
}
return dbValue;
} else {
// 獲取鎖失敗,返回默認(rèn)值或拋出異常
throw new RuntimeException("緩存更新繁忙,請(qǐng)稍后重試");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
} finally {
// 釋放鎖
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
// 數(shù)據(jù)加載函數(shù)式接口(封裝數(shù)據(jù)庫(kù)查詢(xún)邏輯)
@FunctionalInterface
public interface DataLoader<T> {
T load();
}
}
(3)緩存更新與清除:保障數(shù)據(jù)一致性
當(dāng)數(shù)據(jù)庫(kù)數(shù)據(jù)發(fā)生變更(新增、修改、刪除)時(shí),需同步清除各級(jí)緩存,避免數(shù)據(jù)不一致:
// 緩存清除方法(用于數(shù)據(jù)庫(kù)更新后)
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)熱點(diǎn)數(shù)據(jù)預(yù)熱:系統(tǒng)啟動(dòng)時(shí)加載
通過(guò)CommandLineRunner實(shí)現(xiàn)系統(tǒng)啟動(dòng)時(shí)預(yù)熱熱點(diǎn)數(shù)據(jù),避免緩存冷啟動(dòng):
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ù)庫(kù)DAO層
@Override
public void run(String... args) throws Exception {
// 加載熱點(diǎn)商品數(shù)據(jù)(如銷(xiāo)量前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("熱點(diǎn)數(shù)據(jù)預(yù)熱完成,共加載" + hotProducts.size() + "條數(shù)據(jù)");
}
}
四、優(yōu)化與監(jiān)控:讓緩存體系更穩(wěn)定
1. 配置優(yōu)化建議
- Caffeine 參數(shù):初始容量設(shè)為預(yù)期熱點(diǎn)數(shù)據(jù)量的 80%,最大容量避免超過(guò) JVM 內(nèi)存的 30%(防止 OOM);
- Redis 優(yōu)化:開(kāi)啟持久化(AOF+RDB),配置主從復(fù)制,避免單點(diǎn)故障;調(diào)整連接池參數(shù)適配并發(fā)量;
- 過(guò)期時(shí)間:根據(jù)數(shù)據(jù)更新頻率調(diào)整,高頻更新數(shù)據(jù)(如庫(kù)存)過(guò)期時(shí)間設(shè)為 5-10 分鐘,低頻數(shù)據(jù)設(shè)為 1-2 小時(shí)。
2. 監(jiān)控與告警
- 緩存命中率:通過(guò) Spring Boot Actuator 暴露緩存指標(biāo),監(jiān)控 Caffeine 和 Redis 命中率(目標(biāo):均≥90%);
- 響應(yīng)時(shí)間:統(tǒng)計(jì)接口緩存命中 / 未命中的響應(yīng)時(shí)間,超過(guò)閾值(如 50ms)觸發(fā)告警;
- 異常監(jiān)控:監(jiān)控 Redis 連接異常、分布式鎖獲取失敗等情況,及時(shí)排查問(wèn)題。
3. 注意事項(xiàng)
- 數(shù)據(jù)一致性:緩存清除需與數(shù)據(jù)庫(kù)事務(wù)同步(建議用事務(wù)提交后異步清除,避免阻塞業(yè)務(wù));
- 內(nèi)存管理:Caffeine 緩存避免存儲(chǔ)大對(duì)象,定期清理過(guò)期數(shù)據(jù);Redis 啟用內(nèi)存淘汰策略(如 LRU);
- 敏感數(shù)據(jù):緩存中不存儲(chǔ)明文敏感數(shù)據(jù)(如密碼、手機(jī)號(hào)),需加密后存儲(chǔ);
- 降級(jí)策略:Redis 集群故障時(shí),關(guān)閉 Redis 緩存讀取,僅用本地緩存 + 數(shù)據(jù)庫(kù)兜底,保障核心功能可用。
五、總結(jié)
SpringBoot+Caffeine+Redis + 空值緩存的三級(jí)緩存方案,通過(guò) “本地緩存提效、分布式緩存保一致、空值緩存防穿透” 的設(shè)計(jì),完美解決了高并發(fā)場(chǎng)景下的緩存核心難題。該方案不僅能將接口響應(yīng)時(shí)間壓縮至毫秒級(jí),還能大幅降低數(shù)據(jù)庫(kù)壓力,同時(shí)具備故障隔離、優(yōu)雅降級(jí)的高可用特性,適用于電商、支付、社交等各類(lèi)高并發(fā)系統(tǒng)。
實(shí)際落地時(shí),可根據(jù)業(yè)務(wù)場(chǎng)景靈活調(diào)整緩存參數(shù)(如過(guò)期時(shí)間、最大容量)和預(yù)熱策略,結(jié)合監(jiān)控工具持續(xù)優(yōu)化,讓緩存體系真正成為系統(tǒng)的 “性能加速器”。
到此這篇關(guān)于Caffeine結(jié)合Redis空值緩存實(shí)現(xiàn)多級(jí)緩存的文章就介紹到這了,更多相關(guān)Caffeine結(jié)合Redis實(shí)現(xiàn)多級(jí)緩存內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
redis list類(lèi)型命令的實(shí)現(xiàn)
本文主要介紹了redis list類(lèi)型命令的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07
使用Redis防止重復(fù)發(fā)送RabbitMQ消息的方法詳解
今天遇到一個(gè)問(wèn)題,發(fā)送MQ消息的時(shí)候需要保證不會(huì)重復(fù)發(fā)送,注意不是可靠到達(dá),這里保證的是不會(huì)生產(chǎn)多條一樣的消息,所以本文主要介紹了使用Redis防止重復(fù)發(fā)送RabbitMQ消息的方法,需要的朋友可以參考下2025-01-01
Redis 的過(guò)期策略與鍵的過(guò)期時(shí)間設(shè)置方法
Redis通過(guò)惰性刪除和定期刪除策略管理內(nèi)存,提供多種命令設(shè)置鍵的過(guò)期時(shí)間,并通過(guò)過(guò)期字典高效處理過(guò)期鍵,合理設(shè)置過(guò)期時(shí)間、監(jiān)控過(guò)期鍵數(shù)量和避免大量鍵同時(shí)過(guò)期是最佳實(shí)踐,本文介紹Redis 的過(guò)期策略與鍵的過(guò)期時(shí)間設(shè)置,感興趣的朋友一起看看吧2025-03-03
Redis實(shí)戰(zhàn)之Redis實(shí)現(xiàn)異步秒殺優(yōu)化詳解
這篇文章主要給大家介紹了Redis實(shí)戰(zhàn)之Redis實(shí)現(xiàn)異步秒殺優(yōu)化方法,文章通過(guò)圖片和代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,感興趣的同學(xué)可以自己動(dòng)手試一下2023-09-09
淺析Redis Sentinel 與 Redis Cluster
本文主要介紹Redis Sentinel 及 Redis Cluster的區(qū)別及用法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-06-06
Redis的緩存使用技巧分享(商戶(hù)查詢(xún)緩存)
本文介紹了緩存的原理和常見(jiàn)問(wèn)題及其解決方案,包括緩存更新策略、緩存穿透、緩存雪崩和緩存擊穿等,通過(guò)實(shí)際案例和代碼示例,展示了如何在后端開(kāi)發(fā)中有效利用緩存提高性能和降低數(shù)據(jù)庫(kù)壓力2026-02-02

