0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

聊一聊在大型分布式系統(tǒng)中緩存應(yīng)該怎么玩?

jf_ro2CN3Fa ? 來源:黎杜 ? 2023-05-23 14:20 ? 次閱讀

今天我們來聊一聊在大型分布式系統(tǒng)中,緩存應(yīng)該怎么玩,從畢業(yè)到現(xiàn)在也有三年多了,大大小小的系統(tǒng)也經(jīng)歷了幾十個,今天就從各個角度來討論一下,我們的不同的緩存應(yīng)該怎么玩,才能用的高效。

我們團隊現(xiàn)在做的是直播類的產(chǎn)品,就拿抖音來說,比如說要開發(fā)一個榜一大哥、榜二大哥等各類大哥的排行榜單,要怎么開發(fā),對于抖音這個dau十億級別的產(chǎn)品,緩存的設(shè)計肯定是家常便飯。

對于一個百萬、千萬級別的接口調(diào)用,若是沒有緩存的設(shè)計,直接打到數(shù)據(jù)持久層,那將是毀滅性的災(zāi)難。之前我就經(jīng)歷過,一個接口一天幾百萬次的調(diào)用,因為緩存的設(shè)計不嚴(yán)謹(jǐn),緩存失效后,瞬間直接打在數(shù)據(jù)庫層,幸好有告警,及時修復(fù),差點就領(lǐng)了p0的故障。

大體來說緩存分為客戶端緩存服務(wù)端緩存 ,客戶端緩存我們比較常見的就是瀏覽器緩存,也就是通過http進行控制的緩存。

客戶端緩存

基于請求-應(yīng)答模式下,在大多數(shù)場景下客戶端都是通過https協(xié)議,請求后臺獲取數(shù)據(jù),若是高頻的接口一天幾百萬次的調(diào)用,即使短時間的客戶端緩存也會帶來高效的收益。

因為客戶端到服務(wù)端要經(jīng)過漫長的網(wǎng)絡(luò)鏈路,多變的網(wǎng)絡(luò)環(huán)境,數(shù)據(jù)包可能小的幾十K到大的數(shù)據(jù)包幾十M,這樣就能夠省去復(fù)雜多變的網(wǎng)絡(luò)請求的時間。

客戶端緩存減少了客戶端到服務(wù)端之間的通信次數(shù)以及成本,只要緩存可用,就能夠及時響應(yīng)數(shù)據(jù)。

客戶端緩存常見的也就是瀏覽器緩存,簡而言之也就是http緩存,不知道大家在實際開發(fā)過程中有沒有用過這段代碼:

ResponseEntity.ok().cacheControl(CacheControl.maxAge(3,TimeUnit.SECONDS)).body()

看他的包,他就是屬于springframework 框架下http包下的一個工具類。

importorg.springframework.http.CacheControl;
importorg.springframework.http.ResponseEntity;

在瀏覽器緩存中,http協(xié)議header有這么個key-value字段進行控制,叫做Cache-Control:max-age=30 ,max-age標(biāo)志該資源在客戶端緩存多少秒。

假如max-age=0,表示不緩存數(shù)據(jù),除了max-age可以控制數(shù)據(jù)的緩存狀態(tài),還有以下三個屬性來控制緩存狀態(tài)no_store、no_cache、must-revalidate 。

no_store表示不緩存數(shù)據(jù),每次都去服務(wù)器獲取 。

no_cache看起來也是不緩存的意思,但是它表示的意思是可以緩存的,只不過在使用緩存之前,都要去服務(wù)器驗證數(shù)據(jù)是否有效,是否過期,是否是最新版 。

must-revalidate和no_cache有點類似,就是緩存不過期的話可以繼續(xù)使用,過期了就需要去服務(wù)器驗證一下 。

除了Cache-Control可以使用客戶端緩存,在http里面還有一個條件請求的header更加智能的使用客戶端緩存。

條件請求是基于響應(yīng)報文返回的“Last-modified ”和“ETag ”實現(xiàn)的。Last-modified資源最后的一次修改時間,ETag則表示資源的唯一標(biāo)識,你可以理解為只要資源修改后都不一樣了

再次請求的時候在請求頭里面就會帶上"If-None-Match:ETag返回的值 ",去驗證資源是否有效。

假如有效的話,就會返回"304 Not Modified ",表示緩存資源有效還可以繼續(xù)使用。

95ba9f84-f920-11ed-90ce-dac502259ad0.png

但是這種方式我較少使用,基本上使用Cache-Control就夠了,控制好實效的時間,一般的場景都是允許短暫的不一致。

除了客戶端能夠發(fā)送Cache-Control之外,客戶端也能夠發(fā)送Cache-Control兩者進行協(xié)商使用客戶端緩存的方案。

