京東八年架構師: Redis 如何分散式,金融的設計原理

認真期待發表於2018-04-20

前言

R2M 是京東金融線上大規模應用的分散式快取系統,目前管理的機器總記憶體容量超過 60TB,近 600 個 Redis Cluster 叢集,9200 多個 Redis 例項。

其主要功能包括:全 web 視覺化運維、快取叢集一鍵部署、資源池統籌管理、線上擴容及快速資料遷移、多機房切換及容災、完善的監控及告警、Redis API 相容等。

本文將從 R2M 系統架構、資源管理、叢集擴容與遷移、資料冷熱交換、多機房容災等多方面進行深入剖析,希望讀者能有所收穫。

業務使用及運維上的優點

極簡化接入

R2M 接入簡單,以 Java 客戶端為例,只需在業務程式中引入客戶端 jar 包,配置好 appname 及 ZK 地址,即可使用。

配置自動下發

R2M 客戶端提供了諸如連線池、讀寫超時時間、重試次數等配置,有些情況下,需要對這些配置進行調優,比如 618、雙十一大促來臨時,有些業務需要減小客戶端讀寫超時時間,避免超時問題引發的連鎖反應。

為了修改配置,業務不得不重新上線所有的客戶端,不僅費時費力,還有可能引發故障。因此,為了避免這個問題,R2M 提供了配置自動下發功能,業務客戶端無需重新上線,管理端修改配置後,所有客戶端即時生效。

多種資料型別 API 支援

完全相容 Redis 各資料型別 API 介面,提供包括雜湊、集合、有序集合、列表、釋出 / 訂閱等近百個介面。

支援資料冷熱交換

在保證高效能和資料一致性的前提下,實現了基於 LFU 熱度統計的資料動態冷熱交換,使得冷資料被交換到 SSD 上,熱資料被載入到 Redis 上,取得高效能和低成本的高平衡。

全 web 視覺化運維

R2M 實現了包括叢集部署、擴容、資料遷移、監控、叢集下線、資料清理、故障節點替換及機器下線等在內的所有功能的視覺化運維,運維效率及可用性大大提升。

多機房一鍵切換

通過改造 Redis Cluster 支援多機房災備、防止叢集腦裂,以及各系統元件針對多機房切換選舉的支援,實現了在 Web 控制檯的多機房一鍵切換。

多種方式的叢集同步及資料遷移工具

R2M 提供了專門的遷移工具元件,支援從原生 Redis 遷移至 R2M,實現了基於 Redis RDB 檔案傳輸及增量同步的遷移機制。

同時,還可以指定規則比如按照字首匹配或者反向匹配進行部分資料遷移。由於內部歷史原因,京東金融部分業務以前用的是京東商城的 Jimdb,R2M 同樣也支援了從 Jimdb 叢集進行遷移。

R2M 遷移工具還支援資料實時同步的功能,包括 R2M 叢集間的資料同步和 Jimdb 源叢集的資料同步。

非 Java 語言代理

我們的業務開發人員主要用的是 Java,對於非 Java 語言的客戶端,比如 Python、C 等等,則通過 R2M Proxy 元件接入,各種運維操作在 Proxy 層進行,對業務方遮蔽。同時,Proxy 也提供了高可用保障方案。

系統架構

元件功能

Web console

是 R2M 快取系統的視覺化運維控制檯。所有運維操作均在 Web console 進行。

Manager

整個系統的管理元件。負責所有運維操作的下發、監控資料的收集等。運維操作包括叢集建立、資料遷移、擴容、多機房切換等等。

Agent

每臺物理機上部署一個 Agent 元件,Agent 負責本機 Redis 例項的部署和監控,並進行資料上報。

快取叢集節點

每個叢集的節點分佈在不同機器上,若干個節點構成一個分散式叢集,去中心化,無單點。

Client

客戶端由業務方引入,如:Java 通過 jar 包方式引入。

Proxy

對於非 Java 客戶端的業務,通過 Proxy 提供快取接入和服務。

快取叢集一鍵部署

分散式叢集的部署通常來說是比較麻煩的,涉及到多臺機器、多個節點及一系列的動作和配置,比如部署 Redis Cluster,就包括節點安裝與啟動、節點握手、主從分配、插槽分配、設定複製這些步驟。

雖然官方提供了構建叢集的工具指令碼,但機器推薦、節點安裝及配置仍然沒有自動化,對於需要大規模部署和運維 Redis Cluster 的企業來說,仍然不夠靈活和便捷。

