Redis 集群的合縱與連橫

jopen 8年前發布 | 7K 次閱讀 Redis NoSQL數據庫

之前一篇寫了關于 Redis 的性能,這篇就寫寫我認為比性能更重要的擴展性方面的主題。

如果再給我一次回到好幾年前的機會,對于使用 Redis 我一開始就要好好考慮將來的擴展問題。就像我們做數據庫分庫分表,一旦決策了分庫分表,通常一次就會分到位,比如搞上 8 或 16 個庫,每個庫再分 256 或 1024 個表。不管將來業務再怎么發展,基本這個量級的分片都足夠應對,而且底層庫可以做成邏輯的,扛不住時再換成物理的,對應用方完全透明,沒有數據遷移的煩惱。

而 Redis 其實也提供了類似的邏輯庫概念,每個 Redis 實例都有 0 到 15 號獨立的邏輯庫空間。當我們早期機器資源緊張而業務量又不大時,可以好好根據業務把不同的數據放在的單一實例的不同編號邏輯庫上。這是一種垂直切分方式,也可以用水平方式,把 0 到 15 號邏輯庫當成 16 個分片來用,只是這種用法可能對 Client 庫有些要求。

總之好幾年前我們都沒有這樣,當時物理機資源緊張,為了考慮不遠將來的業務擴張,所以在有限的資源下決定盡可能的分片。但也沒分太多,大約 10 片吧,多了運維成本也高。感覺按 Redis 的性能這一組分片最大承載幾十萬每秒的 OPS 估計能支撐很長時間的發展了。那 10 片怎么部署呢?由于每個 Reids 實例只能利用一個核,當時的服務器大概是 16 核,全放一臺機也可以。當時我們正好有 10 臺物理機,所以很自然的每臺放了一個實例,但 Redis 只能用一個核,太浪費了。所以每臺物理機上除了部署 Reids 還要部署應用服務,后來領悟到這又是一個錯誤的部署方式(背景音樂:多么痛的領悟)。

一臺 PC Server 的硬件可靠性大約是 99.9%,Redis 作為一個應用全局共享的關鍵服務分成 10 片放在十臺 PC Server 上。實際上導致整體系統可靠性還降低了一個量級,變成了兩個 9。因為任何一臺 PC Server 掛了都可能導致全局系統故障。然而當初沒有多余的機器資源,為了提高可靠性,必須對 Redis 做主備,唯一的辦法就是交叉主備,所以部署結構大概類似下面這樣。

后來,隨著業務發展流量變得越來越大,Redis 內存占用越來越多,而且開始出現一些詭異的故障現象。比如出現瞬時 Redis 大量連接和處理超時,應用業務線程被阻塞,導致服務拒絕,過一段時間可能又自動恢復了。這種瞬時故障非常難抓現場,一天來上幾發就會給人業務不穩定的感受,而一般基礎機器指標的監控周期在分鐘級。瞬時故障可能發生在監控的采集間隙,所以只好上腳本在秒級監控日志,發現瞬時出現大量 Redis 超時錯誤,就收集當時應用的 JVM 堆棧、內存和機器 CPU Load 等各項指標。終于發現瞬時故障時刻 Redis 機器 CPU Load 出現瞬間飆升幾百的現象,應用和 Redis 混合部署時應用可能瞬間搶占了全部 CPU 導致 Redis 沒有 CPU 資源可用。而應用處理業務的邏輯又可能需要訪問 Redis,而 Redis 又沒有 CPU 資源可用導致超時,這不就像一個死鎖么。搞清楚了原因其實解決方法也簡單,就是分離應用和 Redis 的部署,各自資源隔離,自此我們的 Redis 集群發展開始走上一條合縱與連橫的道路。

合縱

分離應用和 Redis 的部署后,關于物理機資源的有一個尷尬點就是 Redis 單線程機制帶來的。當時一臺 PC Server 16 核,內存 16 G,你想多利用核就要多部署實例,但每個實例分到的內存又不多。最終我們一臺物理機只部署 2 個實例,因為業務發展對內存的需求強過對 CPU 的利用,所以調整后的部署模型變成下面這樣。

這樣每個 Redis 實例能分到的內存是小于 8G 的(還要給系統留一點不是)。隨著業務發展,一開始是 2G,很快 4G 然后 6G 就到了單機內存瓶頸,下一步只能分一個實例出去,每個實例獨享單機內存。縱向擴容在操作性上是最簡單的,在另外一臺機器上先掛一個從分片,同步復制完成后,通知 Client 端切換連接而分片 Hash 規則還是不變。這個過程會有短暫的(下圖 2-5 步這個過程程序執行的時間窗口)寫丟失,在業務上是可接受的。

