VDL:唯品會強一致、高可用、高效能分散式日誌儲存 (質量篇)

唯技術訂閱號發表於2018-11-30

這是VDL系列的第三篇,本文主要講述VDL在質量控制上的一些工作。 

FIU測試

分散式系統,在正常情況下還是比較簡單的。異常情況才是分散式系統的難點,包括節點故障、磁碟異常、網路延時/丟包、網路分割槽等等。如何在這些異常或者異常組合的情況下保證VDL穩定正常,這是VDL的重難點之一。

FIU是Fault Injection Utility的縮寫,是我們為了更好地測試分散式系統(RDP/VDL)自研的一個小工具。FIU的主要目的是:自動化構造各種異常/錯誤,並且具備定製擴充套件的能力。

VDL使用FIU來模擬上述各種異常,並測試在異常情況下,VDL是否正常(表現是否符合預期)。我們先來簡單介紹一下FIU框架。

FIU框架

VDL:唯品會強一致、高可用、高效能分散式日誌儲存 (質量篇)

如上圖所示,FIU由三個元件組成:

-fiu_driver:driver是總控節點,是所有test case的發起端,為每一個test case準備所需的異常/錯誤等前提條件,然後執行設計的test case; fiu_driver和fiu_agent之間透過UDP協議通訊;

 

-fiu_agent:和測試的目標程式執行在相同機器上,負責接受fiu_driver的指令並在目標測試的機器上進行各種操作,完成這些操作一般透過編寫shell指令碼完成;FIU本身提供一些shell原型,使用者開發的shell原型會自動同步到fiu_driver端,所以測試行為可以靈活擴充套件;fiu_agent和fiu_engine之間透過named shared memory同步fiu_driver開啟的sync point;

-fiu_engine:以原始碼和so兩種方式嵌入到被測試的專案中,主要是插入各種sync point 和各種定點異常。

目前VDL的異常用例有30多例還在不斷地新增中,主要分類有: 

1.模擬磁碟IO異常:包括磁碟寫入異常、寫入延時;

2.模擬網路異常:包括丟包,Delay,使用TC進行模擬。我們並不處理拜占庭的情況; 

3.程式異常:包括隨機Kill/Kill -9 VDL節點; 

4.Raft流程異常:包括Delete Raft Log,落後節點加入叢集,不斷停止Leader節點等情況;

5.Log Store異常:包括資料損壞、索引檔案異常等情況;

6.客戶端異常測試:包括客戶端邊界請求、異常請求測試。

FIU的例子:

我們舉一個例子方便與更加直觀理解。假設我們有一個三節點組成的VDL叢集,配合FIU,部署圖如下:

VDL:唯品會強一致、高可用、高效能分散式日誌儲存 (質量篇)

(FIU Driver也可以跟agent機器部署在一起)

上圖中fiu_driver用於給agent傳送執行請求,異常測試用例指令碼會部署在driver所在伺服器,在執行用例過程中,指令碼會呼叫fiu_driver給agent傳送命令。fiu_driver不是常駐程式。

fiu_agent接收fiu_driver的請求,執行操作。主要有執行shell指令碼與往Share Mem Map 注入異常資訊。fiu_agent是常駐程式。

假設我們要完成如下的異常測試:

1.#####################################

2.#

3.# 流程:

4.#

5.# 測試與Raft流程相關的儲存介面異常時,VDL叢集情況

6.# 1) 啟動3節點叢集,啟動傳送服務

7.# 2) 選取一個節點,往儲存介面注入錯誤

8.# 3) 判斷節點情況

9.# 4) 恢復節點,清除錯誤

10.# 5) 迴圈2-4,向下一個介面注入錯誤

11.# 6) 共測試20次

12.# 7) 完成20次異常後,清除錯誤,關閉傳送服務,並檢查

13.#

14.# 預期:

15.# 1) 注入錯誤後,節點會crash

16.# 2) 叢集恢復正常後, 叢集有Leader, 且三節點raft log資料一致

17.#

18.# ###################################

那麼需要實現一個測試用例,並放在driver所在的機器上:

1.function TestCase_logstore_error() {

2.// 過程先略過

3.}

我們按用例的流程介紹:

-第一步: 啟動3節點叢集,啟動傳送服務

