1 、背景redis
Redis的出現確實大大地提升系統大併發能力支撐的可能性,轉眼間Redis的最新版本已是3.X版本了,但咱們的系統依然繼續跑着2.8,並很好地支撐着咱們當前天天5億訪問量的應用系統。想當年Redis的單點單線程特性沒法知足咱們日益壯大的系統,只能硬着頭皮把Redis「集羣化」負載。且這套「集羣化」方案良好地運行至今。雖難度不高,勝在簡單和實用。不管簡單仍是很簡單,記錄這種經歷是一件很是有趣的事情。數據庫
2 、問題緩存
系統訪問量日益倍增,當前的Redis單點服務確實客觀存在連續可用性以及支撐瓶頸風險,這種主/備模式在服務故障突發的狀況下就會被動中止服務進行Redis節點切換。針對單點問題,咱們結合自身的業務應用場景對Redis「集羣化」提出幾個主要目標:性能優化
一、避免單點狀況,確保服務高可用;bash
二、緊可能把數據分佈式存儲,下降故障影響範圍,知足服務靈活伸縮;服務器
三、控制「集羣化」的複雜度,從而控制邊際成本;架構
3 、過程併發
以上目標1和2就是所謂的分佈式集羣方案,把大問題分而治之。但最難把控的是目標3的「簡化」實現。基於當時開源社區的那幾種Redis集羣方案,對於咱們「簡化」的要求來講相對略顯臃腫。因此仍是決定結合自身的業務應用等因素打造一個「合適」的Redis集羣。框架
初始,咱們憑藉本身對分佈式集羣的認識勾結合應用場景勾勒出一個咱們以爲足夠「簡化」的設計圖,而後在這個「簡化」架構的基礎上繼續擊破咱們各類應用場景所帶來的缺陷。架構圖以下:分佈式
不難看出,咱們想盡可能經過一個RedisManager類和配置文件就能管理整個集羣,不須要而外的軟件支持。單例使用的時候RedisManager和配置文件就已經存在,RedisManager有各類單例操做的API重寫(如get、set等),如今咱們仍是想保持這種模式對業務處理提供集羣API,保持整個服務化應用框架(相似於今天所倡導的「微服務」)的輕量級特性。如上圖所示,數據根據hash實現分紅不一樣塊放在不一樣的hash節點上,而每一個hash節點必須存在兩個Redis實例作hash節點集羣支撐。爲何會是兩個而不是三個或可擴展多個?咱們是這樣考慮的:
一、任何可持續擴展或抽象是站在規範這個巨人的肩膀上,咱們秉承了整個系統架構「約定遠遠大於配置」的原則,適當地限制了邊界範圍換取控制性而又不失靈活。
二、對於咱們系統目前的服務器質量來講,宕機的機率較小,雙機(雙實例)同時宕機的機率更小。就算這個機率出現,咱們衆多的業務場景仍是容許這種部分間接性故障。這就是成本與質量之間的平衡和取捨。
三、因爲咱們沒有使用額外的軟件輔助,這些額外的操做都依賴了線程額外性能去彌補,例如兩個Redis實例負載之間的同步等,因此咱們是用性能換取部分一致性。負載節點越多性能消耗越多,因此兩個實例作負載是咱們「適當約束」和衡量的決策。
在此我向你們推薦一個架構學習交流羣。交流學習羣號:575745314 裏面會分享一些資深架構師錄製的視頻錄像:有Spring,MyBatis,Netty源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化、分佈式架構等這些成爲架構師必備的知識體系。還能領取免費的學習資源,目前受益良多
4 、場景
4.1 場景 1
在RedisManager類中有兩個最基本的API,那就是c_get和c_set,其中c表明cluster。這兩個API的基本實現以下:
從這兩個基本操做能夠看出,咱們利用了遍歷hash節點的全部負載實例來實現高可用性,並經過「同步寫」來知足Redis數據「弱一致性」問題。而這個「同步寫」就是額外的性能消耗,是依賴於雙寫過程當中只成功寫入一個實例的機率。由於Redis的穩定性,這種機率不高,因此額外性能消耗的機率也不高。以上操做幾乎適用全部緩存類集羣支持。這類緩存數據的強一致性更多放在數據庫。數據在庫表變動後,只須要把緩存數據delete便可。具體場景以下:
一、數據的變動經過「後臺」維護,變動後同步DELETE緩存的相互數據。
二、業務線程請求緩存數據爲空(c_get),則查詢書庫並把數據同步至緩存(c_set)。
三、Redis的hash節點集羣安裝集羣內部實現負責和數據同步問題(c_get和c_set的實現原來)。
咱們大部分業務數據緩存都是基於以上流程實現,這個流程會存在一個髒數據問題,例如當UPDATE庫表成功但DELETE緩存數據不成功,就會存在髒數據。爲了能儘量下降髒數據的可能性,咱們會在緩存設置緩存數據一個有效期(setex),就算髒數據出現,也只會影響seconds時間段。另外在後臺變動過程若是DELETE緩存失敗咱們會有適當的提示語提示,好讓認爲發現繼續進一步處理(例如從新變動)。
4.1 場景 2
除了場景1的緩存用途外,還存在持久化場景。就是基於Redis作數據持久化集羣,即全部操做都是基於Redis集羣的。那麼在數據一致性問題上就須要下點功夫了(c_set_sync),僞代碼以下:
c_set_sync(key,value){
if(c_del_sync(key)){
c_set(key,value);
}
}
c_del_sync(key){
if(del(r1,key)&&del(r2,key)){
return true;
}
return false;
}
複製代碼
經過以上僞代碼能夠看出,c_set_sync方法是先強制所有刪除數據後再c_set,確保數據一致性。但這會出現一個數據丟失問題,就是c_del_sync後但set失敗,那數據就會丟失,由於咱們的數據幾乎都是從後臺操做的,若是出現這種數據丟失,簡單的咱們能夠從新配置,複雜的咱們能夠經過日誌恢復。
5 伸縮
以上兩個場景更多圍繞C(一致性)和A(可用性)的特性進行討論,那麼接下來再介紹一下咱們「集羣化」的P(分區容錯性)特性。其實從我思考觸發就能夠看得出咱們對P的權重是輕於C和A的。爲何這麼說?由於咱們系統架構是服務化架構(那時我還沒接觸到「微服務」概念),也就是從問題角度把大問題(業務統稱)拆分各類小問題(服務化)逐一獨立解決。各類小問題的業務複雜度咱們緊可能控制到必定的輕量級程度(若是要量化解釋的話那就是服務保持在10到20個API的規模,甚至小於10)。並且每一個服務的承載量增加率預估值也在可控範圍,因此到目前爲止,極少有對Redis集羣進行伸縮的需求。但少數的伸縮仍是存在,但頻率不高。對於一個完整的集羣化方案,伸縮功能必須得有,只不過可能須要像以上兩種場景那樣針對不一樣業務使用場景在「規範化(原架構基礎上)」下定製出各類場景API。
5.1 場景 1
由於緩存場景相對簡單,擴展或收縮hash節點後,若是在緩存中找不到數據,則會訪問數據庫從新Load數據到新的hash節點。伸縮期完成初期可能會對數據庫帶來必定的壓力,這種壓力的大小來源於設計hash數據的變化大小,這種數據變化大小取決於從新這個hash實現規則的變化的大小。因此,可根據具體狀況來重寫hash規則。
還有一個就是數據一致性問題(C和P),如何在動態伸縮過程當中,確保緩存數據一致性。爲了解決這個問題,咱們在動態擴展過程當中,中止各類更新接口操做。由於咱們的數據變動都是經過管理員的,因此這個代價能夠忽略不計。
5.2 場景 2
此場景2對應是卻倒是4.2場景,若是用Redis集羣作持久化工具,若是確保分區容錯性(P)和數據一致性(C)。對於數據一致性問題,咱們一樣選擇了場景1的辦法,在伸縮期間中止全部更新操做,只保留讀。這就避免了數據一致性問題。對於分區容錯性問題,那就是若是確保從新hash後,數據能流向各類的新hash節點呢。爲了繼續保持這種「簡化性」框架,咱們繼續選擇了犧牲必定的性能來知足分區容錯性問題,具體實現以下所示:
一、先嚐試從New_Hash節點讀取;
二、若不存在則繼續尋找Old_Hash節點;
三、若仍是不存在,怎放回空;
四、若存在則c_set到New_Hash節點。
經過以上流程分析看到,咱們犧牲了部分線程性能(第一次訪問的變動數據的線程)的性能,可能會多2到3此的redis請求(每一個Redis請求約5至10毫秒)。當伸縮完成後,從新放開數據更新API(咱們服務化框架全部業務API均可以經過控制檯控制併發並設置相關提示語,無需重啓應用)。除了讀取須要遍歷新舊hash節點外,爲了確保數據一致性問題,咱們c_del_sync內置了一個判斷是否存在舊hash節點。僞代碼以下:
c_del_sync(key){
if(hash_old){
if(del(nr1,key)&&del(nr2,key) &&del(or1,key) &&del(or2,key)) {
true;
}
}else{
if(del(r1,key)&&del(r2,key)){
true;
}
}
return false;
}
複製代碼
這種場景比較適用於set操做很少的場景,由於多set操做會多消耗約一倍的性能,若是以爲資源充足,這固然能夠考慮。
在此我向你們推薦一個架構學習交流羣。交流學習羣號:575745314 裏面會分享一些資深架構師錄製的視頻錄像:有Spring,MyBatis,Netty源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化、分佈式架構等這些成爲架構師必備的知識體系。還能領取免費的學習資源,目前受益良多
6 、總結
以上集羣化方案已經運行約兩年,系統日訪問量約億,70%歸功於Redis的支撐。以上集羣方案是基於咱們的行業業務場景和自身框架量身定作的,我更多地是想分享解決這個問題的思路和過程。世界上沒有「絕對通用」,只有「相對通用」,通用範圍越廣,臃腫程度越高,可能帶來的成本就會越大。咱們更多的是面對若是「很好地」解決問題,這個「很好地」隱藏着各類各樣的考慮因素。抉擇就是一個爲了能達到最佳效果而去衡量、選擇、放棄的過程。