像我在瀏覽器訪問一個連接,在輸入框敲一下回車,Request的Headers里面就有Cache-Control:max-age = 0,表示不使用緩存,直接去后臺獲取數(shù)據(jù)。

95bf71a8-f920-11ed-90ce-dac502259ad0.png

所以Cache-Control來控制客戶端緩存也不太好控制,要兩者協(xié)商好,但是Cache-Control有一個好處就是可以控制CDN緩存。

服務(wù)端緩存

CDN緩存

上面聊到Cache-Control來控制客戶端緩存,它也同時影響CDN緩存,告訴CDN客戶緩存這個接口的數(shù)據(jù)。

CDN服務(wù)一般是由第三方提供的內(nèi)容分發(fā)網(wǎng)絡(luò)服務(wù),主要是用于緩存靜態(tài)的數(shù)據(jù),比如:圖片、音頻、視頻,這些數(shù)據(jù),都是不不變的,那么命中率就很高。

不用回源獲取數(shù)據(jù),效率高,畢竟使用CDN的費用高,一般小公司也不會用,可能大公司采用。

CDN廠商花費大價錢在全國各地建立CDN的服務(wù)站點,用于用戶的就近訪問,減少響應(yīng)時間。

95c489c2-f920-11ed-90ce-dac502259ad0.png

所以這個對于應(yīng)用層的來說是0開發(fā)的,一般只要在你的服務(wù)治理平臺針對某一個接口配置一下就好了。

除了緩存靜態(tài)數(shù)據(jù),想一些動態(tài)數(shù)據(jù),但是不會經(jīng)常變的數(shù)據(jù)CDN也是可以緩存的,只不過可能緩存的時間設(shè)置的比較短,那么在高并發(fā)場井下取得的效益也是比較大的。

好了,關(guān)于CDN的也沒啥好說的,我還沒接觸過CDN開發(fā),但是項目中使用到了,就是簡單而配置一下而已,等我接觸到開發(fā),再和你們詳聊,CDN其實就是代理源站服務(wù)器緩存數(shù)據(jù)而已。

Redis緩存

在服務(wù)端緩存中Redis緩存可能是我們最常見的緩存,可以說Redis已經(jīng)是各大公司常用的緩存中間件選型之首,也不為過。

我們項目中也是在使用Redis,只不過在Redis的層面上進行封裝,包括Redis哨兵、Redis分片 ,都是基于自己的業(yè)務(wù)情況下進行二次開發(fā),然后供自己的業(yè)務(wù)使用。

在Redis的基礎(chǔ)數(shù)據(jù)類型中,有五大類型供大家選擇,包括String、List、Set、SortedSet、Hash 。這五種數(shù)據(jù)類型在百分之九十五的場景下都能夠解決,并且在這五種基本數(shù)據(jù)類型的底層運用了高效與省空間的數(shù)據(jù)結(jié)構(gòu),所以Redis的高性能之一也是因為有這些數(shù)據(jù)結(jié)構(gòu)作為支撐。

95cc0922-f920-11ed-90ce-dac502259ad0.png

圖片來源于Redis核心技術(shù)與實戰(zhàn)

比如:要實現(xiàn)一個排行榜單,凸顯直播間榜一大哥以及上榜大哥財大氣粗的實力。

那么這個明顯是按照某個字段進行排序,比如刷的抖音幣進行排序,那個在redis中List和Sorted Set都是可以實現(xiàn)有序的緩存。

List是按照寫入List順序進行存儲,而Sorted Set是按照某一個字段的權(quán)重來排序,并且可以查詢權(quán)重范圍內(nèi)的數(shù)據(jù)。

對于我們的場景List可能不太適合,因為是對數(shù)據(jù)每次都是新產(chǎn)生的,并且按照時間來順序來寫入,List集合就比較適合。

我們的場景是某個在榜大哥不斷的刷禮物,就需要重新對他進行排序,并不是按照每次新增寫入緩存的順序取數(shù),那么按照大哥刷的抖音幣的多少就可以當(dāng)做權(quán)重來排序,很好的服務(wù)我們的場景,按照時間來排序的場景Sorted Set都可以來做。

還有一些聚合統(tǒng)計的場景,比如要統(tǒng)計兩個key數(shù)據(jù)集的交集、并集、差集可以使用set集合來做。

假如某一天你的老板讓你開發(fā)一個統(tǒng)計每天新增的用戶數(shù)據(jù)功能,其實那么也就兩個集合差集,一個set集合用戶保存所有用戶的id,一個set用戶保存當(dāng)天用戶的id集合,然后當(dāng)天用戶集合與所有用戶集合的差集就是新增的用戶集合??梢允褂胹et集合中的SDIFFSTORE 命令進行實現(xiàn)。