1.function TestCase_logstore_error() { 

2.       

3....

4.# 啟動vdl0

5.local start_vdl0=`${fiu_driver} ${vdl0_ip} ${fiu_port} 'remote.sync.pipe' 'sh '${vdl_root}'/tools/fiu/fiu_agent/script/start_vdl.sh' '10000'`

6.# 檢查vdl0是否正確

7.Expect_EQ $start_vdl0 "1"

8.

9.# 啟動vdl1

10.local start_vdl1=`${fiu_driver} ${vdl1_ip} ${fiu_port} 'remote.sync.pipe' 'sh '${vdl_root}'/tools/fiu/fiu_agent/script/start_vdl.sh' '10000'`

11.# 檢查vdl1是否正確

12.Expect_EQ $start_vdl1 "1"

13.

14.# 啟動vdl2

15....

16.       

17.# 啟動傳送服務

18.# 啟動傳送服務會執行本地的一個shell指令碼,包括動態編譯producer並啟動,不再詳細展開。

19.echo '-------------produce message async(start)----------------'

20.${vdl_root}/tools/fiu/fiu_agent/script/pmsg.sh 6000 5 t

21.

22.}

指令碼中,start_vdl1命令中的fiu_driver是driver的路徑,vdl0_ip是要啟動的VDL的IP地址,fiu_port是agent的port,remote.sync.pipe是指要同步執行, xxxx/start_vdl.sh是指agent要執行的指令碼,10000是指超時時間。

啟動VDL,測試指令碼會呼叫driver,往agent傳送啟動命令。agent接受到請求後,執行本地指令碼並返回結果,流程圖如下:

VDL:唯品會強一致、高可用、高效能分散式日誌儲存 (質量篇)

-第二步: 選取一個節點,往儲存介面注入錯誤

1.function TestCase_logstore_error() {

2.       

3.......

4.       

5. #這裡定義要注入的錯誤的陣列

6.local store_interface_error_array=('fiu_logstore_entries_error' 'fiu_logstore_term_error' ...省略)

7.local error_array_count=${#store_interface_error_array[@]}

8.

9.#------節點注入錯誤--------------------------------

10.for((i=0;i<20;i++))

11.{

12.# 偽隨機選取要注入的異常

13.local inject_node=$((i%3))

14.local inject_error_index=$(( i % error_array_count ))

15.                    

16.echo '--------注入錯誤-----------'

17.PrepareOneCondition ${vdl_ip_array[inject_node]} ${fiu_port}'sync.point'${store_interface_error_array[inject_error_index]}

18.

19.#下面是檢查程式碼

20....

21.}

22.}

關鍵程式碼是:

1.PrepareOneCondition ${vdl_ip_array[inject_node]} ${fiu_port}'sync.point'${store_interface_error_array[inject_error_index]}

PrepareOneCondition是共用函式,實際是呼叫driver,往agent執行'sync.point'命令。流程如下:

VDL:唯品會強一致、高可用、高效能分散式日誌儲存 (質量篇)

往Share Mem Map插入Key後,VDL使用fiu_engine的介面,就能獲取到對應的錯誤訊號,程式碼如下:

1. // 下面是Go程式碼

2.func (s *LogStore) Term(i uint64) (uint64, error) {

3.       

4.// mock error

5.if fiu.IsSyncPointExist(vdlfiu.FiuStoreTermError) {

6.return 0, errors.New("Mock FiuStoreTermError")

7.}

8.}

-後面幾步: 與前面的第一、二步類似,例如"判斷節點情況",則Driver傳送'remote.sync.pipe'請求到agent,agent執行"檢查VDL指令碼",返回結果。

 

到目前為止,我們介紹了VDL如何使用異常測試對各種情況進行模擬。下面我們來講講如何校驗節點間資料一致性。

節點資料一致性校驗

判斷VDL是否能正常服務,可以簡單透過監控頁面看到,但如何才能簡單地確認叢集的資料是一致的呢?VDL在壓測環境下,三個副本,目前共生產2400多億條資料,並且還在不斷增長中,灰度環境兩天就能產生15億條資料,如何才能簡單校驗副本間資料都是一致的呢?

由於資料量太多,不可能對每條資料進行對比,且每個節點的log檔案不相同,也不可以簡單使用md5直接對比檔案。對此我們自研了一個資料校驗工具,校驗節點間的資料一致性。使用的方案是連續checksum的方式對資料進行對比。

VDL:唯品會強一致、高可用、高效能分散式日誌儲存 (質量篇)

如圖所示,三個節點均從某個index開始,計算連續的checksum,只要相同的index有相同的checksum,則認為三節點資料均一致。

在實際使用中,我們將每個節點的checksum資料按一定規則輸出到日誌中,我們只要校驗最新的相同的index,若有相同的checksum,則在["計算的index", "校驗的index"] 這個區間的資料均一致。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69900365/viewspace-2222265/,如需轉載,請註明出處,否則將追究法律責任。

相關文章