因此,R2M 基於 Golang 實現了在 Web 控制檯一鍵完成從機器推薦、節點部署、叢集構建、到節點配置的所有動作,這個過程中每臺機器上的 Agent 元件負責下載並安裝 RPM 包、在指定埠上啟動例項,Manager 元件則負責完成叢集構建和節點配置過程。

自動部署過程中,還有一些必要的驗證條件和優先規則,主要是以下幾點:

1.檢查主節點數量和主備策略,是否滿足分散式選舉及高可用的最低配置條件:三個以上主節點、一主一從。

2.避免同一個叢集多個節點部署在相同機器上,防止機器故障,優先推薦符合條件的機器。

3.根據機器可用記憶體,優先推薦剩餘可用記憶體多的機器。

4.根據主節點數量,均衡分配插槽數量。

資源規劃與統籌管理

由於機房、機器、業務數量眾多,要做好平臺化管理,需要對資源進行合理的事先規劃,事先規劃包括:申請接入時業務方對容量進行預估,合理分配快取例項大小和數量、機器的預留記憶體,為了方便管理和統計,通常還需要根據機房對機器進行分組、或者根據業務進行機器分組。

做好事先規劃的同時,還需要對資源使用情況做到一目瞭然,避免出現超用或嚴重超配的情況。

嚴重超配,就是實際使用量遠小於預估容量的情況,這種情況還是很多的,因為很多時候容量很難準確預估,或者有的業務開發為了保險起見或擔心不好擴容,申請的容量往往遠大於實際需要,對於這種情況,我們可以進行一鍵縮容,防止資源浪費。

對於 超用,也就是機器實際資源使用量超過了標準,則是要非常注意的,快取機器超用比如 Redis 機器超用可能導致非常嚴重的後果,比如 OOM、發生資料淘汰、效能急劇下降,為了避免超用,需要進行合理的資源預留、選擇合適的淘汰策略,同時平臺要有完善的監控和實時的報警功能。

R2M 通過對機房、分組、機器、例項的層級管理和監控,方便我們更好地對資源進行規劃,並最大限度的統籌和平衡資源利用,防止資源浪費和機器超用。

擴容及資料遷移

業務在申請快取時,我們都會要求預估容量,根據預估值進行分配,但預估值經常是不準確的,計劃也永遠趕不上變化,業務場景擴充、業務量和資料的增長總是無法預測的。

因此,良好的擴容機制顯得尤為重要,擴容做得好不好,決定了系統的擴充套件性好不好。在 R2M 中,我們將水平擴容解耦為兩個步驟,新增新節點和資料遷移,新增新節點其實就是一個自動部署的過程,很多步驟與叢集建立過程是相同的,那麼關鍵就在於如何解決資料遷移問題了。

資料遷移主要解決以下問題:

1.如何做到不影響業務的正常讀寫,即業務方對遷移是基本無感知的?

2.如何保證遷移的速度?

3.當一條資料處於遷移中間狀態時,如果此時客戶端需要對該資料進行讀寫,如何處理?

4.遷移過程中收到讀操作,是讀源節點還是目標節點,如何確保客戶端不會讀到髒資料?

5.遷移過程中是寫源節點還是寫目標節點,如果確保不寫錯地方、不會由於遷移使得新值被舊值覆蓋?

6.遷移完成,如何通知客戶端,將讀寫請求路由到新節點上?

為了解決這些問題,我們充分吸取了 Redis Cluster 的優點,同時也解決了它的一些不足之處和缺點。

資料遷移原理

上圖就是 Redis Cluster 的資料遷移原理圖,遷移過程是通過源節點、目標節點、客戶端三者共同配合完成的。

Redis Cluster 將資料集分為 16384 個插槽,插槽是資料分割的最小單位,所以資料遷移時最少要遷移一個插槽,遷移的最小粒度是一個 key,對每個 key 的遷移可以看成是一個原子操作,會短暫阻塞源節點和目標節點上對該 key 的讀寫,這樣就使得不會出現遷移“中間狀態”,即 key 要麼在源節點,要麼在目標節點。

如上圖,假設正在遷移 9 號插槽,首先會在源節點中將 9 號插槽標記為“正在遷移”狀態,將目標節點中 9 號插槽標記為“正在匯入”狀態,然後遍歷遷移該插槽中所有的 key。

此時,如果客戶端要對 9 號插槽的資料進行訪問,如果該資料還沒被遷移到目標節點,則直接讀寫並返回,如果該資料已經被遷移到目標節點,則返回 Ask 響應並攜帶目標節點的地址,告訴客戶端到目標節點上再請求一次。