還有一些二值統(tǒng)計的場景,也就是基于redis的Bitmap來統(tǒng)計,他并不記錄數(shù)據(jù)的本身,只能判斷是否存在,有沒有,Bitmap保存的是bit 位,所以億級別數(shù)量的存儲只要M級別的存儲單位就可以了,所以Bitmap非常的節(jié)省空間。

Bitmap中提供SETBIT設(shè)置bit位,以及GETBIT獲取某個bit位的值,還可以使用BITCOUNT統(tǒng)計bit位位1的值,比如可以統(tǒng)計某個月簽到場景。

Redis高性能的緩存給我們系統(tǒng)帶來了極大的性能提升,但是同時也會有一些類的問題,比如數(shù)據(jù)一致性的問題、緩存的三大問題(擊穿、穿透、雪崩)、與Redis網(wǎng)絡(luò)通信接口超時、Redis里面緩存的數(shù)據(jù)變多,操作時間復(fù)雜度大的導(dǎo)致Redis變慢 。

數(shù)據(jù)的一致性

數(shù)據(jù)一致性問題指的是緩存與數(shù)據(jù)庫的一致性問題,只要使用緩存就會有一致性問題,現(xiàn)在市場上都不會要求強一致性,都是追求最終一致性

緩存按照是否可寫分為讀寫緩存與只讀緩存 ,大部分是只讀緩存,現(xiàn)在我們來討論一下只讀緩存一致性問題。

只讀緩存的一致性問題包括以下以下兩種場景:

先更新數(shù)據(jù)庫,然后刪除緩存。

先刪除緩存,然后更新數(shù)據(jù)庫。

但是這兩種場景在高并發(fā)場景下都會有問題,先來看看第一種場景:先更新數(shù)據(jù)庫,然后刪除緩存。

這種場景也會有一致性問題,當(dāng)我們更新了數(shù)據(jù)庫后,然后刪除緩存,刪除緩存失敗了,此時請求讀取的緩存還是舊的值。

這種情況下的解決方案就是重試 ,可以在應(yīng)用層重試,也可以放入消息隊列里面重試,當(dāng)重試次數(shù)達到最大的限制,就需要發(fā)送告警進行人工排查了。

95d23bf8-f920-11ed-90ce-dac502259ad0.png

或者設(shè)置比較短的緩存失效時間,短暫的不一致性,也是可以接受的。

第二種場景:先刪除緩存,然后再更新數(shù)據(jù)庫。在高并發(fā)場景下也有可能數(shù)據(jù)不一致。

假如線程A刪除了緩存,但是還沒有更新數(shù)據(jù)庫,然后線程B讀取緩存發(fā)現(xiàn)緩存缺失,然后從數(shù)據(jù)庫里面讀取舊值,并且緩存到Redis中,后面的請求就會從Redis中讀取舊值。

95d83576-f920-11ed-90ce-dac502259ad0.png

這種場景市面上推薦使用延遲雙刪的方案進行解決,就是在請求A刪除緩存后,更新數(shù)據(jù)庫,然后等一段時間刪除緩存,請求A的sleep的時間大于請求B的讀取數(shù)據(jù)寫入緩存的時間。

但是這種一般等的時間不調(diào)好估計,而且在高并發(fā)場景下,讓線程去等無疑是降低性能,這個通常是不允許的。

所以一般建議采用第一種方案,先更新數(shù)據(jù)庫,然后刪除緩存的方式。

我們項目中也會用到讀寫緩存,之前遇到一個需求就是,在直播過程中,主播的公屏的流水,要顯示用戶中獎的橫幅,也就是“恭喜某某在某某直播間抽中了XXX禮物”。

禮物的抽獎流水之前就已經(jīng)發(fā)送消息隊列了,所以只要監(jiān)聽對應(yīng)抽獎流水topic就行了,然后將中獎的流水按照排序規(guī)則放入Redis中。然后客戶端從Redis中讀取,其實很簡單,流水也不需要存庫,只要展示就行了。

95dfa180-f920-11ed-90ce-dac502259ad0.png

所以Redis的應(yīng)用場景還是很多的,幾乎可以覆蓋開發(fā)中的95%以上的需求

緩存擊穿、穿透、雪崩

使用分布式緩存還會涉及到緩存的三大問題,也就是緩存擊穿、緩存穿透、緩存雪崩 。

緩存穿透 的解決方案有兩種:

緩存空對象:代碼維護較簡單,但是效果不好。

布隆過濾器:代碼維護復(fù)雜,效果很好。