獨享了更大內存,我們就可以繼續縱向擴內存,但擴到了 12G 后就基本到頂了,即便還有更大內存的物理機也不宜再擴大單分片的內存了。主要原因是因為 Redis 的主從復制導致的服務中斷,當初 Redis 版本是 2.4,直到 2.8 才有增量的主從復制。即使 2.8 主從復制依然可能在斷鏈長時間后導致全量復制,雖然官方文檔號稱主從復制不中斷服務,但實際每次全量復制 dump 內存時是阻斷了主線程執行。這個阻斷時間在 12G 內存時大概有一分多鐘, 繼續縱向擴大內存會導致更長時間的阻斷,在業務上不可接受,合縱之路也走到頭了。

連橫

為了對業務做到無縫透明的擴容,只能走橫向發展的道路。而 Redis 官方的 Cluster 方案一直跳票,遲遲出不來,大家的業務都在快速發展,等不及啊。所以在橫向擴展上演變出了兩種方案,一種是代理模式,利用引入中間 Proxy 來向應用層屏蔽后端的集群分布。業界最早是 推ter 開源的 Twemproxy 采用了這種模式,后來豌豆莢開源的 Codis 進一步在運維可操作性上完善了這種模式。主要是在擴容方面盡可能的做到業務無感知,思路就是前端引入 Proxy 隔離應用層,后端改造 Redis 引入 Slot(有些也叫 Buket)來分組 key。應用層訪問時基于算法將 key 先映射到 Slot 再映射到具體分片實例,大概如下面這樣。

F(key) -> Slot -> Instance

Redis 中的 key 按 Slot 來組織,平滑擴容時比如加分片后,按 Slot 為單位遷移,這通常需要改造 Redis 源碼來支持。這個模式的架構示意圖如下所示。

引入 Proxy 是犧牲了少量性能來換取了對應用的透明和更好的擴展性。另一種方案是基于 Smart Client 免代理,但對應用有一定的侵入性,本質上就是把 Proxy 的功能放到了 Client。

至于采用哪種方案就是仁者見仁、智者見智了,需要根據實際情況去考慮。不過個人認為基于代理的方案更靈活些,而且可在 Proxy 層能做的事情比 Client 要多,但對 Proxy 的實現要求也更高。不管上面哪種方案都采用了中心化的控制方式,中心化對簡化運維操作是有好處的,而且能方便做到集群全局的管理。

Redis Cluster 終于遲遲推出后,采用了與中心化不同的思路,而且設計目標更追求性能,所以是依賴 Smart Client 的方式。而 Redis Server 的實現還是使用了 Slot 方式默認最大 16 * 1024 = 16384 個 Slot,所以理論集群最大就是這么多個實例,實際不大可能需要這么大。采用 Gossip 消息來同步集群配置,基于投票機制來進行主從 Failover 發現和自動切換。從目前已經推出的版本和功能來看,作者是在往一個純 Smart Cluster 方向發展,但顯然目前的版本還不成熟。比如自動發現、集群智能再平衡等功能都沒有,還依賴人工操作。而且非中心化的集群相比中心化集群的可預測性和操作性都要差不少,其在真實的應用案例,除了在網易有道有人分享了一個非關鍵類場景,還沒見過比較有份量的成熟案例。所以 Redis Cluster 的道路恐怕還在路漫漫其修遠兮,吾將上下而求索的階段。

總結

前面回顧了 Redis 集群發展的歷程,從合縱到連橫實際是一個從業務垂直切分到平臺服務化的過程。至于到底應該采用什么樣的集群模式,可能需要好好結合自身的業務發展階段、團隊能力和企業環境去分析取舍了。沒有最好的,只有合適的。

參考

[1] antirez. Redis Cluster Specification
[2] 黃東旭. 分布式 Redis 架構設計和踩過的那些坑們
[3] 西代零零發. 全面剖析 Redis Cluster 原理和應用
[4] 西代零零發. Redis Cluster 架構優化
[5] 楊肉. Redis Cluster 使用經驗

你還可以看

Redis 的性能幻想與殘酷現實

來自: http://blog.csdn.net/mindfloating/article/details/50458768

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!