如果 9 號插槽的資料被全部遷移完成,客戶端還繼續到源節點進行讀寫的話,則返回 Moved 響應給客戶端,客戶端收到 Moved 響應後可以重新獲取叢集狀態並更新插槽資訊,後續對 9 號插槽的訪問就會到新節點上進行。這樣,就完成了對一個插槽的遷移。

多節點並行資料遷移

Redis Cluster 是基於 CRC16 演算法做 hash 分片的,在插槽數量差不多且沒有大 key 存在的情況下,各節點上資料的分佈通常非常均衡,而由於資料遷移是以 key 為單位的,遷移速度較慢。

當資料量暴漲需要緊急擴容時,如果一個接一個地進行主節點資料遷移,有可能部分節點還沒來得及遷移就已經把記憶體“撐爆”了,導致發生資料淘汰或機器 OOM。

因此,R2M 實現了多節點並行資料遷移,防止此類問題發生,同時也使資料遷移耗時大大縮短,另外,結合 Redis 3.0.7 之後的 pipeline 遷移功能,可以進一步減少網路互動次數,縮短遷移耗時。

可控的資料遷移

水平擴容新增新節點後,為了做資料 / 負載均衡,需要把部分資料遷移到新節點上,通常資料遷移過程是比較耗時的,根據網路條件、例項大小和機器配置的不同,可能持續幾十分鐘至幾個小時。

而資料遷移時可能對網路造成較大的壓力,另外,對於正在遷移的 slot 或 keys,Redis Cluster 通過 ASK 或 MOVED 重定向機制告訴客戶端將請求路由至新節點,使得客戶端與 Redis 多發生一次請求響應互動。

並且通常客戶端的快取讀寫超時比較短 (通常在 100~500ms 以內),在多重因素的作用下,有可能造成大量讀寫超時情況,對線上業務造成較大的影響。

基於此,我們實現了遷移任務暫停和遷移任務繼續,當發現遷移影響業務時,隨時可以暫停遷移,待業務低峰期再繼續進行剩餘的資料遷移,做到靈活可控。

自動擴容

R2M 還提供了自動擴容機制,開啟自動擴容後,當機器可用記憶體足夠時,如果例項已使用容量達到或超過了預設的閥值,則自動對容量進行擴大。

對於一些比較重要的業務,或不能淘汰資料的業務,可以開啟自動擴容。當然,自動擴容也是有條件的,比如不能無限制的自動擴容,例項大小達到一個比較高的值時,則拒絕自動擴容,還要預留出一部分記憶體進行 Fork 操作,避免機器發生 OOM。

資料冷熱交換儲存

由於我們線上存在很多大容量 (幾百 GB 或幾個 TB) 快取叢集,快取機器記憶體成本巨大,線上機器總記憶體容量已達到 66TB 左右。

經過調研發現,主流 DDR3 記憶體和主流 SATA SSD 的單位成本價格差距大概在 20 倍左右,為了優化基礎設施 (硬體、機房機櫃、耗電等) 綜合成本,因此,我們考慮實現基於熱度統計的資料分級儲存及資料在 RAM/FLASH 之間的動態交換,從而大幅度降低基礎設施綜合成本,並達到效能與成本的高平衡。

R2M 的冷熱交換儲存的基本思想是:基於 key 訪問次數 (LFU) 的熱度統計演算法識別出熱點資料,並將熱點資料保留在 Redis 中,對於無訪問 / 訪問次數少的資料則轉存到 SSD 上,如果 SSD 上的 key 再次變熱,則重新將其載入到 Redis 記憶體中。

思想很簡單,但實際上做起來就不那麼容易了,由於讀寫 SATA SSD 相對於 Redis 讀寫記憶體的速度還是有很大差距的,設計時為了避免這種效能上的不對等拖累整個系統的效能、導致響應時間和整體吞吐量的急劇下降,我們採用了多程式非同步非阻塞模型來保證 Redis 層的高效能,通過精心設計的硬碟資料儲存格式、多版本 key 惰性刪除、多執行緒讀寫 SSD 等機制來最大限度的發揮 SSD 的讀寫效能。

多程式非同步模型,主要是兩個程式,一個是 SSD 讀寫程式,用於訪問 SSD 中的 key,一個是深度改造過的 Redis 程式,用於讀寫記憶體 key,同時如果發現要讀寫的 key 是在 SSD 上,則會將請求轉發給 SSD 讀寫程式進行處理。

Redis 程式這一層,最開始我們其實是基於 Redis 3.2 做的,但 Redis 4 出了 RC 版本之後尤其是支援了 LFU、Psync2、記憶體碎片整理等功能,我們就果斷的切到 Redis 4 上進行改造開發了。