緩存空對象是指當(dāng)一個請求過來緩存中和數(shù)據(jù)庫中都不存在該請求的數(shù)據(jù),第一次請求就會跳過緩存進行數(shù)據(jù)庫的訪問,并且訪問數(shù)據(jù)庫后返回為空,此時也將該空對象進行緩存。

若是再次進行訪問該空對象的時候,就會直接擊中緩存,而不是再次數(shù)據(jù)庫,緩存空對象實現(xiàn)的原理圖如下:

95e529e8-f920-11ed-90ce-dac502259ad0.png

緩存空對象的實現(xiàn)代碼如下:

publicclassUserServiceImpl{
@Autowired
UserDAOuserDAO;
@Autowired
RedisCacheredisCache;

publicUserfindUser(Integerid){
Objectobject=redisCache.get(Integer.toString(id));
//緩存中存在,直接返回
if(object!=null){
//檢驗該對象是否為緩存空對象,是則直接返回null
if(objectinstanceofNullValueResultDO){
returnnull;
}
return(User)object;
}else{
//緩存中不存在,查詢數(shù)據(jù)庫
Useruser=userDAO.getUser(id);
//存入緩存
if(user!=null){
redisCache.put(Integer.toString(id),user);
}else{
//將空對象存進緩存
redisCache.put(Integer.toString(id),newNullValueResultDO());
}
returnuser;
}
}
}

布隆過濾器是一種基于概率的數(shù)據(jù)結(jié)構(gòu),主要用來判斷某個元素是否在集合內(nèi),它具有運行速度快(時間效率),占用內(nèi)存小的優(yōu)點(空間效率),但是有一定的誤識別率和刪除困難的問題。它只能告訴你某個元素一定不在集合內(nèi)或可能在集合內(nèi)。

在計算機科學(xué)中有一種思想:空間換時間,時間換空間。一般兩者是不可兼得,而布隆過濾器運行效率和空間大小都兼得,它是怎么做到的呢?

在布隆過濾器中引用了一個誤判率的概念,即它可能會把不屬于這個集合的元素認(rèn)為可能屬于這個集合,但是不會把屬于這個集合的認(rèn)為不屬于這個集合,布隆過濾器的特點如下:

一個非常大的二進制位數(shù)組 (數(shù)組里只有0和1)

若干個哈希函數(shù)

空間效率和查詢效率高

不存在漏報(False Negative):某個元素在某個集合中,肯定能報出來。

可能存在誤報(False Positive):某個元素不在某個集合中,可能也被爆出來。

不提供刪除方法,代碼維護困難。

位數(shù)組初始化都為0,它不存元素的具體值,當(dāng)元素經(jīng)過哈希函數(shù)哈希后的值(也就是數(shù)組下標(biāo))對應(yīng)的數(shù)組位置值改為1。

實際布隆過濾器存儲數(shù)據(jù)和查詢數(shù)據(jù)的原理圖如下:

95eceed0-f920-11ed-90ce-dac502259ad0.png

緩存擊穿是指一個key非常熱點,在不停的扛著大并發(fā),大并發(fā)集中對這一個點進行訪問,當(dāng)這個key在失效的瞬間,持續(xù)的大并發(fā)就穿破緩存,直接請求數(shù)據(jù)庫,瞬間對數(shù)據(jù)庫的訪問壓力增大。

緩存擊穿這里強調(diào)的是并發(fā),造成緩存擊穿的原因有以下兩個:

該數(shù)據(jù)沒有人查詢過 ,第一次就大并發(fā)的訪問。(冷門數(shù)據(jù))

添加到了緩存,reids有設(shè)置數(shù)據(jù)失效的時間 ,這條數(shù)據(jù)剛好失效,大并發(fā)訪問(熱點數(shù)據(jù))

對于緩存擊穿的解決方案:

加鎖,用戶出現(xiàn)大并發(fā)訪問的時候,在查詢緩存的時候和查詢數(shù)據(jù)庫的過程加鎖,只能第一個進來的請求進行執(zhí)行,當(dāng)?shù)谝粋€請求把該數(shù)據(jù)放進緩存中,接下來的訪問就會直接集中緩存,防止了緩存擊穿。

95f23f52-f920-11ed-90ce-dac502259ad0.png

不設(shè)置熱點key的失效時間

緩存雪崩 是指在某一個時間段,緩存集中過期失效。此刻無數(shù)的請求直接繞開緩存,直接請求數(shù)據(jù)庫。

造成緩存雪崩的可能原因有:

reids宕機

大部分?jǐn)?shù)據(jù)失效

對于緩存雪崩的解決方案有以下兩種:

搭建高可用的集群,防止單機的redis宕機。

