Redis+Caffeine多級緩存數(shù)據(jù)一致性解決方案
問題分析
通過Redis+Caffeine,似乎可以完成一級、二級緩存中數(shù)據(jù)的同步,如果在單節(jié)點項目中是沒有問題的,但是,在分布式場景下是有問題的,看下圖:

說明:
- 部署了2個transport-info微服務(wù)節(jié)點,每個微服務(wù)都有自己進程級的一級緩存,都共享同一個Redis作為二級緩存
- 假設(shè),所有節(jié)點的一級和二級緩存都是空的,此時,用戶通過節(jié)點1查詢運單物流信息,在完成后,節(jié)點1的caffeine和Redis中都會有數(shù)據(jù)
- 接著,系統(tǒng)通過節(jié)點2更新了數(shù)據(jù),此時節(jié)點2中的caffeine和Redis都是更新后的數(shù)據(jù)
- 用戶還是進行查詢動作,依然是通過節(jié)點1查詢,此時查詢到的將是舊的數(shù)據(jù),也就是出現(xiàn)了一級緩存與二級緩存之間的數(shù)據(jù)不一致的問題
解決方案
如何解決該問題呢?可以通過消息的方式解決,就是任意一個節(jié)點數(shù)據(jù)更新了數(shù)據(jù),發(fā)個消息出來,通知其他節(jié)點,其他節(jié)點接收到消息后,將自己caffeine中相應(yīng)的數(shù)據(jù)刪除即可。
關(guān)于消息的實現(xiàn),可以采用RabbitMQ,也可以采用Redis的消息訂閱發(fā)布來實現(xiàn),在這里為了應(yīng)用技術(shù)的多樣化,所以采用Redis的訂閱發(fā)布來實現(xiàn)。
方案概述

Redis 發(fā)布訂閱(pub/sub)是一種消息通信模式:發(fā)送者(pub)發(fā)送消息,訂閱者(sub)接收消息

當(dāng)有新消息通過 publish 命令發(fā)送給頻道 channel1 時, 這個消息就會被發(fā)送給訂閱它的三個客戶端。
Redis的訂閱發(fā)布功能與傳統(tǒng)的消息中間件(如:RabbitMQ)相比,相對輕量一些,針對數(shù)據(jù)準(zhǔn)確和安全性要求沒有那么高的場景可以直接使用。
代碼實現(xiàn)
- 在RedisConfig增加訂閱的配置:
/**
* 配置訂閱,用于解決Caffeine一致性的問題
*
* @param connectionFactory 鏈接工廠
* @param listenerAdapter 消息監(jiān)聽器
* @return 消息監(jiān)聽容器
*/
@Bean
public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.addMessageListener(listenerAdapter, new ChannelTopic(CHANNEL_TOPIC));
return container;
}
- 編寫
RedisMessageListener用于監(jiān)聽消息,刪除caffeine中的數(shù)據(jù)。
/**
* redis消息監(jiān)聽,解決Caffeine一致性的問題
*/
@Slf4j
@Component
public class RedisMessageListener extends MessageListenerAdapter {
@Resource
private Cache<String, TransportInfoDTO> transportInfoCache;
@Override
public void onMessage(Message message, byte[] pattern) {
// 獲取到消息中的運單id
String transportOrderId = Convert.toStr(message);
log.info("redis消息監(jiān)聽緩存變更,運單id:{}", transportOrderId);
// 將本jvm中的緩存刪除掉
this.transportInfoCache.invalidate(transportOrderId);
}
}
- 更新數(shù)據(jù)后向redis發(fā)送消息:
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
@CachePut(value = "transport-info", key = "#p0")
public TransportInfoEntity saveOrUpdate(String transportOrderId, TransportInfoDetail infoDetail) {
// 省略代碼
// 清除緩存中的數(shù)據(jù)
// this.transportInfoCache.invalidate(transportOrderId);
// Caffeine本地緩存一致性,發(fā)布訂閱消息到redis,通知訂閱者更新緩存
this.stringRedisTemplate.convertAndSend(RedisConfig.CHANNEL_TOPIC, transportOrderId);
// 保存/更新到MongoDB
return this.mongoTemplate.save(transportInfoEntity);
}
總結(jié)
本文主要講解了在使用Redis和Caffeine多級緩存時使用Redis的發(fā)布訂閱模式來保證兩級緩存的數(shù)據(jù)一致性。本地緩存是基于服務(wù)本地內(nèi)存的,分布式系統(tǒng)中當(dāng)緩存更新時,可能造成多個實例間的本地緩存不一致問題。可以使用RabbitMQ或者Redis的發(fā)布訂閱來解決本地緩存不一致的問題。
以上就是Redis+Caffeine多級緩存數(shù)據(jù)一致性解決方案的詳細(xì)內(nèi)容,更多關(guān)于Redis Caffeine緩存數(shù)據(jù)一致性的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Redis實現(xiàn)短信登錄的企業(yè)實戰(zhàn)
本文主要介紹了Redis實現(xiàn)短信登錄的企業(yè)實戰(zhàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07
Redis中redis-shake實現(xiàn)數(shù)據(jù)遷移同步
在業(yè)務(wù)環(huán)境中使用Redis進行跨區(qū)域數(shù)據(jù)遷移和同步的挑戰(zhàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-11-11
Redis可視化工具Redis?Desktop?Manager的具體使用
本文主要介紹了Redis可視化工具Redis?Desktop?Manager的具體使用,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-12-12

