【高并發(fā)】高并發(fā)環(huán)境下構(gòu)建緩存服務(wù)需要注意哪些問題?我和阿里P9聊了很久!
掃描二維碼
隨時隨地手機看文章
寫在前面
周末,跟阿里的一個朋友(去年晉升為P9了)聊了很久,聊的內(nèi)容幾乎全是技術(shù),當然了,兩個技術(shù)男聊得最多的話題當然就是技術(shù)了。從基礎(chǔ)到架構(gòu),從算法到AI,無所不談。中間又穿插著不少天馬行空的想象,雖然現(xiàn)在看起來不太實際,但是隨著技術(shù)的進步,相信五年、十年之后都會實現(xiàn)的。
不知道是誰提起了在高并發(fā)環(huán)境下如何構(gòu)建緩存服務(wù),結(jié)果一路停不下來了??!
緩存特征
(1)命中率:命中數(shù)/(命中數(shù)+沒有命中數(shù))
(2)最大元素(空間):代表緩存中可以存放的最大元素的數(shù)量,一旦緩存中元素的數(shù)量超過這個值,或者緩存數(shù)據(jù)所占的空間超過了最大支持的空間,將會觸發(fā)緩存清空策略。根據(jù)不同的場景,合理設(shè)置最大元素(空間)的值,在一定程度上可以提高緩存的命中率,從而更有效的使用緩存。
(3)清空策略:FINO(先進先出)、LFU(最少使用)、LRU(最近最少使用)、過期時間、隨機等。
FINO(先進先出):最先進入緩存的數(shù)據(jù),在緩存空間不夠或超出最大元素限制的情況下,會優(yōu)先被清除掉,以騰出新的空間來接收新的數(shù)據(jù)。這種策略的算法主要是比較緩存元素的創(chuàng)建時間,在數(shù)據(jù)實時性較高的場景下,可以選擇這種策略,優(yōu)先保證最新策略可用。
LFU(最少使用):無論元素是否過期,根據(jù)元素的被使用次數(shù)來判斷,清除使用次數(shù)最少的元素來釋放空間。算法主要是比較元素的命中次數(shù),在保證高頻數(shù)據(jù)有效的場景下,可以選擇這種策略。
LRU(最近最少使用):無論元素是否過期,根據(jù)元素最后一次被使用的時間戳,清除最遠使用時間戳的元素,釋放空間。算法主要是比較元素最近一次被獲取的時間,在熱點數(shù)據(jù)場景下,可以選擇這種策略。
過期時間:根據(jù)過期時間判斷,清理過期時間最長的元素,或者清理最近要過期的元素。
緩存命中率影響因素
(1)業(yè)務(wù)場景和業(yè)務(wù)需求
緩存往往適合讀多寫少的場景。業(yè)務(wù)需求對實時性的要求,直接會影響到緩存的過期時間和更新策略。實時性要求越低,就越適合緩存。在相同Key和相同請求數(shù)的情況下,緩存的時間越長,命中率就會越高。
(2)緩存的設(shè)計(粒度和策略)
通常情況下,緩存的粒度越小,命中率越高。緩存的更新和命中策略也會影響緩存的命中率,當數(shù)據(jù)發(fā)生變化時,直接更新緩存的值會比移除緩存或使緩存過期的命中率更高。
(3)緩存容量和基礎(chǔ)設(shè)施
緩存的容量有限,則容易引起緩存失效和被淘汰(目前多數(shù)的緩存框架或中間件都采用了LRU算法)。同時,緩存的技術(shù)選型也是至關(guān)重要的,比如采用應(yīng)用內(nèi)置的本地緩存就比較容易出現(xiàn)單機瓶頸,而采用分布式緩存則畢竟容易擴展。所以需要做好系統(tǒng)容量規(guī)劃,并考慮是否可擴展。此外,不同的緩存框架或中間件,其效率和穩(wěn)定性也是存在差異的。
(4)其他因素
當緩存節(jié)點發(fā)生故障時,需要避免緩存失效并最大程度降低影響,這種特殊情況也是架構(gòu)師需要考慮的。業(yè)內(nèi)比較典型的做法就是通過一致性Hash算法,或者通過節(jié)點冗余的方式。
有些朋友可能會有這樣的理解誤區(qū):既然業(yè)務(wù)需求對數(shù)據(jù)時效性要求很高,而緩存時間又會影響到緩存命中率,那么系統(tǒng)就別使用緩存了。其實這忽略了一個重要因素--并發(fā)。通常來講,在相同緩存時間和key的情況下,并發(fā)越高,緩存的收益會越高,即便緩存時間很短。
提高緩存命中率的方法
從架構(gòu)師的角度,需要應(yīng)用盡可能的通過緩存直接獲取數(shù)據(jù),并避免緩存失效。這也是比較考驗架構(gòu)師能力的,需要在業(yè)務(wù)需求,緩存粒度,緩存策略,技術(shù)選型等各個方面去通盤考慮并做權(quán)衡。盡可能的聚焦在高頻訪問且時效性要求不高的熱點業(yè)務(wù)上,通過緩存預(yù)加載(預(yù)熱)、增加存儲容量、調(diào)整緩存粒度、更新緩存等手段來提高命中率。
對于時效性很高(或緩存空間有限),內(nèi)容跨度很大(或訪問很隨機),并且訪問量不高的應(yīng)用來說緩存命中率可能長期很低,可能預(yù)熱后的緩存還沒來得被訪問就已經(jīng)過期了。
緩存的分類和應(yīng)用場景
(1)本地緩存:編程實現(xiàn)(成員變量、局部變量、靜態(tài)變量)、Guava Cache
(2)分布式緩存:Memcached、Redis
高并發(fā)場景下緩存常見問題
(1)緩存的一致性
更新數(shù)據(jù)庫成功——更新緩存失敗
更新緩存成功——更新數(shù)據(jù)庫失敗
更新數(shù)據(jù)庫成功——淘汰緩存失敗
淘汰緩存成功——更新數(shù)據(jù)庫失敗
(2)緩存并發(fā)
并發(fā)時請求緩存時已過期或者沒有命中或者更新的情況下有大量的請求訪問數(shù)據(jù)庫。
解決辦法:在緩存更新或者過期的情況下,先嘗試獲取到lock,當更新完成后,嘗試釋放鎖,其他的請求只需要犧牲一定的等待時間
(3)緩存穿透
在高并發(fā)的場景下,如果某一個key被高并發(fā)的訪問沒有被命中,出于對容錯性的考慮會嘗試從后端的數(shù)據(jù)庫獲取,從而導(dǎo)致大量的請求訪問了數(shù)據(jù)庫,主要是當key對應(yīng)的數(shù)據(jù)為空或者為null的情況下,這就導(dǎo)致數(shù)據(jù)庫中并發(fā)的執(zhí)行了很多不必要的查詢操作。從而導(dǎo)致了巨大的沖擊和壓力。
解決方法:
緩存空對象:對查詢結(jié)果為空的對象也進行緩存,如果是集合可以緩存一個空的集合,而不是null,如果是單個對象可以通過字段標識來區(qū)分,需要保證緩存數(shù)據(jù)的時效性(實現(xiàn)相對簡單),適合命中不高但可能會頻繁更新的數(shù)據(jù)。
單獨過濾處理:對所有可能對應(yīng)數(shù)據(jù)為空的key進行統(tǒng)一的存放,并在請求前做攔截(實現(xiàn)相對復(fù)雜),適合命中不高更新不頻繁的數(shù)據(jù)
(4)緩存顛簸問題
緩存的顛簸問題,有些地方可能被稱為“緩存抖動”,可以看作是一種比“雪崩”更輕微的故障,但是也會在一段時間內(nèi)對系統(tǒng)造成沖擊和性能影響。一般是由于緩存節(jié)點故障導(dǎo)致。業(yè)內(nèi)推薦的做法是通過一致性Hash算法來解決。
(5)緩存雪崩現(xiàn)象
緩存雪崩就是指由于緩存的原因,導(dǎo)致大量請求到達后端數(shù)據(jù)庫,從而導(dǎo)致數(shù)據(jù)庫崩潰,整個系統(tǒng)崩潰,發(fā)生災(zāi)難。導(dǎo)致這種現(xiàn)象的原因有很多種,上面提到的“緩存并發(fā)”,“緩存穿透”,“緩存顛簸”等問題,其實都可能會導(dǎo)致緩存雪崩現(xiàn)象發(fā)生。這些問題也可能會被惡意攻擊者所利用。還有一種情況,例如某個時間點內(nèi),系統(tǒng)預(yù)加載的緩存周期性集中失效了,也可能會導(dǎo)致雪崩。為了避免這種周期性失效,可以通過設(shè)置不同的過期時間,來錯開緩存過期,從而避免緩存集中失效。
從應(yīng)用架構(gòu)角度,我們可以通過限流、降級、熔斷等手段來降低影響,也可以通過多級緩存來避免這種災(zāi)難。
此外,從整個研發(fā)體系流程的角度,應(yīng)該加強壓力測試,盡量模擬真實場景,盡早的暴露問題從而防范。
(6)緩存無底洞現(xiàn)象
該問題由 facebook 的工作人員提出的, facebook 在 2010 年左右,memcached 節(jié)點就已經(jīng)達3000 個,緩存數(shù)千 G 內(nèi)容。他們發(fā)現(xiàn)了一個問題---memcached 連接頻率,效率下降了,于是加 memcached 節(jié)點,添加了后,發(fā)現(xiàn)因為連接頻率導(dǎo)致的問題,仍然存在,并沒有好轉(zhuǎn),稱之為”無底洞現(xiàn)象”
特別推薦一個分享架構(gòu)+算法的優(yōu)質(zhì)內(nèi)容,還沒關(guān)注的小伙伴,可以長按關(guān)注一下:
長按訂閱更多精彩▼
如有收獲,點個在看,誠摯感謝
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!