SSD 讀寫程式,起初是基於開源的 SSDB 進行開發,不過由於 SSDB 的主從複製實現效能很差,資料儲存格式設計還不夠好,與 Redis API 有很多的不相容問題,最終除了基本的網路框架外,基本重寫了 SSDB。

另外由於 SSDB 預設採用的儲存引擎是 leveldb,結合功能特性、專案活躍度等方面的原因,我們改成了比較流行的 RocksDB,當然,其實它也是發源於 leveldb 的。

目前我們內部已完成了該專案的開發,並進行了全面的功能、穩定性和效能測試,即將上線。由於冷熱交換儲存涉及的內容較多,由於篇幅原因,這裡不再詳述。該專案已命名為 swapdb,並在 Github 開源

在此我向大家推薦一個架構學習交流群。交流學習群號: 744642380, 裡面會分享一些資深架構師錄製的視訊錄影:有Spring,MyBatis,Netty原始碼分析,高併發、高效能、分散式、微服務架構的原理,JVM效能優化、分散式架構等這些成為架構師必備的知識體系。還能領取免費的學習資源,歡迎大家加群。

多機房切換及容災支援

多機房切換是一個從上到下各元件協調聯動的過程,要考慮的因素非常多,在 R2M 中,主要包括快取叢集、路由服務 (如:ZooKeeper、etcd) 及各個元件 (Manager、Web console、客戶端) 的多機房切換。

資料層的多機房切換——即快取服務的多機房切換

對於多機房支援,關鍵在於如何避免 腦裂 問題。我們先來看看腦裂是如何發生的。

正常情況下,一個快取叢集的節點部署在同一個機房,按最低三主、一主一從進行部署,每個主節點負責一部分資料,如果主節點掛了,剩餘主節點通過選舉將它的從節點提升為新的主節點,即自動 failover。

如果要進行機房切換或對重要的業務進行多機房容災,則需要在另一個機房對每個主節點再新增一個從節點,那麼每個主節點將有兩個從,執行過程中,如果叢集中有節點發生自動 failover,主節點可能被 failover 到另一個機房,結果,就會出現同一個叢集的主節點分佈在不同機房的情況。

這種情況下,如果機房間網路鏈路出現問題,A 機房的主節點與 B 機房的主節點會互相認為對方處於 fail 狀態,假設多數主節點都在 A 機房,那麼 A 機房的主節點會從同機房的從節點中選出新的主節點來替代 B 機房的主節點,導致相同的分片同時被分屬兩個機房的主節點負責,A 機房的客戶端把這些分片的資料寫到了 A 機房新選出的主節點,B 機房的客戶端仍然把資料寫到了 B 機房的主節點上,從而造成腦裂導致資料無法合併。

熟悉 Redis Cluster 的同學可能知道,Redis Cluster 有一個 cluster-require-full-coverage 的引數,預設是開啟的,該引數的作用是:只要有節點當機導致 16384 個分片沒被全覆蓋,整個叢集就拒絕服務,大大降低可用性,所以實際應用中一定要關閉。但帶來的問題就是,如果出現上述問題,就會造成腦裂帶來的後果。

為了解決這個問題,我們在 Redis 的叢集通訊訊息中加入了 datacenter 標識,收到自動 failover 請求時,主節點會將自己的 datacenter 標識與被提名做 failover 的從節點的 datacenter 標識進行比較,如果一致,則同意該節點的自動 failover 請求,否則,拒絕該請求。

保證自動 failover 只會發生在同機房從節點上,避免主節點分佈在多個機房的情況。而對於手動 failover 或強制 failover,則不受此限制。針對 Redis 多機房支援的功能,已經向 Redis 官方提交了 pull request,作者 antirez 大神在 Redis 4.2 roadmap 中表示會加入這塊功能。

目前,R2M 的機房正常切換及容災切換都已經實現了在 Web console 的一鍵切換。當需要做機房正常維護或遷移時,就可以通過手動 failover 將主節點批量切到跨機房的從節點上。

值得一提的是,正常切換過程保證切換前後主從節點的資料一致。當機房由於斷電或其它原因出故障時,通過強制 failover 將主節點批量切到跨機房的從節點上,由於是突發事件,少量資料可能還未同步給從節點,但這裡的主要目的是容災、及時恢復服務,可用性重要程度要遠大於少量的資料不一致或丟失。

系統元件的多機房切換

快取叢集路由服務 (ZK) 的多機房切換

業務客戶端通過路由服務拿到對應的快取節點地址,在我們的生產環境中,每個機房的 ZK 都是獨立部署的,即不同機房的 ZK 例項屬於不同的 ZK 叢集,同時每個機房的業務客戶端直接訪問本機房的 ZK 獲取快取節點。

