HBase服務高可用之路的探索
HBase服務高可用之路的探索
一、背景
這裡的高可用並不是指HBase本身的高可用機制。而是HBase主備雙服務的高可用,線上業務依賴於主備HBase叢集來提供資料支援,主叢集首要的任務時負責資料的讀寫,備叢集只是為了容災。
對於HBase主備服務高可用方案的調研,團隊內部從未停止過探索的步伐。從最初手動切換Nginx的域名對映,到統計異常日誌佔比,然後進行自動的域名切換。那時候我們面臨的狀況是,主叢集大量讀寫超時、甚至服務不可用,造成業務方介面無法為使用者提供正常的線上業務時,HBase運維小夥伴們才能感知到HBase叢集的異常狀態,手動切換流量至備叢集,從而在服務恢復的時間內,造成了無法容忍的損失。
針對舊方案的種種痛點,以及受微服務中熔斷概念的啟發,最終選擇集成了餓了麼提供的一個熔斷框架——doctor
,實現了HBase主叢集服務查詢異常時,查詢流量能夠及時、自動、無感知地進行切換到備叢集。
二、HBase熔斷API目前已實現的功能
- [x] 基於happybase封裝的HBase讀寫操作的基本功能API
- [x] 錯誤請求比例到達一定閾值,觸發熔斷機制,主備叢集自動無感知切換
- [x] 主備切換後,熔斷的恢復機制,將自動感知主叢集是否可以正常提供線上服務
- [x] 服務異常切換時,精確到單個介面的微信預警
三、關於熔斷
一般在微服架構中,有一個元件角色叫熔斷器。顧名思義,熔斷器起的作用就是在特定的場景下關掉當前的通路,從而起到保護整個系統的效果。
在微服務架構中,一般我們的獨立服務是比較多的,每個獨立服務之間劃分責任邊界,並通過約定協議介面來進行通訊。當我們的呼叫鏈路複雜依賴多時,很可能會發生雪崩效應。
假設有這麼一個場景,有A, B, C, D四個獨立服務,A會依賴B,C,D;當D發生負載過高或網路異常等導致響應過慢或超時時,很可能A會因此堆積過多的等待連結,從而導致A的狀態也轉為異常,後面依賴到A的其他服務跟著發生鏈式反應,這將會導致大面積的服務不可用,即使本來是一些沒有依賴到B,C,D的服務。如下圖所示:
這不是我們希望看到的結果,所以這個時候熔斷器可以派上用場。最簡單的做法,我們為每個依賴服務配置一個熔斷器開關,正常情況下是關閉的,也就是可以正常發起請求;當請求失敗(超時或者其他異常)次數超過預設值時,熔斷器自動開啟,這時所有經過這個熔斷器的請求都會直接返回失敗,並沒有真正到達所依賴的服務上。這時服務A本身仍然是能正常服務的。當然,我們針對失敗請求的策略,並沒有這麼簡單粗暴。
四、借鑑HBase熔斷切換在有贊團隊內的實踐
HBase 雖然提供了 HBase Replication 機制,用來實現叢集間單方向的非同步資料複製,線上雖然部署了雙叢集,備叢集 SSD 分組和主叢集 SSD 分組有相同的配置。當主叢集因為磁碟,網路,或者其他業務突發流量影響導致某些 RegionServer 甚至叢集不可用的時候,就需要提供備叢集繼續提供服務,備叢集的資料可能會因為 HBase Replication 機制的延遲,相比主叢集的資料是滯後的,按照我們叢集目前的規模統計,平均延遲在 100ms 以內。所以為了達到高可用,業務方只能接受複製延遲,放棄強一致性,選擇最終一致性和高可用性。
有贊技術團隊對於HBase高可用服務介面的設計,同樣使用了熔斷的概念,只是其底層的熔斷技術依賴於java微服務中的Hystrix框架。其簡單的客戶端高可用方案原理圖如下所示:
業務方是不想感知到後端服務的狀態,也就是說在客戶端層面,他們只希望一個 Put 或者 Get 請求正常送達且返回預期的資料即可,那麼就需要高可用客戶端封裝一層降級,熔斷處理的邏輯,這裡有贊採用 Hystrix 做為底層熔斷處理引擎,在引擎之上封裝了 HBase 的基本 API,使用者只需要配置主備機房的 ZK 地址即可,所有的降級熔斷邏輯最終封裝到 ha-hbase-client 中。
以上文字描述摘選自有讚的技術部落格,詳情可以參考連結,有贊 HBase 技術實踐:讀流程解析與優化
五、熔斷在我們的HBase介面服務中的應用
與微服務中的熔斷概念類比,我們也可以把我們的主備HBase叢集看做是兩個獨立的服務,而我們的業務方則需要依賴這一個HBase服務,對外提供自己的服務。這裡稍微有一點不一樣的地方是,我們HBase服務的角色是由兩個叢集來擔任,正常情況下,只有一個叢集來承擔起HBase服務的功能。HBase熔斷切換的簡單示例如下:
- 正常狀態下APP的請求通過熔斷器只會落在主叢集上
- 當發生例如超時異常時,在指定的視窗期內,錯誤的請求數達到一定的閾值,熔斷器就會認為,HBase主叢集處於非正常狀態,此時,在服務的最小恢復時間內,所有的請求通過熔斷器,會落在備叢集中。而熔斷器與主叢集的通訊鏈路則是被鎖定的。
- 過了指定的服務的最小恢復時間,還未到達服務的最大恢復時間時,APP的請求會隨機落在主備叢集,當主叢集的請求依舊異常時,熔斷器會繼續鎖住與主叢集的通訊鏈路。直至時間達到服務的最大恢復時間,熔斷器才會繼續嘗試把請求落在主叢集上。
六、HBase熔斷工作的流程圖
此處,我們以get請求舉例,用流程圖來演示我們的HBase查詢熔斷與主備切換機制。
七、滾動計數RollingNumber
如果想要更深入地理解主備熔斷切換的設計理念,那麼,需要優先理解一下滾動視窗計數,以及閾值判斷相關的一些內容。doctor
熔斷框架的設計中,依賴於滑動視窗時間內的滾動計數,來進行閾值計算,從而判斷當前服務的健康狀況。
1. 滾動計數的概念
滾動計數的行為類似於一個擁有固定長度的先進先出佇列,或者時間戳序列上的滑動視窗。一個滾動計數的值是佇列元素的和,時鐘結束時,最後一個元素的值將滾動到先前的位置,傳遞了一個時間粒度,這個時間粒度,預設1s。下面將藉助一個小例子,具體來說明這種機制。
示例中我們使用的滑動視窗長度為4,移位的時間戳粒度為1s。總的時間週期是20s。
- 初始時建立一個填充4個0元素的列表,這是整個滾動計數行為的最開始的狀態。
[0, 0, 0, 0]
- 在第一個滑動視窗的時間週期(4s)內,假如第一秒內處理了3個請求,第二秒內處理了2個請求,第三秒內處理了5個請求,第四秒內處理了4個請求,那麼,此時列表中的狀態如下,需要計算的指標從上至下依次為,
請求的總數
,失敗請求的總數
,失敗請求所佔的比例
[3, 2, 5, 4]
+--- 14 ---+
+--- 7 ---+
+--- 0.5 ---+
# 則這一個滑動視窗的時間週期內的請求數總和為14,假如失敗的請求總數為7,那麼,此時間週期內的失敗比例為7 / 14 = 0.5
- 假如在第五秒處理的請求為4,滑動視窗需要前移一個時間粒度,此時列表中的狀態如下:
3, [2, 5, 4, 4]
+--- 15 ---+
+--- 3 ---+
+--- 0.2 ---+
# 則這一個滑動視窗的時間週期內的請求數總和為15,假如失敗的請求總數為3,那麼,此時間週期內的失敗比例為3 / 15 = 0.2
- 依次類推,第m個時間週期內
3, 2, 5, 4, 4,..., 6, [8, 2, 4, 4], 5 ... (<= time passing 20s)
+--- 18 ---+
+--- 6 ---+
+--- 0.33 ---+
# 則第m個滑動視窗的時間週期內的請求數總和為18,假如失敗的請求總數為6,那麼,此時間週期內的失敗比例為6 / 18 = 0.33
八、深入理解熔斷在我們HBase介面服務中的工作機制
1. HBase熔斷機制工作的核心引數
讀寫閾值判定的配置示例
READ_DOCTOR_CONF = dict(
# Metrics settings.
METRICS_GRANULARITY=1, # sec
METRICS_ROLLINGSIZE=10,
# Health settings.
HEALTH_MIN_RECOVERY_TIME=10, # sec
HEALTH_MAX_RECOVERY_TIME=2 * 10, # sec
HEALTH_THRESHOLD_REQUEST=5 * 1, # per `INTERVAL`
HEALTH_THRESHOLD_TIMEOUT=0.01, # percentage per `INTERVAL`
HEALTH_THRESHOLD_SYS_EXC=0.01, # percentage per `INTERVAL`
HEALTH_THRESHOLD_UNKWN_EXC=0.01, # percentage per `INTERVAL`
)
WRITE_DOCTOR_CONF = dict(
# Metrics settings.
METRICS_GRANULARITY=3, # sec
METRICS_ROLLINGSIZE=20,
# Health settings.
HEALTH_MIN_RECOVERY_TIME=20, # sec
HEALTH_MAX_RECOVERY_TIME=2 * 60, # sec
HEALTH_THRESHOLD_REQUEST=10 * 1, # per `INTERVAL`
HEALTH_THRESHOLD_TIMEOUT=0.5, # percentage per `INTERVAL`
HEALTH_THRESHOLD_SYS_EXC=0.5, # percentage per `INTERVAL`
HEALTH_THRESHOLD_UNKWN_EXC=0.5, # percentage per `INTERVAL`
)
核心引數解讀
- METRICS_GRANULARITY:滾動計數中視窗移位的時間粒度
- METRICS_ROLLINGSIZE:滾動計數中滑動視窗的長度
- HEALTH_MIN_RECOVERY_TIME:服務發生異常時的最小恢復時間
- HEALTH_MAX_RECOVERY_TIME:服務發生異常時的最大恢復時間
- HEALTH_THRESHOLD_REQUEST:此引數用於控制是否需要進行錯誤閾值計算
- HEALTH_THRESHOLD_TIMEOUT:發生超時異常的請求數與請求總數的比值,超過此設定,將觸發主備服務的熔斷切換
- HEALTH_THRESHOLD_SYS_EXC:發生系統異常的請求數與請求總數的比值,超過此設定,將觸發主備服務的熔斷切換
- HEALTH_THRESHOLD_UNKWN_EXC:發生未知異常的請求數與請求總數的比值,超過此設定,將觸發主備服務的熔斷切換
2. 判斷介面是否健康的策略
判斷當前介面是否健康的詳細策略
- 如果當前api(例如:getRow的一個操作)在讀/寫主叢集時嚴重出錯,則會直接去從備叢集中獲取結果,在配置中設定的服務最小恢復時間內,進一步的請求不會再操作主叢集。
- 如果主叢集恢復健康,但是熔斷器此時並不知道主叢集已經恢復正常了,它的恢復機制是:在配置中設定的服務恢復的最小和最大時間之間,請求通過熔斷器,隨機去操作主備叢集,如果期間訪問主叢集的請求,有一次發生異常,熔斷器就會鎖住與主叢集的通訊鏈路,餘下所有請求將會訪問備叢集,繼續去等待最小間隔,然後開啟隨機訪問模式,直至達到配置設定的服務最大的恢復時間,熔斷器認為主叢集已恢復上線,之後的請求又會繼續操作主叢集。
錯誤閾值說明
- 介面錯誤包括系統錯誤與操作的超時
- 閾值是百分比,錯誤請求數/總的請求數
- 當一個滑動視窗時間內的請求計數大於配置設定的THRESHOLD_REQUEST時,才會觸發進一步的錯誤閾值檢查,然後,如果錯誤閾值大於設定的比例時,才會觸發最終的熔斷切換。
3. 健康檢查
- 每一次請求經過熔斷器,都會觸發健康檢查。
九、總結
上述便是對HBase熔斷思想所做的一個由淺入深的解釋,用於實現業務方訪問HBase時,對於主備HBase叢集的狀態切換無感知。即使主叢集處於異常狀態,我們依舊可以為業務方提供正常的HBase服務。