設(shè)置不同的過期時間,防止同一時間內(nèi)大量的key失效。

接口超時&操作時間復(fù)雜度高

Redis數(shù)據(jù)第三方緩存中間件,要與Redis通信,必須經(jīng)過網(wǎng)絡(luò),那么經(jīng)過網(wǎng)絡(luò)就有可能出現(xiàn)網(wǎng)絡(luò)超時的現(xiàn)象。

之前我們也出現(xiàn)過,某個機房因為網(wǎng)絡(luò)波動,出現(xiàn)了一系列的Redis查詢網(wǎng)絡(luò)超時的告警。

所以為了解決一時的網(wǎng)絡(luò)超時,我們有可能還要做好接口重試的機制,提高接口的可用性。

并且對Redis五種基本數(shù)據(jù)類型的底層數(shù)據(jù)結(jié)構(gòu)熟悉的,Redis中對集合類型的操作HGETALL、SMEMBERS,以及對集合進行聚合統(tǒng)計 等,時間復(fù)雜度都是O(N)

那么Redis中存儲的數(shù)據(jù)越多,這個N就越大,操作的復(fù)雜度就越高,這就是所謂的bidkey現(xiàn)象,已經(jīng)出現(xiàn)查詢阻塞了。

當(dāng)然出現(xiàn)這種問題時,可以將bigkey按照一定規(guī)律進行拆分,這樣分成多個key進行存儲,查詢的效率就會變高。

當(dāng)然Redis的數(shù)據(jù)分片解決方案也可以,將原來一個實例中存儲全量數(shù)據(jù),按照16384進行crc16(key) % 16384 決定數(shù)據(jù)存儲于哪個槽中。

這樣擴展性也比較好,不過一般優(yōu)先推薦拆分key的方案,這樣實現(xiàn)成本低,實現(xiàn)簡單。

緩存消息隊列玩法

有一些場景還可以使用消息隊列進行更新緩存,用戶更新數(shù)據(jù),異步的發(fā)送消息隊列,消費者就可以監(jiān)聽消息隊列的消息,消費消息后更新緩存。

因為有些數(shù)據(jù)的更新是需要發(fā)送消息隊列的,被其他消費者監(jiān)聽使用,所以你只要監(jiān)聽消息隊列就行了。

并且消息的隊列的消息由消息隊列的方式來保證,包括生產(chǎn)者可靠的發(fā)送消息隊列,通過ack以及重試保證,消息隊列本身通過持久化機制來保證,而消費者也是通過消費后手動ack來確認(rèn)消息消費。

95f74196-f920-11ed-90ce-dac502259ad0.png

消息對壘更新緩存

定時任務(wù)

定時任務(wù)其實就是本地緩存了,在分布式系統(tǒng)中,定時任務(wù)就是每個服務(wù)中都會緩存一份,這樣數(shù)據(jù)不一致性也會加大。

但是在某些場景下,他帶來的收益也是非常可觀的,比如說某個場景下你要查詢一些安全中臺的白名單/黑名單列表,而且這些列表不會經(jīng)常變,可能需求上線后只要配置一下就ok了,后面的更改頻率也是非常的低。

但是你的接口可能是高流量接口,每次用戶進來都會請求一次,進行判斷,而且用戶是千萬級別的,那有可能一天的請求就是上百萬次的請求。

那你有兩種選擇來請求安全中臺的白名單/黑名單列表,要么就是實時請求,要么就是定時任務(wù)請求本地緩存一份,然后查詢只要從本地獲取就行了。

在這種情況下肯定是定時任務(wù)請求,帶來的效益更大,在SprngBoot項目中開啟定時任務(wù)很簡單,只需要在你的啟動主類上加上這個注解:**@EnableScheduling**

然后在需要定時任務(wù)的執(zhí)行類的方法上加上這個注解:**@Scheduled(cron = "0 0 2 * * ?")** , 其實就是cron表達式,執(zhí)行的規(guī)律隔多久執(zhí)行一次。

只要你的時間配置的足夠短,這樣數(shù)據(jù)也是近實時的,不會差太遠,你可以配置成30秒或者幾十秒執(zhí)行一次,或者幾分鐘執(zhí)行一次都可以,這個可以和產(chǎn)品進行協(xié)商,看產(chǎn)品可以接受多久的延遲。

然后,查詢的中臺的列表數(shù)據(jù)緩存在本地的一個map里面,用戶的uid作為map的key,然后后面需要查詢的時候,直接從map里面獲取。

這樣就不用每次請求過來都會實時的調(diào)用中臺的http/rpc接口查詢數(shù)據(jù),直接從本地獲取提高效率,這也是空間換時間的思想。

接口超時

