https://segmentfault.com/a/1190000008931971
《Redis架構(gòu)之防雪崩設(shè)計(jì)》這篇文章(下文稱之為“原文”)寫得非常好严蓖,全面概括了大規(guī)模系統(tǒng)可能面對的緩存穿透和緩存雪崩等問題,可以看出是一線實(shí)戰(zhàn)經(jīng)驗(yàn)的精華總結(jié)烤黍,非常適合大家學(xué)習(xí)剃根。
而我想再補(bǔ)充一些信息览爵,使“原文”的版圖更加完整。
關(guān)于“緩存穿透”
“原文”給出了空對象和布隆過濾器兩種解決方案滓侍。
空對象是首選方案喝峦,簡單直接,碰到查詢結(jié)果為空的鍵剑梳,放一個空值在緩存中唆貌,下次再訪問就立刻知道這個鍵無效,不用發(fā)出SQL了垢乙。但“原文”也說了锨咙,存在如下問題:
第一,空值做了緩存追逮,意味著緩存層中存了更多的鍵蓖租,需要更多的內(nèi)存空間 ( 如果是攻擊,問題更嚴(yán)重 )羊壹,比較有效的方法是針對這類數(shù)據(jù)設(shè)置一個較短的過期時間蓖宦,讓其自動剔除。
第二油猫,緩存層和存儲層的數(shù)據(jù)會有一段時間窗口的不一致稠茂,可能會對業(yè)務(wù)有一定影響。例如過期時間設(shè)置為 5 分鐘情妖,如果此時存儲層添加了這個數(shù)據(jù)睬关,那此段時間就會出現(xiàn)緩存層和存儲層數(shù)據(jù)的不一致,此時可以利用消息系統(tǒng)或者其他方式清除掉緩存層中的空對象毡证。
對于第一點(diǎn)电爹,我還建議空值放在另外的緩存空間中,不宜與正常值共用空間料睛,否則當(dāng)空間不足時丐箩,緩存系統(tǒng)的LRU算法可能會先剔除正常值摇邦,再剔除空值——這個漏洞可能會受到攻擊。
對于第二點(diǎn)屎勘,如果是Redis緩存施籍,更新數(shù)據(jù)后直接在Redis中清除即可;如果是本地緩存概漱,就需要用消息來通知其他機(jī)器清除各自的本地緩存了丑慎。(業(yè)界終于接受了用消息來同步緩存的設(shè)計(jì)思想,cheers! )我有一個小項(xiàng)目joint-cache-redis來簡單地演示“用消息來同步多個機(jī)器的緩存”瓤摧,而且在實(shí)踐中發(fā)現(xiàn)Kafka可能比Redis MQ更適合于這個場景竿裂。
關(guān)于“緩存雪崩”
這句概括很傳神!緩存層宕掉后照弥,流量會像奔逃的野牛一樣铛绰,打向后端存儲
沒什么要補(bǔ)充的,就感謝一下Netflix開源的Hystrix吧产喉!雖然只是一個庫捂掰,但是要實(shí)現(xiàn)可靠的限流算法還是頗有門道的。
關(guān)于“緩存熱點(diǎn) key 重建”
“原文”說到在緩存失效的瞬間曾沈,有大量線程來重建緩存这嚣,造成后端負(fù)載加大,甚至可能會讓應(yīng)用崩潰塞俱,并給出“互斥鎖”和“永遠(yuǎn)不過期”兩種候選方案姐帚。
互斥鎖(Mutex):
“分布式緩存加鎖”通常是一個反模式(見我去年的文章大型服務(wù)端開發(fā)的反模式第7條),如果持有鎖的實(shí)例不穩(wěn)定導(dǎo)致沒及時釋放障涯,就會浪費(fèi)這個鎖罐旗,直到鎖過期∥ǖ“原文”的作者還指出有死鎖的風(fēng)險(xiǎn)九秀。
其實(shí)是可以優(yōu)化的:等待一兩次后,重試時可繞過互斥鎖粘我。即使繞過互斥鎖鼓蜒,也不會產(chǎn)生什么不好的后果,因?yàn)楦戮彺媸且粋€冪等操作征字。
也可以把鎖的過期時間設(shè)得更短都弹。
從這個例子我們能感覺到,冪等操作比非冪等操作更容易優(yōu)化匙姜。
永遠(yuǎn)不過期:
"原文"很好地介紹了在Redis中的做法畅厢。對于Guava本地緩存就簡單多了,使用refreshAfterWrite即可氮昧。
“原文”讀到最后框杜,才知道這是《Redis開發(fā)與運(yùn)維》一書的節(jié)選浦楣,相信這本書會是國產(chǎn)技術(shù)書籍的精品!