Redis集群案例與場景分析


 

1、背景

Redis的出現確實大大地提高系統大並發能力支撐的可能性,轉眼間Redis的最新版本已經是3.X版本了,但我們的系統依然繼續跑着2.8,並很好地支撐着我們當前每天5億訪問量的應用系統。想當年Redis的單點單線程特性無法滿足我們日益壯大的系統,只能硬着頭皮把Redis“集群化”負載。且這套“集群化”方案良好地運行至今。雖難度不高,勝在簡單和實用。無論簡單還是很簡單,記錄這種經歷是一件非常有趣的事情。

 

2、問題

系統訪問量日益倍增,當前的Redis單點服務確實客觀存在連續可用性以及支撐瓶頸風險,這種主/備模式在服務故障突發的情況下就會被動停止服務進行Redis節點切換。針對單點問題,我們結合自身的業務應用場景對Redis“集群化”提出幾個主要目標:

1、避免單點情況,確保服務高可用;

2、緊可能把數據分布式存儲,降低故障影響范圍,滿足服務靈活伸縮;

3、控制“集群化”的復雜度,從而控制邊際成本;

 

3、過程

以上目標1和2就是所謂的分布式集群方案,把大問題分而治之。但最難把控的是目標3的“簡化”實現。基於當時開源社區的那幾種Redis集群方案,對於我們“簡化”的要求來說相對略顯臃腫。所以還是決定結合自身的業務應用等因素打造一個“合適”的Redis集群。

初始,我們憑借自己對分布式集群的認識勾結合應用場景勾勒出一個我們覺得足夠“簡化”的設計圖,然后在這個“簡化”架構的基礎上繼續擊破我們各種應用場景所帶來的缺陷。架構圖如下:

不難看出,我們想盡量通過一個RedisManager類和配置文件就能管理整個集群,不需要而外的軟件支持。單例使用的時候RedisManager和配置文件就已經存在,RedisManager有各種單例操作的API重寫(如get、set等),現在我們還是想保持這種模式對業務處理提供集群API,保持整個服務化應用框架(類似於今天所倡導的“微服務”)的輕量級特性。如上圖所示,數據根據hash實現分成不同塊放在不同的hash節點上,而每個hash節點必須存在兩個Redis實例做hash節點集群支撐。為什么會是兩個而不是三個或可擴展多個?我們是這樣考慮的:

1、任何可持續擴展或抽象是站在規范這個巨人的肩膀上,我們秉承了整個系統架構“約定遠遠大於配置”的原則,適當地限制了邊界范圍換取控制性而又不失靈活。

2、對於我們系統目前的服務器質量來說,宕機的概率較小,雙機(雙實例)同時宕機的概率更小。就算這個概率出現,我們眾多的業務場景還是允許這種部分間接性故障。這就是成本與質量之間的平衡和取舍。

3、由於我們沒有使用額外的軟件輔助,這些額外的操作都依賴了線程額外性能去彌補,例如兩個Redis實例負載之間的同步等,所以我們是用性能換取部分一致性。負載節點越多性能消耗越多,所以兩個實例做負載是我們“適當約束”和衡量的決策。

 

4、場景

4.1 場景1

在RedisManager類中有兩個最基本的API,那就是c_get和c_set,其中c代表cluster。這兩個API的基本實現如下:

從這兩個基本操作可以看出,我們利用了遍歷hash節點的所有負載實例來實現高可用性,並通過“同步寫”來滿足Redis數據“弱一致性”問題。而這個“同步寫”就是額外的性能消耗,是依賴於雙寫過程中只成功寫入一個實例的概率。因為Redis的穩定性,這種概率不高,所以額外性能消耗的概率也不高。以上操作幾乎適用所有緩存類集群支持。這類緩存數據的強一致性更多放在數據庫。數據在庫表變更后,只需要把緩存數據delete即可。具體場景如下:

1、數據的變更通過“后台”維護,變更后同步DELETE緩存的相互數據。

2、業務線程請求緩存數據為空(c_get),則查詢書庫並把數據同步至緩存(c_set)。

3、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節點呢。為了繼續保持這種“簡化性”框架,我們繼續選擇了犧牲一定的性能來滿足分區容錯性問題,具體實現如下所示:

1、先嘗試從New_Hash節點讀取;

2、若不存在則繼續尋找Old_Hash節點;

3、若還是不存在,則返回空;

4、若存在則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操作會多消耗約一倍的性能,如果覺得資源充足,這當然可以考慮。這種同步方式還有一個明顯缺點就是短期內多次伸縮,會丟失Hash_Old_Old_....的數據,因為每次Hash_Old都無法能確保在下一次伸縮前1能00%同步所有數據(同步數據需要依賴數據訪問,不排除部分數據一直沒有被訪問)。

 

6、總結

以上集群化方案已經運行約兩年,系統日訪問量約5億,70%歸功於Redis的支撐。以上集群方案是基於我們的行業業務場景和自身框架量身定做的,我更多地是想分享解決這個問題的思路和過程。世界上沒有“絕對通用”,只有“相對通用”,通用范圍越廣,臃腫程度越高,可能帶來的成本就會越大。我們更多的是面對如果“很好地”解決問題,這個“很好地”隱藏着各種各樣的考慮因素。抉擇就是一個為了能達到最佳效果而去衡量、選擇、放棄的過程。

 

注:畢竟個人能力有限,這套集群方案可能存在不周全的地方,僅供參考,若發現缺陷,懇請指點,感謝!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM