今天我們來聊一聊在大型分布式系統(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ù)使用。
但是這種方式我較少使用,基本上使用Cache-Control就夠了,控制好實效的時間,一般的場景都是允許短暫的不一致。
除了客戶端能夠發(fā)送Cache-Control之外,客戶端也能夠發(fā)送Cache-Control兩者進行協(xié)商使用客戶端緩存的方案。
像我在瀏覽器訪問一個連接,在輸入框敲一下回車,Request的Headers里面就有Cache-Control:max-age = 0,表示不使用緩存,直接去后臺獲取數(shù)據(jù)。
所以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)時間。
所以這個對于應(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)作為支撐。
圖片來源于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ā)送告警進行人工排查了。
或者設(shè)置比較短的緩存失效時間,短暫的不一致性,也是可以接受的。
第二種場景:先刪除緩存,然后再更新數(shù)據(jù)庫。在高并發(fā)場景下也有可能數(shù)據(jù)不一致。
假如線程A刪除了緩存,但是還沒有更新數(shù)據(jù)庫,然后線程B讀取緩存發(fā)現(xiàn)緩存缺失,然后從數(shù)據(jù)庫里面讀取舊值,并且緩存到Redis中,后面的請求就會從Redis中讀取舊值。
這種場景市面上推薦使用延遲雙刪的方案進行解決,就是在請求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中讀取,其實很簡單,流水也不需要存庫,只要展示就行了。
所以Redis的應(yīng)用場景還是很多的,幾乎可以覆蓋開發(fā)中的95%以上的需求
緩存擊穿、穿透、雪崩
使用分布式緩存還會涉及到緩存的三大問題,也就是緩存擊穿、緩存穿透、緩存雪崩 。
緩存穿透 的解決方案有兩種:
緩存空對象:代碼維護較簡單,但是效果不好。
布隆過濾器:代碼維護復(fù)雜,效果很好。
緩存空對象是指當(dāng)一個請求過來緩存中和數(shù)據(jù)庫中都不存在該請求的數(shù)據(jù),第一次請求就會跳過緩存進行數(shù)據(jù)庫的訪問,并且訪問數(shù)據(jù)庫后返回為空,此時也將該空對象進行緩存。
若是再次進行訪問該空對象的時候,就會直接擊中緩存,而不是再次數(shù)據(jù)庫,緩存空對象實現(xiàn)的原理圖如下:
緩存空對象的實現(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ù)的原理圖如下:
緩存擊穿是指一個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ù)放進緩存中,接下來的訪問就會直接集中緩存,防止了緩存擊穿。
不設(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)消息消費。
消息對壘更新緩存
定時任務(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在第二階段提交失敗,那么只能不斷重試,直到所有參與者都成功(回滾或者提交成功)。
因為除了重試,沒有更好的辦法,只能不斷重試直到都成功,而且多數(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.Cache 和 org.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 publicList load(StringcacheKey){ returnget(cacheKey); } @Override publicListenableFuture >reload(Stringkey,List
oldValue){ ListenableFutureTask >task=ListenableFutureTask.create(()->get(key)); executorService.execute(task); returntask; } } );
相比@Cacheable就是代碼比較冗余,注解方式會更加直觀簡潔,不過LoadingCache的靈活性更高。
我們自己對Cacheable進行了擴展,加入了實效時間以及自動更新的方案,這樣的Cacheable更加適用于我們的業(yè)務(wù)。
總結(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ù)端緩存中。
審核編輯:劉清
-
過濾器
+關(guān)注
關(guān)注
1文章
419瀏覽量
19385 -
Redis
+關(guān)注
關(guān)注
0文章
368瀏覽量
10780
原文標(biāo)題:大型分布式系統(tǒng)中,緩存就該這么玩
文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論