這里需要注意的是,就是要提高你的接口調(diào)用的可用性 ,畢竟中臺屬于另一個服務(wù),那么服務(wù)之間涉及遠程調(diào)用,就有可能存在超時的現(xiàn)象。

那么你就要確保你的接口99.9%可用,對于接口超時,你可以就要設(shè)置接口重試 。

因為有時候可能是網(wǎng)絡(luò)的原因?qū)е碌囊粫r超時,設(shè)置被調(diào)用方一時因為網(wǎng)絡(luò)抖動導(dǎo)致超時,那么重試成功的概率就可能比較高。

一般重試的次數(shù)會設(shè)置為2-3 次比較合理,除非網(wǎng)絡(luò)故障了或者接口一直調(diào)不通,這樣的話就需要及時告警,通知到開發(fā)人員,及時檢查到底是哪里的問題,確保好接口的兜底方案。

并且還要設(shè)置每次的超時時間,設(shè)置超時時間也是非常的重要,假如超時時間設(shè)置的太短,還沒有查出來就已經(jīng)超時了,這樣就會導(dǎo)致頻繁超時,浪費資源。

要是設(shè)置的超時間太長,那么線程就會一直阻塞在那里等待調(diào)用的結(jié)果返回,這樣在高并發(fā)場景下,就會資源耗盡,系統(tǒng)崩潰。

所以我給你的建議就是可以結(jié)合線上服務(wù)所在服務(wù)器的配置以及qps進行配置,配置一個合理的超時時間,合理的時間內(nèi)能夠超時返回并且不會導(dǎo)致資源耗盡。

重試這種機制,在很多中間件的思想中都會涉及到,比如:在分布式事務(wù)中2PC和3PC

2PC在第二階段提交失敗,那么只能不斷重試,直到所有參與者都成功(回滾或者提交成功)。

95ff3248-f920-11ed-90ce-dac502259ad0.png

因為除了重試,沒有更好的辦法,只能不斷重試直到都成功,而且多數(shù)情況可能都是一時的網(wǎng)絡(luò)抖動的原因?qū)е碌?,這樣重試成功的概率就非常高。

批量查詢數(shù)據(jù)

定時任務(wù)緩存其實也是一種集中式緩存 ,假如緩存的數(shù)據(jù)量也比較大,那么在接口調(diào)用時就需要批量獲取,但是一次性又不能查詢太多,一般嚴(yán)謹(jǐn)?shù)闹信_設(shè)計,都會都傳參進行參數(shù)校驗。

因為對于調(diào)用方完全是透明的,不可信任的 ,什么參數(shù)都有可能傳過來,假如調(diào)用方一下子查幾萬個或者是幾萬個數(shù)據(jù)集,那不是接口都爆了。

所以,必須要做好分批調(diào)用,調(diào)用方分批、分頁調(diào)用 ,中臺對參數(shù)做校驗一次只能查詢幾百個,這樣子去規(guī)定,保證接口的可用性。

調(diào)用方的偽代碼如下:

booleanend=true;
intpage=1;
intpageSize=500;
while(end){
//設(shè)置好超時,失敗重試
Datadata=getData(page,pageSize);

Mapmap=data.getDataMap();
//data里面的字段hashMore表示查詢下一個分頁是否還有數(shù)據(jù)
end=Objects.equals(data.getHasMore(),1);
page++;
}

本地緩存@Cacheable

@Cacheable是springframework下提供的緩存注解類,在spring中定義了 org.springframework.cache.Cacheorg.springframework.cache.CacheManager 接口來統(tǒng)一實現(xiàn)cache。

除了@Cacheable用戶緩存數(shù)據(jù),也可以使用@CachePut用于緩存更新,這兩個是比較常用的。

他們緩存的數(shù)據(jù)也是緩存在本地和定時任務(wù)一樣,除了使用@Cacheable還可使用谷歌研發(fā)的cache工具類LoadingCache ,他也是本地緩存的一種,并且可以設(shè)置緩存的大小,重新刷新的時間。

相對比Cacheable會更加方便,因為你發(fā)現(xiàn)Cacheable還缺少緩存時間和緩存更新的屬性配置實現(xiàn),可能還需要自己再二級開發(fā),比如加入緩存失效時間、多少秒后自動更新更新緩存,這樣Cacheable才能更加完善。

privatefinalLoadingCache>tagCache=CacheBuilder.newBuilder()
.concurrencyLevel(4)
.expireAfterWrite(5,TimeUnit.SECONDS)
.initialCapacity(8)
.maximumSize(10000)
.build(newCacheLoader>(){

@Override
publicListload(StringcacheKey){
returnget(cacheKey);
}

@Override
publicListenableFuture>reload(Stringkey,ListoldValue){
ListenableFutureTask>task=ListenableFutureTask.create(()->get(key));
executorService.execute(task);
returntask;
}
}
);

