資料庫故障容錯之系統時鐘故障
本文通過對CnosDB資料庫系統時鐘故障的討論,提出了在單機環境和叢集環境中解決系統時鐘故障的方案,具有非常重要的指導意義。
可能並不存在的情況引發的討論
有一天,CnosDB實習生小邵在做時間亂序資料處理邏輯的設計,時序資料的產生一般有以下幾個規律可以遵循:
1. 一般情況下,時序資料由一條條時間線組成,每一條時間線背後都是一個獨立的資料來源。
2. 時序資料的產生往往是實時的,時間線上的資料也是按照時間順序排列的,大多數時序資料庫的底層資料儲存設計也基於此。
3. 在一些特殊的情況下,一條時間線上的時間順序可能會被破壞,一段時間之前的資料可能現在才到達資料庫;由於處理起來太過麻煩,一些資料庫會拒絕這種資料寫入。
問題出在“一些特殊的情況上”,小邵異想天開地提出了一個我沒有想到過的可能情況:
1. 如果客戶端發來的資料是沒有時間戳的,那麼資料的時間戳由資料庫服務端的系統時間戳決定。
2. 如果資料庫服務端的系統時鐘發生了修改,比如本來是5點,調成了3點,就會出現時間亂序資料。
不得不說,這是比“一些特殊的情況”還要特殊的情況,而比較典型的時間亂序資料場景是這樣的;一個遠端裝置不斷向資料庫端傳送自帶時間戳的資料,可能有兩種情況出現:
1. 網路方面的故障,導致從裝置發來的資料,並沒有按照資料發出的時間到達。
2. 裝置發出的資料在到達資料庫前丟失或損壞了,裝置在之後的時間裡將這些資料重新發給資料庫。
經過了一段時間的思考,我發現小邵說的那種情況,不應該放在時間亂序資料的問題裡討論,而是一個需要單獨處理的問題。
系統時鐘故障
相比於掉電宕機、網路故障等常見又嚴重的故障,系統時鐘發生變化的情況其實很少被考慮到,一般主要影響的也都是日誌時間戳之類,不會直接影響到使用者的部分。
近年來,由於NewSQL的概念普及,許多分散式資料庫選擇依賴系統時鐘與NTP實現混合邏輯時鐘,這時系統時鐘故障可能才真正被廣泛討論。不過,CnosDB是個典型的原生時序資料庫,不是那種拼接改造的資料庫專案,並不支援事務,所以在這裡並不是討論混合邏輯時鐘的問題。
定義
在具體討論之前,先給系統時鐘故障一個明確的定義:指由於惡意攻擊、使用者誤操作等原因,導致資料庫部署環境的系統時鐘突然發生跨度較大的變化,比如前一秒系統時鐘為17:00,下一秒變成了3:19。
由於從資料庫的角度無法確認這種變化是否正確、是否符合使用者的預期,所以將這種變化視為故障。
叢集中的情況
單機下的情況比較簡單,就是上文中的定義情況,而叢集中的情況可以大致分為三種:
1. 只有少數節點的系統時鐘發生變化。
2. 超過半數節點的系統時鐘發生變化。
3. 所有節點同步切換到新的、相同的系統時鐘。
對時序資料庫的影響
就像上文中小邵所說的“特殊情況”,系統時鐘故障會影響沒有自帶時間戳的資料,其背後是時序資料庫對這類資料處理的策略:
1. 要求使用者寫入的所有資料都必須自帶時間戳。
2. 對於沒有時間戳的資料,自動新增客戶端的系統時間戳。
3. 對於沒有時間戳的資料,自動新增資料庫服務端的系統時間戳。
據我觀察,目前策略三應該是比較主流的方案,所以一個時序資料庫最好還是要考慮如何處理系統時鐘故障。否則,一旦出現問題,就會有大量“時間戳異常”的資料寫入到資料庫中,影響到資料庫的效能還是小事,出現了使用者角度的“髒”資料就是大麻煩了。
故障的處理
根據既往的經驗和知識,一個足夠健壯的系統,在處理故障時需要依循以下四個原則:
1. Fail-Fast:能夠第一時間發現故障的產生。
2. Fail-Safe:盡將故障的影響範圍控制在最小。
3. Fail-Over:在發生故障時能夠進行主備切換,由沒有發生故障的備份/備用節點繼續提供服務。
4. Fail-Back:在主副本/主節點從故障狀態恢復到正常狀態之後,可以切回到主副本/主節點提供服務。
基於這樣的原則,針對單機和叢集兩種情況,做出了以下設計。
單機:發現故障然後服務降級
在單機的環境中,系統發現故障之後,需要做以下的操作:
1. 週期性獲取當前系統時間戳,並與上一次獲取到的值對比,如果兩者的偏差超過了容忍閾值,判定為出現了時鐘故障。
2. 使用者可以通過檢視系統表中的某項,檢視當前CnosDB節點是否處於時鐘故障狀態,此狀態不會因CnosDB程序的重啟而變化。
3. 出現故障後,只允許使用者自行指定時間戳的資料寫入,拒絕未指定時間戳的資料寫入。
4. 使用者可以通過使用管理員許可權執行一條命令,解除當前CnosDB節點的時鐘故障狀態。
我們在處理單機故障時,可以用以下的使用者介面設計去定義:
統一的命名規則
clock_fault_XX
系統變數/引數
1. clock_fault_check_interval
a. 獲取系統時間戳並進行檢查的時間間隔。
b. 預設值5min,可線上變更。
2. clock_fault_last_timestamp
a. 最後一次獲取到的系統時間戳。
b. 只讀。
3. clock_fault_check_threshold
a. 時鐘故障的觸發閾值。
b. 當前系統實際時間戳與clock_fault_last_timestamp + clock_fault_check_interval的差值超過此閾值時,當前CnosDB節點切換為時鐘故障狀態。
c. 預設值為1h,可線上變更。
4. clock_fault_state
a. 當前CnosDB節點是否處於時鐘故障狀態
b. 處於時鐘故障狀態,此值為error;反之,為normal。
c. CnosDB將此值設定為error時,需要同時在日誌中進行記錄。
d. 可線上變更,但只能由CnosDB管理員手動設定為normal,代表CnosDB管理員對目前的情況已經完全掌握並進行了相應的處理。
系統時間線
clock_fault_timestamp:記錄每次進行檢查時獲取的系統時間戳,使用者可以通過檢視此時間線來判斷時鐘故障發生的具體時間點。
叢集:複雜的故障發現與主備切換機制
在叢集環境中,因為多節點的原因,處理的過程相對於單機的機制會更加的複雜,那麼也有以下的相應操作:
1. 以每個CnosDB節點的單節點故障處理為基礎。
2. 當一個節點處於時鐘故障狀態時,未指定時間戳的資料寫入將自動路由到其他節點進行執行。
3. 定期檢查叢集所有節點的系統時鐘:
a. N為當前叢集內的主機(物理機、虛擬機器或容器)總數量, μ為叢集內全部主機某個子集,μ包含的主機數量超過N/2。
b. 如果存在一個μ在兩個節點間的系統時間戳差值不超過一個預先設定的閾值,這個μ就可以稱為時鐘基準主機組。
c. 時鐘基準主機組應儘可能包含更多主機。
d. 如果部署在時鐘基準主機組中的所有節點都沒有處於時鐘故障狀態,這些節點的集合就可以成為時鐘基準節點組。
e. 當叢集中無法找出一個時鐘基準節點組時,整個叢集將被判定為處於時鐘異常狀態。
f. 只有當叢集中可以找到時鐘基準節點組時,CnosDB叢集管理員才可以將叢集解除時鐘異常狀態。
g. 當某一節點的系統時鐘與時鐘基準主機組中一個節點的系統時鐘的差值超過了一個預先設定的閾值,叢集會將該節點的clock_fault_state設定為error(通過叢集內部的一個賬號,一般使用者無法進行這樣的操作)。
我們在處理叢集故障時,可以用以下的使用者介面設計去定義:
統一的命名規則
cluster_clock_fault_XX
系統變數/引數
1. cluster_clock_fault_check_interval
a. 叢集範圍內進行clock_fault_check的時間間隔。
b. 預設值5min,可線上變更。
2. cluster_clock_fault_check_threshold
a. 用於判定時鐘基準節點是否存在的閾值。
b. 預設值為10s,可線上變更。
3. cluster_clock_fault_state
a. 當前CnosDB叢集處於時鐘故障狀態時,此值為error;反之,為normal。
b. CnosDB將此值設定為error時,需要同時在日誌中進行記錄。
c. 可線上變更,但只能由CnosDB管理員手動設定為normal,代表CnosDB管理員對目前的情況已經完全掌握並進行了相應的處理。
系統時間線
1. cluster_clock_fault_base_clock_node_list:記錄當前基準時鐘節點組包含的節點列表。
2. cluster_clock_fault_base_clock_host_list:記錄當前基準時鐘主機組包含的節點列表。
結語
儘管系統時鐘故障並不是一個時序資料庫首要去考慮如何處理的故障,對於叢集層面的時鐘故障檢測與處理,可能也不需要直接放在時序資料庫這一層來實現,但這個思考和做大體設計的過程還是很有趣的,於是決定分享給大家。
如果大家有真實遇到這種問題的案例,可以告訴我們,這樣本文中的設計會考慮得更加嚴謹,也會更快地在CnosDB中落地。
參與 CnosDB 社群交流群:
掃描下方二維碼,加入 CC 進入 CnosDB 社群進入社群交流,CC 也會在群內分享直播連結噠