在 R2M 中,每個機房的 ZK 路由節點儲存的配置全部相同,我們保持 ZK 路由節點中的資訊儘量簡單,所有叢集狀態相關的東西都不在 ZK 上,基本是靜態配置,只有擴容或下線節點時才需要更改配置。所以,當機房切換時,不需要對 ZK 做任何變更。

客戶端的多機房切換

業務客戶端本身是多機房部署的,不存在多機房問題,但客戶端需要在快取叢集發生多機房切換後及時把服務路由到切換後的機房,這就需要通知分佈於多個機房的各個客戶端,並與每個客戶端維持或建立連線,無疑是一大麻煩事。

在 R2M 中,由於客戶端是 smart client,當感知到異常時,可以從存活的節點中重新獲取叢集狀態,自動感知節點角色變更並切換,也就不存在通知的問題了。

Manager 元件的多機房切換

Manager 是快取叢集的管理元件,運維操作包括機房切換操作都是通過它來進行,所以必須做到 Manager 本身的多機房才能保證可以隨時進行機房容災切換或正常切換。

需要注意的是,由於不同機房的 ZK 是獨立的,Manager 的多機房切換不能直接依賴於 ZK 來實現,因為有可能剛好被依賴的那個 ZK 所在的機房掛掉了,所以,我們實現了 Manager 的多機房選舉 (類 Raft 機制) 功能,多個 Manager 可以自行選舉 leader、自動 failover。

Web console 的多機房切換

對於 Web console 的多機房切換,這個就相對簡單了。由於 Web 是無狀態的,直接通過 Nginx 做負載均衡,只要有任意一個機房的 Web 元件可用就可以了。

監控、告警及問題排查

1.業務出現呼叫超時、效能指標出現抖動怎麼辦?

2.一次業務呼叫可能涉及多個服務,比如訊息佇列、資料庫、快取、RPC,如何確認不是快取的問題?

3.如果是快取服務的問題,是機器問題、網路問題、系統問題、還是業務使用不當問題?

當出現上述問題時,面對大量機器、叢集以及無數的例項,如果沒有一套完善的監控與告警系統,沒有一個方便易用的視覺化操作介面,那隻能茫然無措、一籌莫展,耐心地一個一個例項的看日誌、查 info 輸出,查網路、查機器,所謂人肉運維。

因此,R2M 提供了各種維度的監控指標和告警功能,及時對異常情況進行預警,當問題出現時,也能夠快速定位排查方向。

機器和網路指標

每臺機器的網路 QoS(丟包率、包重傳次數、傳送接收錯誤數)、流入流出頻寬、CPU、記憶體使用量、磁碟讀寫速率等。

系統引數指標

每個節點的記憶體使用量、網路流入流出流量、TPS、查詢命中率、客戶端 TCP 連線數、key 淘汰數、慢查詢命令記錄、例項執行日誌等。

實時監控及歷史統計圖表

實時和歷史統計圖表是對各項引數指標的實時反饋和歷史走勢的直觀展示,不僅能在出問題時快速給出定位的方向,還能夠對運維和業務開發提供非常有價值的參考資料,這些參考資料也反過來促進業務系統進行優化、促進更好地運維。

對於例項的歷史監控統計資料,由每個機器上部署的 Agent 元件負責收集並上報。由於例項數及監控項非常多,監控資料量可能會非常大。

為了避免監控資料佔用大量資料庫空間,對於歷史統計資料,我們會保留展示最近 12 小時以分鐘為維度、最近一個月以小時為維度、以及最近一年以天為維度的資料,12 小時以前的分鐘資料自動合併為小時維度的資料,一個月以前的小時資料自動合併為天維度的資料,在合併時,保留這個時間段的指標最高值、最低值、以及累加後的平均值。

對於實時監控圖表,則是使用者請求檢視時才與對應的快取例項建立連線,直接從例項獲取實時資訊。

客戶端效能指標

每個客戶端的 TP50、TP90、TP99、TP999 請求耗時統計,快速定位問題 IP。

告警項

容量告警、流入流出流量、TPS、例項阻塞、例項停止服務、客戶端連線數等。

總結

這裡只對 R2M 快取系統大致的設計思路和功能做一個簡要的介紹,很多細節和原理性的東西沒能一一詳述,比如資料的冷熱交換,實際上做這個東西的過程中我們遇到了很多的挑戰,也希望以後能對這塊做詳細的介紹。最後,也希望有更多的技術同仁擁抱和參與開源,讓大家有更多更好的輪子用。

相關文章