相比@Cacheable就是代碼比較冗余,注解方式會更加直觀簡潔,不過LoadingCache的靈活性更高。

我們自己對Cacheable進行了擴展,加入了實效時間以及自動更新的方案,這樣的Cacheable更加適用于我們的業(yè)務(wù)。

9604d27a-f920-11ed-90ce-dac502259ad0.png

總結(jié)

在項目中可能多種緩存并行使用,使用不同的緩存都要基于業(yè)務(wù)進行考量,包括成本,數(shù)據(jù)一致性,性能問題等,不同的緩存方式有不同的特點。

redis緩存分布式系統(tǒng)中共享數(shù)據(jù),性能高效,擴展性強,redis可以基于數(shù)據(jù)分片、哨兵模式進行擴展,但是要額外的費用進行運維,并且引入第三方中間件,系統(tǒng)的復(fù)雜度也高,排查困難,而且每次都要經(jīng)過網(wǎng)絡(luò)調(diào)用,有可能存在網(wǎng)絡(luò)超時的現(xiàn)象,數(shù)據(jù)丟失,所以要做好數(shù)據(jù)兼容,兜底方案。

本地緩存使用簡單方便,低成本,每個服務(wù)實例都會冗余一份數(shù)據(jù),一致性問題加大,但是效率非常高效,不用通過網(wǎng)絡(luò)傳輸獲取數(shù)據(jù)。

一般我們的項目中都會分配6-8G的內(nèi)存,所以一般本地緩存都夠使用的,所以一般能用本地緩存的話,都可以優(yōu)先使用本地緩存。

一些場景不得不使用分布式緩存的,就是用Redis緩存來共享數(shù)據(jù),綜合使用不同的緩存來解決項目中的問題。

從上面的幾種緩存方案中可以看到重試方案,重試是解決很多問題的重要手段之一,但是重試次數(shù),重試的超時時間也要控制,防止資源耗盡,在大多數(shù)場景下,重試都可以解決,要是重試次數(shù)達到限制都不成功,就有可能是網(wǎng)絡(luò)故障或者接口問題,此時就需要應(yīng)用發(fā)送告警通知開發(fā)人員進行排查,這是兜底方案。

客戶端緩存和CDN緩存這兩個對于服務(wù)端來說,比較少使用,一般公司都是用不到,大家可以把關(guān)注點放在服務(wù)端緩存中。





審核編輯:劉清

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 過濾器
    +關(guān)注

    關(guān)注

    1

    文章

    419

    瀏覽量

    19385
  • Redis
    +關(guān)注

    關(guān)注

    0

    文章

    368

    瀏覽量

    10780

原文標(biāo)題:大型分布式系統(tǒng)中,緩存就該這么玩

