之前了解了緩存的原理逛尚、分類以及常用緩存的使用技巧。我們開始用緩存承擔(dān)大部分的讀壓力到逊,從而緩解數(shù)據(jù)庫的查詢壓力滤钱,在提升性能的同時(shí)保證系統(tǒng)的穩(wěn)定性。這時(shí)電商系統(tǒng)整體架構(gòu)演變成這個(gè)樣子:
我們在Web層和數(shù)據(jù)層之間增加了緩存層,請求會首先查詢緩存他炊,只有當(dāng)緩存中沒有需要的數(shù)據(jù)時(shí)才會查詢數(shù)據(jù)庫痊末。
在這里你需要關(guān)注緩存命中率這個(gè)指標(biāo)。一般來說捆蜀,在你的電商系統(tǒng)中幔嫂,核心緩存的命中率需要維持在99%甚至是99.9%履恩,哪怕下降1%,系統(tǒng)都會遭受毀滅性打擊飒筑。
這不是危言聳聽。假設(shè)系統(tǒng)的QPS是10000/s俏脊,每次調(diào)用會訪問10次緩存或者數(shù)據(jù)庫中的數(shù)據(jù)爷贫,那么當(dāng)緩存命中率僅僅減少1%补憾,數(shù)據(jù)庫每秒就會增加1000次請求。而一般來說我們單個(gè)MySQL節(jié)點(diǎn)的讀請求量峰值就在1500/s左右腾务,增加的這1000次請求很可能會給數(shù)據(jù)庫造成極大的沖擊削饵。
影響如此可怕窿撬,更不要說緩存節(jié)點(diǎn)故障了。而圖中單點(diǎn)部署的緩存節(jié)點(diǎn)就成了緩存系統(tǒng)中最大的隱患,那我們?nèi)绾翁嵘彺娴目捎眯阅兀?/p>
重點(diǎn):分布式緩存的高可用方案
主要選擇的方案有客戶端方案宰啦、中間代理層方案和服務(wù)端方案三大類:
客戶端方案就是在客戶端配置多個(gè)緩存的節(jié)點(diǎn)饼拍,通過緩存寫入和讀取算法策略來實(shí)現(xiàn)分布式师抄,從而提高緩存的可用性。
中間代理層方案就是在應(yīng)用代碼和緩存節(jié)點(diǎn)之間增加代理層辆布,客戶端所有的寫入和讀取的請求都通過代理層茶鉴,而代理層中會內(nèi)置高可用策略涵叮,幫助提升緩存系統(tǒng)的高可用伞插。
服務(wù)端方案就是redis 2.4版本后提出的Redis Sentinel方案媚污。
掌握這些方案可以幫助你抵御部分緩存節(jié)點(diǎn)故障導(dǎo)致的緩存命中率下降的影響
客戶端方案
在客戶端方案中廷雅,你需要關(guān)注緩存的寫和讀2個(gè)方面
- 寫入數(shù)據(jù)時(shí)榜轿,需要把被寫入緩存的數(shù)據(jù)分散到多個(gè)節(jié)點(diǎn)中,即進(jìn)行數(shù)據(jù)分片甸私;
- 讀數(shù)據(jù)時(shí)飞傀,可以利用多組的緩存來做容錯(cuò) 砸烦,提升緩存系統(tǒng)的可用性。關(guān)于讀數(shù)據(jù)唬格,這里可以時(shí)候用主從和多副本兩種策略颜说,兩種策略是為了解決不同的問題而提出的门粪。
該如何做?
1.緩存數(shù)據(jù)如何分片
單一的緩存節(jié)點(diǎn)受到機(jī)器內(nèi)存乾吻、網(wǎng)卡帶寬和單節(jié)點(diǎn)請求量的限制绎签,不能承擔(dān)比較高的并發(fā)酝锅,因此我們考慮將數(shù)據(jù)分片屈张,依照分片算法將數(shù)據(jù)打散到多個(gè)不同的節(jié)點(diǎn)上袱巨,每個(gè)節(jié)點(diǎn)上存儲部分?jǐn)?shù)據(jù)愉老。
這樣在某個(gè)節(jié)點(diǎn)故障的情況下剖效,其他節(jié)點(diǎn)也可以提供服務(wù)璧尸,保證一定的可用性。
一般來講垫竞,分片算法常見的及時(shí)Hash分片算法和一致性Hash分片算法兩種蛀序。
Has分片的算法就是對緩存的Key做哈希計(jì)算徐裸,然后對總的緩存節(jié)點(diǎn)個(gè)數(shù)取余∑锼睿可這么理解:
比如說我們部署了三個(gè)緩存節(jié)點(diǎn)組成一個(gè)緩存的集群次企,當(dāng)有新的數(shù)據(jù)要寫入時(shí)健民,我們先對這個(gè)緩存的key做比如crc32等hash算法生成hash值秉犹,然后對hash值模3稚晚,得出的結(jié)果就是要存入緩存節(jié)點(diǎn)的序號客燕。
這個(gè)算法最大的優(yōu)點(diǎn)就是簡單易理解也搓,缺點(diǎn)是當(dāng)增加或者減少緩存節(jié)點(diǎn)時(shí)涵紊,緩存總的節(jié)點(diǎn)個(gè)數(shù)變化造成計(jì)算出來的節(jié)點(diǎn)發(fā)生變化幔摸,從而造成緩存失效不可用。如果采用這種方法驱负,最好建立在你對于這組緩存命中率下降不敏感患雇,比如下面還有另外一層緩存來兜底的情況下苛吱。
當(dāng)然了又谋,用一致性Hash算法可以很好地解決增加和刪減節(jié)點(diǎn)時(shí),命中率下降的問題咧七。在這個(gè)算法中任斋,我們將整個(gè)hash值空間組織成一個(gè)虛擬的圓環(huán)废酷,然后將緩存節(jié)點(diǎn)的IP地址或者主機(jī)名做Hash取值后,放置在圓環(huán)上墨辛。當(dāng)我們需要確定某一個(gè)key需要存取到哪個(gè)節(jié)點(diǎn)上的時(shí)候睹簇,先對這個(gè)key做同樣的hash取值寥闪,確定在環(huán)上的位置疲憋,然后按照順時(shí)針方向在環(huán)上“行走”,遇到的第一個(gè)緩存節(jié)點(diǎn)就是要訪問的節(jié)點(diǎn)埃脏。比如說下面的圖剂癌,key1和key2會落入到node1中,key3,4會落到node2中旁壮,key5抡谐,node3桐猬。key6茄靠,node4。
這時(shí)如果在node1和node2之間增加一個(gè)node5料滥,你可以看到原本命中node2的key3現(xiàn)在命中node5,而其他的key都沒有變化扼雏;
同樣的道理夯膀,如果我們把node3從集群中移除诱建,那么只會影響到node5,所以在增加和刪除節(jié)點(diǎn)時(shí)茎匠,只有少數(shù)的key會“漂移”到其他節(jié)點(diǎn)上汽抚,而大部分的key命中的節(jié)點(diǎn)還是會保持不變伯病,從而可以保證命中率不會大幅下降午笛。
不過事情總有兩面性药磺。雖然這個(gè)算法對命中率的影響比較小癌佩,但還是存在問題:
- 緩存節(jié)點(diǎn)在圓環(huán)上分布不平均,會造成部分緩存節(jié)點(diǎn)的壓力較大我碟;當(dāng)某個(gè)節(jié)點(diǎn)故障時(shí)矫俺,這個(gè)節(jié)點(diǎn)所要承擔(dān)的所有訪問會轉(zhuǎn)移到另一個(gè)節(jié)點(diǎn)上厘托,會對后面的節(jié)點(diǎn)造成壓力稿湿。
- 一致性Hash算法的臟數(shù)據(jù)問題缎罢。
極端情況下,比如一個(gè)有三個(gè)節(jié)點(diǎn)ABC承擔(dān)整體的訪問舰始,每個(gè)節(jié)點(diǎn)的訪問量平均丸卷,A故障后询刹,B將承擔(dān)雙倍的壓力凹联,當(dāng)B承擔(dān)不了流量崩潰后蔽挠,C也將因?yàn)橐袚?dān)原先3倍的流量而崩潰,這就造成了整體緩存系統(tǒng)的雪崩比原。
很可怕但是不用擔(dān)心,程序員就是要能夠創(chuàng)造性的解決各種問題雇寇,所以你可以在一致性hash算法中引入虛擬節(jié)點(diǎn)的概念锨侯。
它講一個(gè)緩存節(jié)點(diǎn)計(jì)算多個(gè)hash值分散到圓環(huán)的不同位置识腿,這樣既實(shí)現(xiàn)了數(shù)據(jù)的平均造壮,而且當(dāng)某一個(gè)節(jié)點(diǎn)故障或者退出的時(shí)候耳璧,它原先承擔(dān)的key將以更加平均的方式分配到其他節(jié)點(diǎn)上旨枯,從而避免雪崩的發(fā)生。
其次攀隔,就是一致性hash算法的臟數(shù)據(jù)問題皂贩。為什么會產(chǎn)生臟數(shù)據(jù)?比如說昆汹,在集群中有兩個(gè)節(jié)點(diǎn)AB明刷,客戶端初始寫入一個(gè)key為k,值為3的緩存數(shù)據(jù)到A中满粗。這時(shí)如果要更新K的值為4辈末,但是緩存A恰好和客戶端連接出現(xiàn)了問題,那這次寫入請求會寫入到B中映皆,
接下來緩存A和客戶端的連接恢復(fù),當(dāng)客戶端要獲取K的值時(shí)捅彻,就會獲取到存再A中的臟數(shù)據(jù)3组去,而不是B中的4。
所以在使用一致性hash算法時(shí)一定要設(shè)置緩存的過期時(shí)間步淹。這樣當(dāng)發(fā)生漂移時(shí)从隆,之前存儲的臟數(shù)據(jù)可能已經(jīng)過期湾戳,就可以減少存在臟數(shù)據(jù)的幾率。
很顯然广料,數(shù)據(jù)分片最大的優(yōu)勢就是緩解緩存節(jié)點(diǎn)的存儲和訪問壓力,但同時(shí)它也讓緩存的使用更加負(fù)載幼驶,在批量獲取場景下艾杏,單個(gè)節(jié)點(diǎn)的訪問量并沒有減少,同時(shí)節(jié)點(diǎn)數(shù)太多會造成緩存訪問的SLA("服務(wù)等級協(xié)議"盅藻,SLA代表了網(wǎng)站服務(wù)可用性)得不到很好的保證购桑,因?yàn)楦鶕?jù)木桶原則,SLA取決于最慢氏淑、最壞的節(jié)點(diǎn)情況勃蜘,節(jié)點(diǎn)數(shù)過多也會增加出現(xiàn)問題的概率,因此推薦4到6個(gè)節(jié)點(diǎn)為佳假残。
2.Memcached的主從機(jī)制
redis本身支持主從的部署方式缭贡,但是Memcached并不支持,所以我們今天主要來了解一下Memcached的主從機(jī)制是如何在客戶端實(shí)現(xiàn)的辉懒。
在之前的項(xiàng)目中阳惹,我就遇到了單個(gè)節(jié)點(diǎn)故障導(dǎo)致數(shù)據(jù)穿透的問題,這時(shí)我為每一組master配置一組slave眶俩,更新數(shù)據(jù)時(shí)主從同步更新莹汤,讀取時(shí)優(yōu)先從slave中讀數(shù)據(jù),如果讀不到樹就穿透到Master讀取颠印,并且將數(shù)據(jù)回種到slave中以保持slave數(shù)據(jù)的熱度纲岭。
主從機(jī)制最大的優(yōu)點(diǎn)是當(dāng)木一個(gè)slave宕機(jī)時(shí),還會有master作為兜底线罕,不會有大量請求穿透到數(shù)據(jù)庫的情況發(fā)生止潮,提升了緩存系統(tǒng)的高可用性。
3.多副本
其實(shí)钞楼,主從方式已經(jīng)能夠解決大部分場景的問題沽翔,但是對于極端流量的場景下,一組slave通常來說不能完全承擔(dān)所有流量窿凤,slave網(wǎng)卡寬帶可能會成為瓶頸仅偎。
為了解決這個(gè)問題,我們考慮在master/slave之前增加一層副本層雳殊,整體架構(gòu)是這樣:
在這個(gè)方案中橘沥,當(dāng)客戶端發(fā)起查詢請求時(shí),請求首先會從多個(gè)副本組中選取一個(gè)副本組發(fā)起查詢夯秃,如果查詢失敗就繼續(xù)查詢master/salve座咆,并且將查詢的結(jié)果回種到所有副本組中痢艺,避免副本組中臟數(shù)據(jù)的存在。
基于成本考慮介陶,每一個(gè)副本組容量比Master和slave要小堤舒,因此它只存儲了更加熱的數(shù)據(jù)。在這套架構(gòu)中哺呜,master和slave的請求量會大大減少舌缤,為了保證他們存儲數(shù)據(jù)的熱度,在時(shí)間后只能怪我們會把master和slave作為一組副本組使用某残。
中間代理層方案
雖然客戶端方案已經(jīng)能解決大部分的問題国撵,但是只能在單一語言系統(tǒng)之間復(fù)用。例如微博用java實(shí)現(xiàn)了這一套邏輯玻墅,PHP就難以復(fù)用介牙。而中間代理層的方案就可以解決這個(gè)問題。你可以將客戶端解決方案的經(jīng)驗(yàn)移植到代理層中澳厢,通過通用的協(xié)議(如redis協(xié)議)來實(shí)現(xiàn)在其他語言中的復(fù)用环础。
如果你來自研緩存代理層,你就可以將客戶端方案中的高可用邏輯封裝在代理層代碼里剩拢,這樣用戶在使用你的代理層的時(shí)候就不需要關(guān)心緩存的高可用是如何做的喳整,只需要依賴代理層就好了。
除此以外裸扶,業(yè)界也有很多中間代理層方案框都,它們的原理基本上可以由一張圖來概括:
從圖中可以看出,所有緩存的讀寫請求都是經(jīng)過代理層完成呵晨。代理層是無狀態(tài)的魏保,主要負(fù)責(zé)讀寫請求的路由功能,并且在其中內(nèi)置了一些高可用的邏輯摸屠。
服務(wù)端方案
redis在2.4版本中提出了redis sentinel模式來解決主從redis部署時(shí)的高可用問題谓罗,它可以在主節(jié)點(diǎn)掛了以后自動將從節(jié)點(diǎn)提升為主節(jié)點(diǎn),保證整體集群的可用性季二,整體架構(gòu)如圖:
redis sentinel也是集群部署的檩咱,這樣可以避免sentinel節(jié)點(diǎn)掛掉后造成無法自動故障恢復(fù)的問題,每一個(gè)sentinel節(jié)點(diǎn)都是無狀態(tài)的胯舷。在sentinel會配置master的地址刻蚯,sentinel會時(shí)刻監(jiān)控master的狀態(tài),當(dāng)發(fā)現(xiàn)master在配置的時(shí)間間隔內(nèi)無響應(yīng)桑嘶,就認(rèn)為master已經(jīng)掛了炊汹,sentinel會從從節(jié)點(diǎn)中選取一個(gè)提升為主節(jié)點(diǎn),并且把所有其他的從節(jié)點(diǎn)作為新主的從節(jié)點(diǎn)逃顶。sentinel集群內(nèi)部在仲裁的時(shí)候讨便,會根據(jù)配置的值來決定當(dāng)有幾個(gè)sentinel節(jié)點(diǎn)認(rèn)為主掛掉可以做主從切換的操作充甚,也就是集群內(nèi)部需要對緩存節(jié)點(diǎn)的狀態(tài)達(dá)成一致才行。
redis sentinel不屬于代理層模式霸褒,因?yàn)閷τ诰彺娴膶懭牒妥x取請求不會經(jīng)過sentinel節(jié)點(diǎn)伴找,sentinel節(jié)點(diǎn)在架構(gòu)上和主從是平級的,是作為管理者存在的废菱,所以可以認(rèn)為是在服務(wù)端提供的一種高可用方案技矮。