1 常見概念
在合理應用緩存前,需要了解緩存領域裡相關的幾個常用術語:
1)緩存命中:表示資料能夠從緩存中擷取,不需要回源;
3)Cache miss:表示沒有命中緩存,如果緩存記憶體中還有記憶體空間的話,會将資料加入到緩存中;
4)存儲成本:當沒有命中緩存時,回源擷取後會将資料放置到存儲中,整個将資料放置到存儲空間所需要的時間以及空間稱之為存儲成本;
5)緩存失效:當源資料發生變更後,意味着緩存中的資料失效;
6)緩存污染:将不經常通路的資料放置到緩存存儲空間中,以至于高頻通路的資料無法放置到緩存中;
7)替代政策:當資料放置到緩存空間時,由于空間不足時,就需要從緩存空間中去除已有的資料,選擇去除哪些資料就是由替代政策決定的。常見的替代政策有如下這些:
Least-Recently-Used(LRU):替換掉最近被請求最少的對象,根據資料的曆史通路記錄來進行淘汰資料,其核心思想是“如果資料最近被通路過,那麼将來被通路的幾率也更高”。LRU針對熱點資料命中率較高,如果是偶發性、大批量操作會導緻命中率急劇下降,緩存污染嚴重,可以采用LRU-K進行優化;
Least-Frequently-Used(LFU):替換掉通路次數最少的對象,如果有些對象在之前被頻繁通路,但是之後通路頻率下降,就會存在“緩存污染”的情況,這些對象依然會駐留在存儲空間中;
SIZE:替換占用空間最大的對象,這種方式企圖通過去除大對象而保留多個小對象來提供命中率,但是又可能保留的小對象并沒有經常通路,造成緩存污染;
First in First Out(FIFO):FIFO通過一個隊列去跟蹤所有的緩存對象,最近最常用的緩存對象放在後面,而更早的緩存對象放在前面,當緩存容量滿時,排在前面的緩存對象會被踢走,然後把新的緩存對象加進去
更多的緩存替代政策,可以參考這篇文章。由于存儲空間有限,替代政策要解決的核心問題是盡量保留高頻通路的緩存資料,降低緩存污染以提升緩存命中率和整體的緩存效率,難點在于,需要基于資料曆史通路情況,以一種合适的對未來通路情況的預估才能找到更佳的政策。
2 通路緩存場景分析
使用緩存通常的操作是,請求先通路緩存資料,如果緩存中不存在的話,就會回源到資料庫中然後将資料寫入到緩存中;如果存在的話就直接傳回資料。從整個過程來看,緩存層就處于資料通路的前置環節,分擔資料庫在高并發容易出現系統故障的風險,是以在使用過程中需要對緩存層很謹慎的進行分析。在通路緩存資料時,有常見的三大場景:緩存穿透、緩存擊穿以及緩存雪崩。
2.1 緩存穿透
現象:每次請求直接穿透緩存層,直接回源到資料庫中,給資料庫帶來了巨大通路壓力,甚至當機。
原因:通路資料會先通路緩存,如果資料不存在緩存中才會查詢資料庫,但是如果查詢資料庫也查詢不出來資料,也是說目前通路資料永遠不會寫入緩存中。這樣就導緻了,通路一定不存在的資料,就相當于緩存層形同虛設,每次請求都會到db層,造成資料庫負擔過大。
解決方案:
方案一:采用bloom filter儲存緩存過的key,在通路請求到來時可以過濾掉不存在的key,防止這些請求到db層;
方案二:如果db查詢不到資料,儲存空對象到緩存層,設定較短的失效時間;
方案三:針對業務場景對請求的參數進行有效性校驗,防止非法請求擊垮db。
2.2 緩存擊穿
現象:當某一key失效時,造成大量請求到db層,擊垮存儲層
原因:為了保證緩存資料的時效性,通常會設定一個失效時間,如果是熱點key,高并發時會有海量請求直接越過緩存層到資料庫,這樣就會給資料庫造成的負擔增大,設定當機。
解決方案
方案一:使用互斥鎖,當緩存資料失效時,保證一個請求能夠通路到資料庫,并更新緩存,其他線程等待并重試;
方案二:緩存資料“永遠不過期”,如果緩存資料不設定失效時間的話,就不會存在熱點key過期造成了大量請求到資料庫。但是,緩存資料就變成“靜态資料”,是以當緩存資料快要過期時,采用異步線程的方式提前進行更新緩存資料。
2.3 緩存雪崩
現象:多個key失效,造成大量請求到db層,導緻db層負擔過重甚至當機
原因:緩存雪崩是指在我們設定緩存時采用了相同的過期時間,導緻緩存在某一時刻同時失效,請求全部轉發到資料庫,最終導緻資料庫瞬時壓力過大而崩潰。
方案一:使用互斥鎖的方式,保證隻有單個線程進行請求能夠達到db;
方案二:多每個key的失效時間在基礎時間上再加上一個1~5分鐘的随機值,這樣就能保證大規模key集體失效的機率,并且需要盡量讓多個key的失效時間能夠均勻分布;
2.4 總結
緩存穿透、緩存擊穿以及緩存雪崩這三個術語很容易弄混,也是讀緩存中典型的三個場景問題,做一下簡單的總結是很有必要的。緩存穿透強調是擷取本不存在的緩存資料,請求必然會越過緩存層直接到達到存儲層,很明顯這是利用業務規則的漏洞對系統發起攻擊,解決方案的核心原則是過濾這些非法業務請求,與是否是熱點資料、緩存失效時間等因素沒有關系。緩存擊穿強調的是熱點key的失效,導緻某一時刻大量請求會直接到db層,解決方案的核心原則是規避資料庫的并發操作。緩存雪崩強調的多個key的集體失效,與key是否是熱點資料并不是必然的因素,解決方案的核心原則則讓key之間的失效時間分布更加均勻,避免集體失效的情況。
3 資料更新場景分析
引入緩存後資料會分别存放到緩存以及資料庫兩個地方,是以資料更新時,需要涉及到這兩處地方得更新,并且更新時序的不同會有不同的結果。關于資料更新目前業界已經沉澱了Cache Aside Pattern,Read/Write through等多種方式。
3.1 Cache Aside Pattern
這是很通用的更新政策,主要流程如下(來源于網絡):