文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    分布式軟件系統(tǒng)

    數(shù)據(jù)庫管理員為基礎(chǔ)的分層控制結(jié)構(gòu),但是每個局部數(shù)據(jù)庫管理員都具有高度的自主權(quán)。 2、分布式數(shù)據(jù)庫系統(tǒng)數(shù)據(jù)獨立性概念也同樣重要,然而增加了個新的概念,就是
    發(fā)表于 07-22 14:53

    基于Java的分布式緩存優(yōu)化在網(wǎng)絡(luò)管理系統(tǒng)的應(yīng)用

    基于Java的分布式緩存優(yōu)化在網(wǎng)絡(luò)管理系統(tǒng)的應(yīng)用討論建立JMX管理框架上的網(wǎng)絡(luò)性能管理系統(tǒng)
    發(fā)表于 09-19 09:20

    淺談分布式緩存技術(shù)

    視圖,API接口簡單,與拓?fù)?,動態(tài)擴展或故障恢復(fù)無關(guān),無需手動配置,自動選擇備份節(jié)點,大多數(shù)緩存系統(tǒng)提供圖形化管理控制臺,簡單易用保持。分布式緩存優(yōu)勢1,提高數(shù)據(jù)讀取速度2,提高
    發(fā)表于 11-16 15:45

    分布式系統(tǒng)的優(yōu)勢是什么?

    當(dāng)討論分布式系統(tǒng)時,我們面臨許多以下這些形容詞所描述的 同類型: 分布式的、刪絡(luò)的、并行的、并發(fā)的和分散的。分布式處理是個相對較新的領(lǐng)域,
    發(fā)表于 03-31 09:01

    AltiumFill,Polygon Pour,Plane的區(qū)別和用法

    Fill會造成短路,為什么還用它呢?來AltiumFill,Polygon Pour,Plane的區(qū)別和用法
    發(fā)表于 04-25 06:29

    stm32的低功耗調(diào)試

    ,stm32l15x系列最大運行在32M;如果在最大頻率下運行,那么mcu功耗是214uA*32=6848uA=6.8mA,如果還有外設(shè),那么整個系統(tǒng)功耗可能會更多。因此,調(diào)試功耗的時候,可以用上面的6.8mA當(dāng)做個參考,判
    發(fā)表于 08-11 08:18

    分布式KVM系統(tǒng)解決方案,坐席協(xié)作管理有什么用怎么用哪里用

    ,分布式坐席成為國內(nèi)眾多廠商集體發(fā)力的領(lǐng)域,也是國內(nèi)各行業(yè)中小型指揮中心應(yīng)用最廣泛的指揮控制系統(tǒng)解決方案。那么我們控制室的
    發(fā)表于 03-12 15:09 ?3619次閱讀

    分布式系統(tǒng)的數(shù)據(jù)庫和緩存操作順序

    分布式系統(tǒng),緩存和數(shù)據(jù)庫同時存在時,如果有寫操作的時候,先操作數(shù)據(jù)庫還是先操作緩存呢?先思考
    的頭像 發(fā)表于 05-03 14:36 ?2128次閱讀
    <b class='flag-5'>分布式</b><b class='flag-5'>系統(tǒng)</b><b class='flag-5'>中</b>的數(shù)據(jù)庫和<b class='flag-5'>緩存</b>操作順序

    分布式系統(tǒng)的CAP理論

    CAP理論最早發(fā)表于2000年,由加州伯克利的教授首先在ACM PODC會議上提出猜想,兩年之后,被麻省理工學(xué)院的教授Seth Gilbert和Nancy Lynch從理論上證明。從此之后,它成了分布式系統(tǒng)領(lǐng)域的公認(rèn)定理。
    的頭像 發(fā)表于 05-03 18:00 ?2541次閱讀
    <b class='flag-5'>聊</b><b class='flag-5'>一</b><b class='flag-5'>聊</b><b class='flag-5'>分布式</b><b class='flag-5'>系統(tǒng)</b>的CAP理論

    干貨:解決分布式緩存與數(shù)據(jù)庫的雙存儲雙寫

    分布式緩存是現(xiàn)在很多分布式應(yīng)用必不可少的組件,但是用到了分布式緩存,就可能會涉及到
    的頭像 發(fā)表于 09-03 10:58 ?2498次閱讀
    干貨:解決<b class='flag-5'>分布式</b><b class='flag-5'>緩存</b>與數(shù)據(jù)庫的雙存儲雙寫

    為什么需要分布式鎖 基于Zookeeper鎖安全嗎

    這篇文章我想和你,關(guān)于 Redis 分布式鎖的「安全性」問題。 Redis 分布式鎖的話題,很多文章已經(jīng)寫爛了,我為什么還要寫這篇文章
    的頭像 發(fā)表于 08-10 18:06 ?5521次閱讀

    分布式系統(tǒng)架構(gòu)設(shè)計異地多活是什么

    軟件開發(fā)領(lǐng)域,「異地多活」是分布式系統(tǒng)架構(gòu)設(shè)計的座高峰,很多人經(jīng)常聽過它,但很少人理解其中的原理。 異地多活到底是什么?為什么需要異地多活?它到底解決了什么問題?究竟是怎么解決的?
    的頭像 發(fā)表于 11-12 09:42 ?1789次閱讀
    <b class='flag-5'>分布式</b><b class='flag-5'>系統(tǒng)</b>架構(gòu)設(shè)計<b class='flag-5'>中</b>異地多活是什么

    【職場雜談】與嵌入物聯(lián)網(wǎng)架構(gòu)師幾個話題

    【職場雜談】與嵌入物聯(lián)網(wǎng)架構(gòu)師幾個話題
    的頭像 發(fā)表于 08-23 09:19 ?1213次閱讀
    【職場雜談】與嵌入<b class='flag-5'>式</b>物聯(lián)網(wǎng)架構(gòu)師<b class='flag-5'>聊</b><b class='flag-5'>一</b><b class='flag-5'>聊</b>幾個話題

    Redis分布式鎖真的安全嗎?

    今天我們來Redis分布式鎖。
    的頭像 發(fā)表于 11-02 14:07 ?892次閱讀

    芯片設(shè)計的NDR是什么?

    今天突然想route相關(guān)的問題,講講NDR是什么,我也梳理總結(jié)下我對NDR的認(rèn)識。
    的頭像 發(fā)表于 12-06 15:14 ?1364次閱讀