Redis+Caffeine實(shí)現(xiàn)高效兩級(jí)緩存架構(gòu)的詳細(xì)指南
引言
在現(xiàn)代高并發(fā)系統(tǒng)中,緩存是提升系統(tǒng)性能的關(guān)鍵組件之一。傳統(tǒng)的單一緩存方案往往難以同時(shí)滿足高性能和高可用性的需求。本文將介紹如何結(jié)合 Redis 和 Caffeine 構(gòu)建一個(gè)高效的兩級(jí)緩存系統(tǒng),并通過(guò)三個(gè)版本的演進(jìn)展示如何逐步優(yōu)化代碼結(jié)構(gòu)。
兩級(jí)緩存架構(gòu)概述
兩級(jí)緩存通常由本地緩存(如 Caffeine)和分布式緩存(如 Redis)組成:
- 本地緩存(Caffeine):基于內(nèi)存,訪問(wèn)速度極快,但容量有限且無(wú)法跨進(jìn)程共享
- 分布式緩存(Redis):可跨進(jìn)程共享,容量更大,但訪問(wèn)速度相對(duì)較慢
通過(guò)結(jié)合兩者優(yōu)勢(shì),我們可以構(gòu)建一個(gè)既快速又具備一致性的緩存系統(tǒng)。
兩級(jí)緩存的優(yōu)勢(shì)
性能優(yōu)勢(shì)
| 緩存類型 | 平均延遲 | 延遲波動(dòng)范圍 |
|---|---|---|
| 本地緩存 | 0.05-1ms | 穩(wěn)定 |
| 遠(yuǎn)程緩存 | 1-10ms | 受網(wǎng)絡(luò)影響大 |
| 數(shù)據(jù)庫(kù)查詢 | 10-100ms | 取決于SQL復(fù)雜度 |
典型案例:某電商平臺(tái)商品詳情頁(yè)采用兩級(jí)緩存后:
- 單純Redis方案:P99響應(yīng)時(shí)間8ms
- 兩級(jí)緩存方案:P99響應(yīng)時(shí)間降至2ms
本地緩存的延遲是最低的,遠(yuǎn)遠(yuǎn)低于redis等遠(yuǎn)程緩存,而且本地緩存不受網(wǎng)絡(luò)的影響,所以延遲的波動(dòng)范圍也是最穩(wěn)定的。所以,二級(jí)緩存在性能上有極大的優(yōu)勢(shì)。
系統(tǒng)穩(wěn)定性
1.抗流量洪峰能力
假如電商環(huán)境中出現(xiàn)了秒殺場(chǎng)景,或者促銷活動(dòng)。會(huì)有大量的訪問(wèn)到同一個(gè)商品或者優(yōu)惠券,以下是兩種情景:
純Redis方案,所有請(qǐng)求直達(dá)Redis,容易導(dǎo)致:
- 連接池耗盡
- 帶寬被打滿
- Redis CPU飆升
兩級(jí)緩存方案:
- 80%以上請(qǐng)求被本地緩存攔截
- Redis負(fù)載降低5-10倍
- 系統(tǒng)整體更平穩(wěn)
2.故障容忍度
由于Redis等遠(yuǎn)程緩存需要通過(guò)網(wǎng)絡(luò)連接,如果網(wǎng)絡(luò)出現(xiàn)異常,很容易出現(xiàn)訪問(wèn)不到數(shù)據(jù)的情況。本地緩存則不存在網(wǎng)絡(luò)問(wèn)題,所以對(duì)故障的容忍度是非常高的。
網(wǎng)絡(luò)分區(qū)場(chǎng)景測(cè)試:
模擬 機(jī)房網(wǎng)絡(luò)抖動(dòng)(丟包率30%):
- 純Redis方案:錯(cuò)誤率飆升到85%
- 兩級(jí)緩存方案:核心接口仍保持92%成功率
Caffeine簡(jiǎn)介
Caffeine 是一個(gè)高性能的 Java 本地緩存庫(kù),可以理解為 Java 版的"內(nèi)存臨時(shí)儲(chǔ)物柜"。它的核心特點(diǎn)可以用日常生活中的例子來(lái)理解:
就像一個(gè)智能的文件柜:
- 自動(dòng)整理 - 會(huì)自己清理不常用的文件(基于大小或時(shí)間)
- 快速查找 - 比去檔案室(數(shù)據(jù)庫(kù))找資料快100倍
- 空間管理 - 只保留最常用的1000份文件(可配置)
技術(shù)特點(diǎn):
基于 Google Guava 緩存改進(jìn)而來(lái)
讀寫(xiě)性能接近 HashMap(O(1)時(shí)間復(fù)雜度)
提供多種淘汰策略:
// 按數(shù)量淘汰(保留最近使用的1000個(gè)) Caffeine.newBuilder().maximumSize(1000) // 按時(shí)間淘汰(數(shù)據(jù)保存1小時(shí)) Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS)
典型使用場(chǎng)景:
// 創(chuàng)建緩存(相當(dāng)于準(zhǔn)備一個(gè)儲(chǔ)物柜)
Cache<String, User> cache = Caffeine.newBuilder()
.maximumSize(100) // 最多存100個(gè)用戶
.expireAfterWrite(10, TimeUnit.MINUTES) // 10分鐘不用就清理
.build();
// 存數(shù)據(jù)(往柜子里放東西)
cache.put("user101", new User("張三"));
// 取數(shù)據(jù)(從柜子拿東西)
User user = cache.getIfPresent("user101");
// 取不到時(shí)自動(dòng)加載(柜子沒(méi)有就去倉(cāng)庫(kù)找)
User user = cache.get("user101", key -> userDao.getUser(key));
優(yōu)勢(shì)對(duì)比:
- 比 HashMap:支持自動(dòng)清理和過(guò)期
- 比 Redis:快100倍(無(wú)需網(wǎng)絡(luò)IO)
- 比 Guava Cache:內(nèi)存效率更高,并發(fā)性能更好
注意事項(xiàng):
- 僅適用于單機(jī)(不同服務(wù)器間的緩存不共享)
- 適合緩存不易變的數(shù)據(jù)(如系統(tǒng)配置)
- JVM重啟后數(shù)據(jù)會(huì)丟失(如需持久化需配合Redis)
版本演進(jìn)
版本1:直接侵入Service代碼
在第一個(gè)版本中,我們直接在 Service 層實(shí)現(xiàn)了兩級(jí)緩存邏輯:
@Override
public Order getOrderById(Integer id) {
String key = CacheConstant.ORDER + id;
return (Order) orderCache.get(key, k -> {
// 先查詢 Redis
Object obj = redisTemplate.opsForValue().get(key);
if (obj != null) {
log.info("get data from redis");
if (obj instanceof Order) {
return (Order) obj;
} else {
log.warn("Unexpected type from Redis, expected Order but got {}", obj.getClass());
}
}
// Redis沒(méi)有或類型不匹配則查詢 DB
log.info("get data from database");
Order myOrder = orderMapper.getOrderById(id);
redisTemplate.opsForValue().set(key, myOrder, 120, TimeUnit.SECONDS);
return myOrder;
});
}
優(yōu)點(diǎn):
- 實(shí)現(xiàn)簡(jiǎn)單直接
- 緩存邏輯清晰可見(jiàn)
缺點(diǎn):
- 緩存代碼與業(yè)務(wù)代碼高度耦合
- 難以復(fù)用緩存邏輯
- 代碼重復(fù)率高
版本2:使用Spring Cache注解
在spring項(xiàng)目中,提供了CacheManager接口和一些注解,允許讓我們通過(guò)注解的方式來(lái)操作緩存。先來(lái)看一下常用的幾個(gè)注解說(shuō)明:
1.@Cacheable- 緩存查詢
作用:將方法的返回值緩存起來(lái),下次調(diào)用時(shí)直接返回緩存數(shù)據(jù),避免重復(fù)計(jì)算或查詢數(shù)據(jù)庫(kù)。
適用場(chǎng)景:
- 查詢方法(如
getUserById、findProduct) - 計(jì)算結(jié)果穩(wěn)定的方法
示例:
@Cacheable(value = "users", key = "#userId")
public User getUserById(Long userId) {
// 如果緩存中沒(méi)有,才執(zhí)行此方法
return userRepository.findById(userId).orElse(null);
}
參數(shù)說(shuō)明:
value/cacheNames:緩存名稱(如"users")key:緩存鍵(支持 SpEL 表達(dá)式,如#userId)condition:條件緩存(如condition = "#userId > 100")unless:排除某些返回值(如unless = "#result == null")
2.@CachePut- 更新緩存
作用:方法執(zhí)行后,更新緩存(通常用于 insert 或 update 操作)。
適用場(chǎng)景:
- 新增或修改數(shù)據(jù)后同步緩存
- 避免緩存與數(shù)據(jù)庫(kù)不一致
示例:
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
return userRepository.save(user); // 更新數(shù)據(jù)庫(kù)后,自動(dòng)更新緩存
}
注意:
與 @Cacheable 不同,@CachePut 一定會(huì)執(zhí)行方法,并更新緩存。
3.@CacheEvict- 刪除緩存
作用:方法執(zhí)行后,刪除緩存(適用于 delete 操作)。
適用場(chǎng)景:
- 數(shù)據(jù)刪除后清理緩存
- 緩存失效策略
示例:
@CacheEvict(value = "users", key = "#userId")
public void deleteUser(Long userId) {
userRepository.deleteById(userId); // 刪除數(shù)據(jù)庫(kù)數(shù)據(jù)后,自動(dòng)刪除緩存
}
參數(shù)擴(kuò)展:
allEntries = true:清空整個(gè)緩存(如@CacheEvict(value = "users", allEntries = true))beforeInvocation = true:在方法執(zhí)行前刪除緩存(避免方法異常導(dǎo)致緩存未清理)
第二個(gè)版本利用了 Spring 的緩存注解來(lái)簡(jiǎn)化代碼,如果要使用上面這幾個(gè)注解管理緩存的話,我們就不需要配置V1版本中的那個(gè)類型為Cache的Bean了,而是需要配置spring中的CacheManager的相關(guān)參數(shù),具體參數(shù)的配置和之前一樣。
注意,在改進(jìn)更新操作的時(shí),這里和V1版本的代碼有一點(diǎn)區(qū)別,在之前的更新操作方法中,是沒(méi)有返回值的void類型,但是這里需要修改返回值的類型,否則會(huì)緩存一個(gè)空對(duì)象到緩存中對(duì)應(yīng)的key上。當(dāng)下次執(zhí)行查詢操作時(shí),會(huì)直接返回空對(duì)象給調(diào)用方,而不會(huì)執(zhí)行方法中查詢數(shù)據(jù)庫(kù)或Redis的操作。
@Cacheable(value = "order", key = "#id")
@Override
public Order getOrderById(Integer id) {
String key = CacheConstant.ORDER + id;
// 先查詢 Redis
Object obj = redisTemplate.opsForValue().get(key);
if (obj != null) {
log.info("get data from redis");
if (obj instanceof Order) {
return (Order) obj;
} else {
log.warn("Unexpected type from Redis, expected Order but got {}", obj.getClass());
}
}
// Redis沒(méi)有或類型不匹配則查詢 DB
log.info("get data from database");
Order myOrder = orderMapper.getOrderById(id);
redisTemplate.opsForValue().set(key, myOrder, 120, TimeUnit.SECONDS);
return myOrder;
}
@Override
@CachePut(cacheNames = "order",key = "#order.id")
public Order updateOrder(Order order) {
log.info("update order data");
orderMapper.updateOrderById(order);
//修改 Redis
redisTemplate.opsForValue().set(CacheConstant.ORDER + order.getId(),
order, 120, TimeUnit.SECONDS);
return order;
}
@Override
@CacheEvict(cacheNames = "order",key = "#id")
public void deleteOrderById(Integer id) {
log.info("delete order");
orderMapper.deleteOrderById(id);
redisTemplate.delete(CacheConstant.ORDER + id);
}
改進(jìn)點(diǎn):
- 使用
@Cacheable注解管理 Caffeine 緩存 - 減少了部分重復(fù)代碼
- 緩存配置更加集中
遺留問(wèn)題:
- Redis 操作仍需手動(dòng)編寫(xiě)
- 兩級(jí)緩存的同步邏輯仍需在業(yè)務(wù)代碼中處理
版本3:自定義注解+AOP實(shí)現(xiàn)
如果單純只是使用Cache注解進(jìn)行緩存,還是無(wú)法把Redis功能實(shí)現(xiàn)從server模塊中剝離出去。如果按照spring對(duì)cache注解的思路,我們可以自定義注解再利用AOP切片操作,把對(duì)應(yīng)的緩存功能切入到service的代碼中,就能實(shí)現(xiàn)二者之間的解耦。
首先,需要定義一個(gè)注解:
/**
* 雙緩存注解,用于標(biāo)記需要使用雙緩存(通常為本地緩存和遠(yuǎn)程緩存)的方法
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DoubleCache {
/**
* 指定緩存的名稱
* @return 緩存名稱
*/
String cacheName();
/**
* 指定緩存的鍵,支持Spring EL表達(dá)式
* @return 緩存鍵
*/
String key(); //支持springEl表達(dá)式
/**
* 指定二級(jí)緩存的超時(shí)時(shí)間,單位默認(rèn)根據(jù)實(shí)現(xiàn)確定(通常為秒)
* 默認(rèn)值為120
* @return 二級(jí)緩存超時(shí)時(shí)間
*/
long l2TimeOut() default 120;
/**
* 指定緩存類型
* 默認(rèn)值為 CacheType.FULL
* @return 緩存類型
*/
CacheType type() default CacheType.FULL;
}
定義一個(gè)枚舉類型的變量,表示緩存操作的類型:
public enum CacheType {
FULL, //存取
PUT, //只存
DELETE //刪除
}
如果要支持springEL的表達(dá)式,還需要一個(gè)工具類來(lái)解析springEI的表達(dá)式:
public class SpelExpressionUtils {
/**
* 解析 SpEL 表達(dá)式并替換變量
* @param elString 表達(dá)式(如 "user.name")
* @param map 變量鍵值對(duì)
* @return 解析后的字符串
*/
public static String parse(String elString, TreeMap<String, Object> map) {
// 將輸入的表達(dá)式包裝為 SpEL 表達(dá)式格式
elString = String.format("#{%s}", elString);
// 創(chuàng)建 SpEL 表達(dá)式解析器
ExpressionParser parser = new SpelExpressionParser();
// 創(chuàng)建標(biāo)準(zhǔn)的評(píng)估上下文,用于存儲(chǔ)變量
EvaluationContext context = new StandardEvaluationContext();
// 將傳入的變量鍵值對(duì)設(shè)置到評(píng)估上下文中
map.forEach(context::setVariable);
// 使用解析器解析表達(dá)式,使用模板解析上下文
Expression expression = parser.parseExpression(elString, new TemplateParserContext());
// 在指定上下文中計(jì)算表達(dá)式的值,并將結(jié)果轉(zhuǎn)換為字符串返回
return expression.getValue(context, String.class);
}
}
定義切片,在切片操作中來(lái)實(shí)現(xiàn)Caffeine和Redis的緩存操作:
@Slf4j
@Component
@Aspect
@AllArgsConstructor
public class CacheAspect {
private final Cache<String, Object> cache;
private final RedisTemplate<String, Object> redisTemplate;
/**
* 定義切點(diǎn),匹配使用了 @DoubleCache 注解的方法
*/
@Pointcut("@annotation(com.example.redis_caffeine.annonation.DoubleCache)")
public void cacheAspect() {}
/**
* 環(huán)繞通知,處理緩存的讀寫(xiě)、更新和刪除操作
*
* @param point 切入點(diǎn)對(duì)象,包含方法執(zhí)行的相關(guān)信息
* @return 方法執(zhí)行的返回結(jié)果
* @throws Throwable 方法執(zhí)行過(guò)程中可能拋出的異常
*/
@Around("cacheAspect()")
public Object doAround(ProceedingJoinPoint point) throws Throwable {
try {
// 獲取方法簽名和方法對(duì)象
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
// 解析參數(shù),將參數(shù)名和參數(shù)值存入 TreeMap 中
String[] paramNames = signature.getParameterNames();
Object[] args = point.getArgs();
TreeMap<String, Object> treeMap = new TreeMap<>();
for (int i = 0; i < paramNames.length; i++) {
treeMap.put(paramNames[i], args[i]);
}
// 獲取方法上的 @DoubleCache 注解
DoubleCache annotation = method.getAnnotation(DoubleCache.class);
// 解析 SpEL 表達(dá)式,得到最終的 key 片段
String elResult = SpelExpressionUtils.parse(annotation.key(), treeMap);
// 拼接完整的緩存 key
String realKey = annotation.cacheName() + CacheConstant.ORDER + elResult;
// 處理強(qiáng)制更新操作
if (annotation.type() == CacheType.PUT) {
// 執(zhí)行目標(biāo)方法
Object object = point.proceed();
// 將結(jié)果存入 Redis,并設(shè)置過(guò)期時(shí)間
redisTemplate.opsForValue().set(realKey, object, annotation.l2TimeOut(), TimeUnit.SECONDS);
// 將結(jié)果存入 Caffeine 緩存
cache.put(realKey, object);
return object;
}
// 處理刪除操作
if (annotation.type() == CacheType.DELETE) {
// 從 Redis 中刪除緩存
redisTemplate.delete(realKey);
// 從 Caffeine 緩存中刪除緩存
cache.invalidate(realKey);
return point.proceed();
}
// 優(yōu)先從 Caffeine 緩存中獲取數(shù)據(jù)
Object caffeineCache = cache.getIfPresent(realKey);
if (caffeineCache != null) {
log.info("get data from caffeine");
return caffeineCache;
}
// 其次從 Redis 中獲取數(shù)據(jù)
Object redisCache = redisTemplate.opsForValue().get(realKey);
if (redisCache != null) {
log.info("get data from redis");
// 將從 Redis 中獲取的數(shù)據(jù)存入 Caffeine 緩存
cache.put(realKey, redisCache);
return redisCache;
}
// 最后查詢數(shù)據(jù)庫(kù)
log.info("get data from database");
Object object = point.proceed();
if (object != null) {
// 將數(shù)據(jù)庫(kù)查詢結(jié)果存入 Redis,并設(shè)置過(guò)期時(shí)間
redisTemplate.opsForValue().set(realKey, object, annotation.l2TimeOut(), TimeUnit.SECONDS);
// 將數(shù)據(jù)庫(kù)查詢結(jié)果存入 Caffeine 緩存
cache.put(realKey, object);
}
return object;
} catch (Exception e) {
// 記錄緩存切面處理過(guò)程中的錯(cuò)誤
log.error("Cache aspect error", e);
throw e;
}
}
}
以上操作的主要工作總結(jié)下來(lái)是:
- 定義切點(diǎn):匹配使用
@DoubleCache注解的方法 - 參數(shù)解析與鍵生成:提取方法參數(shù),解析 SpEL 表達(dá)式生成緩存鍵
- 緩存更新策略:PUT 類型執(zhí)行方法后同步更新 Redis 和本地緩存
- 緩存刪除策略:DELETE 類型先刪除 Redis 和本地緩存,再執(zhí)行方法
- 多級(jí)查詢策略:優(yōu)先查本地緩存 → Redis → 數(shù)據(jù)庫(kù),查詢結(jié)果寫(xiě)入兩級(jí)緩存
執(zhí)行操作流程,以查詢操作為例:
攔截被 @DoubleCache 標(biāo)記的目標(biāo)方法
生成緩存鍵 realKey
依次查詢Caffeine → Redis → 數(shù)據(jù)庫(kù)
將數(shù)據(jù)庫(kù)結(jié)果寫(xiě)入兩級(jí)緩存并返回
若觸發(fā)更新/刪除操作,則同步清理或更新緩存
/**
* 根據(jù)訂單ID獲取訂單信息
* 使用 @DoubleCache 注解,類型為 FULL,會(huì)執(zhí)行完整的緩存操作邏輯
* @param id 訂單ID
* @return 訂單對(duì)象
*/
@Override
@DoubleCache(cacheName = "order", key = "#id",
type = CacheType.FULL)
public Order getOrderById(Integer id) {
return orderMapper.getOrderById(id);
}
/**
* 更新訂單信息
* 使用 @DoubleCache 注解,類型為 PUT,會(huì)執(zhí)行緩存更新操作
* @param order 訂單對(duì)象
*/
@Override
@DoubleCache(cacheName = "order", key = "#id",
type = CacheType.PUT)
public void updateOrder(Order order) {
orderMapper.updateOrderById(order);
}
/**
* 根據(jù)訂單ID刪除訂單信息
* 使用 @DoubleCache 注解,類型為 DELETE,會(huì)執(zhí)行緩存刪除操作
* @param id 訂單ID
*/
@Override
@DoubleCache(cacheName = "order", key = "#id",
type = CacheType.DELETE)
public void deleteOrderById(Integer id) {
orderMapper.deleteOrderById(id);
}
核心注解:
@DoubleCache:自定義注解,用于標(biāo)記需要兩級(jí)緩存的方法CacheType:枚舉,定義緩存操作類型(FULL, PUT, DELETE)
AOP實(shí)現(xiàn)要點(diǎn):
- 解析注解參數(shù)
- 根據(jù)操作類型執(zhí)行不同的緩存邏輯
- 處理緩存穿透、雪崩等問(wèn)題
- 保證兩級(jí)緩存的一致性
優(yōu)勢(shì):
- 業(yè)務(wù)代碼完全專注于業(yè)務(wù)邏輯
- 緩存邏輯集中管理,便于維護(hù)
- 注解配置靈活,可適應(yīng)不同場(chǎng)景
- 代碼簡(jiǎn)潔,可讀性高
關(guān)鍵配置
Caffeine 配置
@Configuration
@EnableCaching
public class CaffeineConfig {
//-----------------------------V1------V3-----------------------------------
@Bean
public Cache<String, Object> orderCache() {
return Caffeine.newBuilder()
.initialCapacity(128)
.maximumSize(1024)
.expireAfterWrite(60, TimeUnit.SECONDS)
.build();
}
//-----------------------------V2------------------------------------------
@Bean
public CacheManager cacheManager(){
CaffeineCacheManager cacheManager=new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.initialCapacity(128)
.maximumSize(1024)
.expireAfterWrite(60, TimeUnit.SECONDS));
return cacheManager;
}
}
Redis 配置
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 創(chuàng)建 ObjectMapper 實(shí)例,用于 JSON 序列化和反序列化
ObjectMapper objectMapper = new ObjectMapper();
// 注冊(cè) JavaTimeModule,用于支持 Java 8 日期時(shí)間類型的序列化和反序列化
objectMapper.registerModule(new JavaTimeModule());
// 禁用將日期寫(xiě)成時(shí)間戳的功能
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
// 啟用默認(rèn)類型信息,用于處理多態(tài)類型的序列化和反序列化
objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(),
ObjectMapper.DefaultTyping.NON_FINAL);
// 創(chuàng)建 GenericJackson2JsonRedisSerializer 實(shí)例,使用配置好的 ObjectMapper
GenericJackson2JsonRedisSerializer serializer =
new GenericJackson2JsonRedisSerializer(objectMapper);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
性能優(yōu)化建議
合理設(shè)置緩存過(guò)期時(shí)間:
- 本地緩存過(guò)期時(shí)間應(yīng)短于 Redis 緩存
- 根據(jù)數(shù)據(jù)更新頻率調(diào)整過(guò)期策略
緩存穿透防護(hù):
- 對(duì)空結(jié)果也進(jìn)行緩存
- 使用布隆過(guò)濾器
緩存雪崩防護(hù):
- 設(shè)置隨機(jī)過(guò)期時(shí)間
- 實(shí)現(xiàn)熔斷機(jī)制
一致性保證:
- 考慮使用消息隊(duì)列同步多節(jié)點(diǎn)本地緩存
- 對(duì)于關(guān)鍵數(shù)據(jù),可采用"先更新數(shù)據(jù)庫(kù),再刪除緩存"策略
總結(jié)
通過(guò)三個(gè)版本的演進(jìn),我們實(shí)現(xiàn)了一個(gè)從強(qiáng)耦合到完全解耦的兩級(jí)緩存系統(tǒng)。最終版本利用自定義注解和 AOP 技術(shù),既保持了代碼的簡(jiǎn)潔性,又提供了強(qiáng)大的緩存功能。這種架構(gòu)特別適合讀多寫(xiě)少、對(duì)性能要求較高的場(chǎng)景。
在實(shí)際應(yīng)用中,還需要根據(jù)具體業(yè)務(wù)特點(diǎn)調(diào)整緩存策略,并做好監(jiān)控和指標(biāo)收集,以便持續(xù)優(yōu)化緩存效果。Redis + Caffeine 實(shí)現(xiàn)高效的兩級(jí)緩存架構(gòu)
以上就是Redis+Caffeine實(shí)現(xiàn)高效兩級(jí)緩存架構(gòu)的詳細(xì)指南的詳細(xì)內(nèi)容,更多關(guān)于Redis Caffeine兩級(jí)緩存的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Windows安裝Redis并添加本地自啟動(dòng)服務(wù)的實(shí)例詳解
這篇文章主要介紹了Windows安裝Redis并添加本地自啟動(dòng)服務(wù)的實(shí)例詳解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11
redis學(xué)習(xí)之RDB、AOF與復(fù)制時(shí)對(duì)過(guò)期鍵的處理教程
這篇文章主要給大家介紹了關(guān)于redis學(xué)習(xí)之RDB、AOF與復(fù)制時(shí)對(duì)過(guò)期鍵處理的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用redis具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11
Redis 實(shí)現(xiàn)隊(duì)列原理的實(shí)例詳解
這篇文章主要介紹了Redis 實(shí)現(xiàn)隊(duì)列原理的實(shí)例詳解的相關(guān)資料,希望通過(guò)本文能幫助到大家,需要的朋友可以參考下2017-09-09
Redis安裝啟動(dòng)及常見(jiàn)數(shù)據(jù)類型
這篇文章主要介紹了Redis安裝啟動(dòng)及常見(jiàn)數(shù)據(jù)類型,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04
Redis與MySQL數(shù)據(jù)一致性問(wèn)題的策略模式及解決方案
開(kāi)發(fā)中,一般會(huì)使用Redis緩存一些常用的熱點(diǎn)數(shù)據(jù)用來(lái)減少數(shù)據(jù)庫(kù)IO,提高系統(tǒng)的吞吐量,本文將給大家介紹了Redis與MySQL數(shù)據(jù)一致性問(wèn)題的策略模式及解決方案,文中通過(guò)代碼示例介紹的非常詳細(xì),需要的朋友可以參考下2024-07-07