(圖檔來源于網絡)
主要涉及到如下幾點:
失效:應用程式先從cache取資料,沒有得到,則從資料庫中取資料,成功後,放到緩存中。
命中:應用程式從cache中取資料,取到後傳回。
更新:先把資料存到資料庫中,成功後,再讓緩存失效
Cache Aside Pattern在資料更新的時候是采用先更新資料庫,再失效緩存。為什麼需要采用這樣的方式來解決資料更新的問題,先假設更新資料庫以及緩存都會事務成功,由于某一種更新導緻的不一緻性在下一章節進行讨論。
1)為什麼不是更新緩存,而是失效(删除)緩存?
并發寫容易寫覆寫造成髒資料問題:當資料發生更新的時候,針對緩存資料可以有兩種方式來進行處理分别是更新緩存資料以及失效資料讓下一次讀請求重新從db中擷取資料後重載入緩存中。假設更新緩存資料的話,在并發情況下會存在多線程寫緩存造成髒資料的問題,如下圖:
如上圖所示,假設A、B兩個線程,A先更新資料庫後 B再更新資料庫,然後分别進行更新緩存,但是B先更新緩存成功,A後更新緩存成功,這樣就導緻資料庫是最新的資料但是緩存中是舊的髒資料。而如果失效緩存資料的話,可以保證下一次讀請求回源到資料庫将最新的資料載入到緩存中,避免髒資料的問題。是以,針對資料更新緩存采用失效的方式進行處理,也可以參考這篇文章《Why does Facebook use delete to remove the key-value pair in Memcached instead of updating the Memcached during write request to the backend?》。
雙寫不同資料源容易造成資料不一緻:同時寫資料庫以及緩存資料,任何一個更新失敗都會造成資料不一緻,由于“實體失敗”造成的資料不一緻在下一個章節進行闡述。另外事務都成功,無論是先更新緩存還是再更新資料庫,還是先更新資料庫再更新緩存,這兩種情況在并發的情況下也很容易出現雙寫不成功,操作時序如下圖,這種方式不推薦。
先更新緩存再更新資料庫
先更新資料庫再更新緩存
違背資料懶加載,避免不必要的計算消耗:如果有些緩存值是需要經過複雜的計算才能得出,是以如果每次更新資料的時候都更新緩存,但是後續在一段時間内并沒有讀取該緩存資料,這樣就白白浪費了大量的計算性能,完全可以後續由讀請求的時候,再去計算即可,這樣更符合資料懶加載,降低計算開銷。
2)可能存在的更新時序?
在确定資料更新後緩存會失效來進行處理的話,針對資料庫以及緩存更新時序就存在如下這幾種:
先失效緩存再更新資料庫
假設在并發的情況下,按照這種更新時序會存在什麼問題?
如時序圖所示,線程A先失效緩存資料的時候,B線程讀請求發現緩存資料為空的話,就會從資料庫中讀取舊值放入到緩存中,這樣就導緻後續的讀請求讀到的都是緩存中的髒資料。針對這樣的情況可以采用延時雙删的政策來有效避免,僞代碼 如下:
cache.delKey(key);
db.update(data);
Thread.sleep(xxx);
cache.delKey(key);
主要是在寫請求更新完資料庫後進行休眠一段時間,然後删除可能由讀請求帶來的髒資料存入到緩存。另外,資料庫如果采用的是主從分離的架構的話,讀出來的資料也有可能是主從未同步完成造成的髒資料。這種通過延時雙删的方式需要線程休眠,是以很顯然會降低系統吞吐量,并不是一種優雅的解決方式,也可以采用異步删除的方式。當然可以設定過期時間,到期後緩存失效載入最新的資料,需要系統能夠容忍一段時間的資料不一緻。
先更新資料庫再失效緩存 :這是推薦的更新資料時采用的方式,實際上這也是可能存在資料不一緻的情況,時序圖如下:
假設緩存剛好到期失效時,讀請求從db中讀取資料,寫請求更新完資料後再失效緩存後,讀請求将舊資料存入到緩存中,這種情況也會導緻髒資料的問題。實際上這種情況發生的機率很低,要發生這種情況的前提條件是寫資料庫要先于讀資料庫完成,一般而言讀資料庫相比于寫資料庫要耗時更短,這種前提條件成立的機率很低。針對這種”邏輯失敗“造成的資料不一緻,可以采用上面所說的異步雙删的政策以及過期失效的方式來避免。
可以看出在并發的情況下,如果條件苛刻的話,這兩種更新的時序都有可能導緻髒資料的情況。隻不過在大機率的情況下先更新資料庫再失效緩存能夠保證資料一緻,也是業界推薦的處理方式,包括Facebook的論文《Scaling Memcache at Facebook》也使用了這個政策。當資料發生變更上,需要考慮的是最新的資料放置在哪裡?很顯然cache aside pattern 選擇的是将最新的資料放到了db上(cache asside pattern:緩存靠邊站),因為資料不一緻的情況大機率會存在,需要根據業務場景選擇合适的可信裝置存儲最新的資料。
3.2 Write/Read Through
Cache Aside Pattern對db以及緩存的更新邏輯是由調用方自己去控制,很顯然這是一個很複雜的過程。Write/Read Through對調用方而言,緩存是作為整個的資料存儲,而不用關系緩存後面的db,資料庫的更新則是由緩存統一進行管理,對調用方而言隻需要和緩存進行互動,整體過程是透明的。
Read Through:當資料發生更新時,查詢緩存時更新緩存,然後由緩存層同步的更新資料庫即可,對調用方而言隻需要和緩存層互動即可;
Write Through:Write Through 套路和Read Through相仿,不過是在更新資料時發生。當有資料更新的時候,如果沒有命中緩存,直接更新資料庫,然後傳回。如果命中了緩存,則更新緩存,然後再由Cache自己同步更新資料庫。如下圖所示(來源于網絡):
3.3 Write Behind Cache Pattern
這種模式是當資料更新的時候直接更新緩存資料,然後建立異步任務去更新資料庫。這種異步方式請求響應會很快,系統的吞吐量會明顯提升。但是,因為是異步更新資料庫,資料一緻性的保障就會變弱,如果更新資料庫失敗則會永遠的造成系統髒資料,需要很精細設計系統重試的政策,另外如果異步服務當機的話,還要考慮更新的資料如何持久化,服務重新開機後能夠迅速恢複。在更新資料庫時,由于并發多任務的存在,還需要考慮并發寫是否會造成髒資料的問題,就需要追溯每次更新資料的時序。使用這種模式需要考慮的細節會有很多,設計出一套好的方案是件很不容易的事情。
3.4 更新政策的思考
上面這四種更新政策是非常經典的,也是業界經過大規模業務總結下來的經驗,如果認真分析這四種更新政策的話,也會是受益匪淺,在更新政策的設計我得了解是主要關注如下兩個方面:
最新的資料應該放置在哪裡?
緩存的存在是為了系統高性能,利用記憶體的IO讀取的高速的特性,來提升系統的性能,提高系統吞吐量,另外,緩存的存在會讓一部分讀請求不會到達db層,分解了db的壓力,畢竟db是最容易出現瓶頸的地方。這是為什麼利用緩存的兩個重要原因。但是,帶來的問題就是,資料會存在在兩個地方分别是緩存以及資料庫中,當資料更新的時候就需要思考讓”正确的資料應該放在哪個最可信的存儲媒體上“,就需要結合業務性質在兩個資料存儲媒體上進行選擇。Cache Aside Pattern選擇先更新資料庫,再失效緩存,這樣可以保證最新最正确的資料一定會落在資料庫中,這樣可以保證核心的業務資料在資料庫中一定是可信的,但是帶來的問題是業務邏輯更複雜,系統處理更新邏輯耗時更長。如果是非核心資料的更新,可以選擇write behind cache pattern的方式,隻需要更新緩存即可,能夠快速的響應。缺點是很容易造成資料不一緻,資料庫中的資料不一定的就是最可信的資料。是以,不同的更新政策實際上也是将最新的資料優先選擇放在哪裡更合适以及系統性能的一種權衡,需要結合業務場景做好trade-off;
4 資料不一緻性
4.1 資料不一緻的原因
由于引入緩存,資料就會分散在兩處不同資料源,當資料更新時,實時上很難做到資料一緻,除非采用強一緻性方案,這裡不在進行讨論。在找出合适的解決方案前,需要分析下存在資料不一緻的主要原因,才能對症下藥:
1)邏輯失敗造成的資料不一緻:在上一章主要分析了更新資料時的四種更新政策,在并發的情況下,無論是先删除緩存還是更新資料庫,還是更新資料庫再失效緩存,都會資料不一緻的情況,主要是因為異步讀寫請求在并發情況下的操作時序導緻的資料不一緻,稱之為”邏輯失敗“。解決這種因為并發時序導緻的問題,核心的解決思想是将異步操作進行串行化。
2)實體失敗造成的資料不一緻:在cache aside pattern中先更新資料庫再删除緩存以及異步雙删政策等等,如果删除緩存失敗時都出現資料不一緻的情況。但是資料庫更新以及緩存操作是沒辦法放到一個事務中,一般來說,使用緩存是分布式緩存如果緩存服務很耗時,那麼将更新資料庫以及失效緩存放到一個事務中,就會造成大量的資料庫連接配接挂起,嚴重的降低系統性能,甚至會因為資料庫連接配接數過多,導緻系統崩潰。像這種因為緩存操作失敗,導緻的資料不一緻稱之為”實體失敗“。大多數情況實體失敗的情況會重用重試的方式進行解決。
4.2 資料一緻性的解決方案
在絕大部分業務場景中,追求的是最終一緻性,針對實體失敗造成的資料不一緻常用的方案有:消費消息異步删除緩存以及訂閱Binlog的方式,針對邏輯失敗造成的資料不一緻常用的方案有:隊列異步操作同步化。
4.2.1 消費消息異步删除緩存
主要流程如下圖所示:
4.2.2 訂閱Binlog
4.2.3 利用隊列串行化
在分析cache aside pattern發現在并發的情況下也會存在資料不一緻的場景,隻不過發生的機率很低,另外如果先删除緩存再更新資料庫在并發讀寫的情況下也會存在資料不一緻的情況。類似這種由于并發時序導緻的資料不一緻的情況,都是因為寫請求還沒有結束讀請求讀取的是舊資料,如果讀請求在寫請求之後處理,即請求的處理能夠串行化的話,就能保證讀請求讀到的是寫請求更新的最新的資料。
将請求進行串行化,最常用的方式是采用隊列的方式,一個隊列隻能對應一個工作線程,更新資料的寫請求放置隊列中,等待異步處理;讀請求如果能從緩存中擷取資料,則傳回,如果緩存中沒有資料,就将讀請求放置到隊列中,等待寫請求資料更新完成。這種方案需要考慮的問題有:
1)讀請求長時間阻塞:如果隊列中擠壓了多個寫請求,則讀請求會存在長時間阻塞的情況,需要設定逾時處理政策,一旦超過逾時時間,則直接讀取資料庫傳回,避免長時間不響應;另外,在業務中需要進行壓測,考慮隊列中在峰值情況下會積攢多少寫請求,如果過多,需要考慮隊列優化的方式和相應的解決方案;
2)多個隊列分散壓力:可以根據資料項通過hash等路由方式,建立多個隊列并行執行來提升系統吞吐量;
3)操作複雜需要考慮全面:由于采用隊列來進行串行化,那麼要考慮隊列的可用性,隊列阻塞以及服務挂掉後的容災恢複政策是否健壯等等,相對而言整體的方案需要考慮的點會有很多;
這種方式可以做到資料強一緻性,由于串行化系統的吞吐量會下降很多并且操作複雜,畢竟任何方案都會有利弊權衡的過程,需要根據業務場景選擇合适的技術方案。針對資料強一緻性很有很多方案,但基本上操作設計都很複雜,在大多數業務場景滿足資料最終一緻性即可。
當然除了以上這三種通用的方法外,為緩存設定過期時間以及定時全量同步,也是接近最終一緻性的最簡單以及有效的方式。
5 常見的幾個場景問題
在分析資料更新的政策後發現正确使用緩存是一件很不容易的事情,在實際使用緩存時,還會有很多有意思的場景(”坑“),在這裡進行一下總結:
1)過期還是不過期緩存資料:針對緩存資料是否需要設定過期時間也需要結合場景來進行分析,一些長尾商品,大多數資料在業務中都是讀場景更多,并且緩存空間很大的話,就可以考慮不過期資料。那是否就意味着這就是一份靜态資料了?當緩存空間已滿時,資料會根據淘汰政策移除緩存,另外資料更新時也可以通過Binlog等其他方式進行異步失效緩存。
如果系統通過消息異步更新操作成本過高或者依賴于外部系統無法進行訂閱binlog異步更新的話,就需要來采用過期緩存資料來保障資料最終一緻性。
2)次元化緩存與增量更新:如果一個實體包含多個屬性,在實體發生變更時,如果将所有的屬性全部更新一遍,這個成本就很高,況且隻是其中的幾個屬性發生變化。是以,将多個屬性進行各個次元化進行拆解,按照多元度進行緩存,更新時隻需要增強更新對應次元即可;
3)大value:大value的問題要時刻警惕,可以考慮将value進行壓縮,以及緩存時進行拆解,然後在業務服務中進行資料聚合來避免大value的問題;
4)熱點緩存問題:針對熱點資料如果每次都從遠端緩存去擷取,會給緩存系統帶來過多的負載,會導緻擷取緩存資料響應過慢,可以使用緩存叢集,挂載更多的從緩存,讀取資料從從緩存中擷取。針對熱點資料可以使用應用本地緩存來減少對遠端緩存的請求負載;
5)資料預熱:可以預先将資料加載到緩存中,方式緩存資料為空,大量的請求回源到db。如果容量很高可以考慮全量預熱,如果容量優先,就隻能選擇高頻熱點資料進行資料預熱,還需要關注是否有批量操作以及慢sql帶來的性能問題,在整個資料預熱過程中需要有可靠的監控機制來保障;
6)非預期熱點資料:針對業務預估不足的熱點資料,需要有熱點發現系統來統計熱點key,實時監控非預期的熱點資料,可以将這些key推到本地緩存中,防止預估不足的熱點key拖垮遠端緩存服務。
7)緩存執行個體故障快速恢複:當某一個緩存執行個體故障後,緩存一般是采用分片執行個體存儲,假設緩存key路由政策采用的取模機制的話,會導緻目前執行個體的流量迅速到達db層,這種情況可以采用主從機制,當一個執行個體故障後其他執行個體可以使用,但是這種方式的問題在于水準擴充不夠,如果分片執行個體上增加一個節點的話,會導緻緩存命中率迅速下降。
如果key路由政策采用的一緻性哈希的話,某一個執行個體節點故障,隻會導緻哈希環上的部分緩存不命中不會導緻大量請求到達db,但是針對熱點資料的話,可能會導緻改節點負載過高成為系統瓶頸。針對執行個體故障恢複的方式有:1. 主從機制,對資料進行備份,盡可能保障有可用資料;2. 服務降低,新增緩存執行個體然後異步線程預熱資料;3. 可以先采用一緻性哈希路由政策,當出現熱點資料時到達某個門檻值時降級為取模的政策。
6 幾個影響因素
影響緩存整體的性能會有很多大大小小的影響因素,比如語言本身的特性的影響,例如Java需要考慮GC的影響。還需要盡可能的提升緩存命中率等等多個方面,總結下來,核心的幾個影響因素如下:
1)提升緩存命中率:影響緩存命中率的幾個因素:
業務時效性要求:緩存适合"讀多寫少"的業務場景,并且業務性質決定了時效性要求,不同的時效性要求決定了緩存的更新政策以及過期時間,對時效性也低的業務越适合使用緩存,并且緩存命中率越高;
緩存粒度設計:通常而言,緩存對象粒度越小就越适合使用緩存,不會導緻頻繁更新導緻緩存命中率下降;
緩存淘汰政策:如果緩存空間有限,不同的緩存淘汰政策也會影響緩存命中率,如果淘汰的緩存資料後續被大量使用,無疑就會降低緩存命中率;
緩存部署方式:在使用分布式緩存時,要做好容量規劃以及容災政策,方式緩存執行個體故障後造成大規模緩存失效;
Key路由政策:不同路由政策會在節點執行個體故障後帶來不同的影響,如果采用取模的方式水準擴充時則會降低緩存命中率。通過這些分析,提高緩存命中率沒有放之四海而皆準的統一規則,需要從這些角度去思考,盡可能的在高頻通路且時效性不是很高的業務資料上使用緩存。
2)序列化方式:使用遠端緩存服務免不了需要經過序列化後在網絡中進行資料傳輸,那麼選擇不同的序列化方式對緩存性能會有影響。選擇序列化方式時需要考慮序列化耗時、序列化後在網絡傳輸中包大小以及序列化的計算開銷。
3)GC影響:采用多級緩存以及大value時會采用應用本地緩存,對于java應用,就需要考慮大對象帶來的GC影響。
4)緩存協定:了解不同的緩存協定的優缺點比如Redis以及Memcached協定,根據業務場景進行選擇。
5)緩存連接配接池:為提升通路性能,需要合理的設定緩存連接配接池。
6)完善的監控平台:需要考慮是否有一套緩存的監控平台,能夠追蹤緩存使用情況、緩存服務整體的性能以及一些非預期熱點資料的發現政策等等,這樣才能綜合整體的保障緩存服務的可用以及性能。
7 多級緩存設計案例
從使用者送出請求到到最底層的資料庫實際上會經曆很多節點,是以在整個鍊路上都可以設定緩存,并且按照緩存最近原則将緩存放置在裡使用者最近的地方提升系統響應的效果最為明顯,相應的提升系統吞吐量的效果就越為顯著,通過能夠大大降低對後端的壓力。在整個鍊路流程裡可以添加緩存的地方有:發起請求-->浏覽器/用戶端緩存-->邊緣緩存/CDN-->反向代理(Nginx)緩存-->遠端緩存-->程序内緩存-->資料庫緩存。關于用戶端緩存以及網路緩存可以檢視第三章節部分,服務端多級緩存設計通用的技術方案如下:
主要流程為:
1)請求先達到Nginx,先讀取Nginx本地緩存,如果命中緩存則傳回緩存資料。這裡的負載均衡路由政策,采用輪詢的方式相對而言通路壓力分布的更加均衡,一緻性哈希方式能夠提升緩存命中率,但是同時也會存在單點壓力過大的問題,可以考慮使用一緻性哈希政策時流量達到一定門檻值的時候切換成輪詢的方式;
2)如果沒有命中Nginx緩存,則讀取分布式緩存,為了高可用以及提升系統吞吐量,一般遠端分布式緩存會采用主從結構,這裡讀取的就是從緩存服務叢集資料,如果命中緩存則傳回資料;
3)如果從緩存沒有命中緩存,則讀取應用本地緩存(堆内/堆外緩存),這裡的路由政策同樣可以采用輪詢或者一緻性哈希。如果命中,則傳回資料,并回寫到Nginx緩存中;為避免由于從緩存服務出現問題,造成過大的流量沖垮資料庫,這裡可以嘗試讀取主緩存服務;
4)如果所有緩存沒有命中,則查詢資料庫并傳回資料,并異步回寫到主緩存以及應用本地緩存中。主緩存通過主從同步機制同步到從緩存服務叢集中。這裡會寫到主緩存的時候需要考慮多個應用執行個體在異步寫,需要考慮資料是否會亂序的問題。
另外,對于一些非預期熱點資料比如微網誌中”某某明星結婚“等等熱門話題帶來的通路流量瞬間沖擊到後端,針對以上多級緩存設計,可以通過引入熱點發現系統來發現非預期的熱點資料,利用flume訂閱Nginx日志,然後通過消息進行消費,最後通過storm等實時計算架構進行熱點資料的統計,當監控發現到熱點資料,将其推送到各個緩存節點上,整體的緩存設計如下:
8 總結
為了追求高性能,每個開發者最先使用的就是緩存,也在潛意識裡将緩存作為了系統性能瓶頸的一劑良藥,經過系統化的總結和分析緩存後,就可以發現緩存如果使用不當真的就會事與願違,成為毒藥,并不會系統疊代出那個局部最優解。如果貿然的使用緩存,需要考慮的地方真的很多稍有不注意,反而會讓系統投入更多的維護成本,陡增更高的複雜度。那是不是就不使用緩存呢?也不是,緩存在高并發的情況下通過IO高速的緩存擷取資料能使得每個請求能夠快速響應,并且能夠大大提升系統吞吐量以及支撐更高的并發使用者數,在現有的高并發大流量的網際網路應用中應用緩存的例子太多了,也足以證明緩存在優化系統整體性能是一種行之有效的方案。
作為開發者不是每個人都有機會和機遇去挑戰高并發的網際網路架構以及高量級的通路流量和應用規模的,那是不是就意味着這些通用的技術方案就不用深刻分析呢?很顯然不是,單從緩存使用中就會發現在高并發下讀寫帶來的資料不一緻性分析下來就會有很多并發場景,單線程下都是正常的,但在并發下就會出現很多意想不到的case,而這些分析的思路是最核心的,也是開發者逐漸形成自己的方法論的有效訓練途徑。在系統化學習每一種技術元件時,業界的通用解決方案都是經過曆史經驗慢慢沉澱下來的智慧,如同品酒,是需要靜下心來好好去品的。
技術最終是服務于業務價值,而業務規模擴張會反哺技術的創新,要設計出一套适應于業務的合理的技術方案,需要很深的内功,需要既懂技術又要對業務了解十分深刻才行,懂業務而不懂技術,很難知道每種技術方案的局限性,也就是經常所說的PPT架構師,PPT很炫酷,一頓操作猛如虎但是并不是最适合業務的那個解,反而就像是跳梁小醜一樣自嗨或者帶着功利心去急于變現,隻有業務與技術結合能夠得到最大價值的那個解就是最合适的方案,需要在優與劣的trade-off上做出權衡。如果很懂技術,但是不懂業務,同樣的就是廢銅爛鐵沒辦法發揮出功力。在不同的職業生涯階段,每個人的精力有限,投入技術以及業務的精力配置設定也是不同的,專注的點會有所不同,就像業務與技術一樣,在人生的賽道中在不同階段也需要疊代出那個最合适的局部最優解,至于什麼最合适,答案在每個人心中!
-
Reference
《深入分布式緩存》;
緩存穿透、緩存擊穿以及緩存雪崩問題探讨:1、2、3;
緩存更新政策:1、2、3、4;
資料最終一緻性:1、2、3、4;
緩存設計:1、2、3、4;