當 TiDB 遇上 Jepsen
本篇文章主要介紹 TiDB 是如何使用分散式一致性驗證框架 Jepsen 進行一致性驗證的。
什麼是 Jepsen
Jepsen 是由 Kyle Kingsbury 採用函數式編程語言 Clojure 編寫的驗證分散式系統一致性的測試框架,作者使用它對許多著名的分散式系統(etcd, cockroachdb…)進行了「攻擊」(一致性驗證),並且幫助其中的部分系統找到了 bug。這裡一系列的博客展示了作者的驗證過程以及對於一致性驗證的許多思考。
Jepsen 如何工作
Jepsen 驗證系統由 6 個節點組成,一個控制節點(control node),五個被控制節點(默認為 n1, n2, n3, n4, n5),控制節點將所有指令發送到某些或全部被控制節點,這些指令包括底層的 shell 命令到上層的 SQL 語句等等。Jepsen 提供了幾個核心 API 用於驗證分散式系統:
- DB
DB 封裝了所驗證的分散式系統下載、部署、啟動和關閉命令,核心函數由 setup 和 teardown 組成,在 TiDB 的 Jepsen 測試中,setup 負責下載 TiDB 並且依次啟動 Placement Driver、TiKV 和 TiDB;teardown 負責關閉整個 TiDB 系統並且刪除日誌。
- Client
Client 封裝了每一個測試所需要提供的客戶,每個 client 提供兩個介面:setup 和 invoke,setup 負責對 TiDB 進行連接,而 invoke 則包含了測試中 client 對 TiDB 調用的 sql 語句,具體語句依測試而定。
- Checker
Checker 用於對測試生成的歷史進行驗證,判斷測試結果是否符合預期,歷史的格式如下圖所示:
- Nemesis
Nemesis 用於對系統引入故障,比如常見的網路分區、網路延時、節點宕機,在 TiDB 的測試中,有以下幾種 nemesis:
下圖展示了 parts nemesis 引入測試中後某些語句執行時出現了 time-out 的錯誤。
- Generator
Generator 是 Jepsen 中的事件發生器,它將 Client 和 Nemesis 的操作交織在一起,為整個測試生成具體的執行語句。
TiDB 中的 Jepsen 測試
TiDB 中的 Jepsen 測試有 3 個,分別是 bank、set 和 register 測試。
Bank Test
銀行測試用於驗證快照隔離。這個測試模擬了一個銀行系統中的各種轉賬,每個銀行系統的初始可以是這樣的:
1-5 分別代表賬戶名稱,而 10 代表賬戶餘額。測試會隨機生成轉賬信息:
代表將金額 5 從賬戶 1 轉入賬戶 2 這個操作。與此同時,測試會隨機讀取所有賬戶的存款信息,例如某一時刻賬戶的存款信息可能是這樣的:
下面是測試進行中的某次截圖:
在快照隔離下,所有的轉賬都必須保證每一時刻所有賬戶的總金額是相同的。TiDB 在即使引入了各種 nemesis 的情況下仍舊順利地通過了測試。
Set Test
這個測試從不同節點並發的將不同的數插入一張表中,並且進行一次最終的表讀取操作,用於驗證所有返回成功的插入值一定會出現在表中,然後所有返回失敗的插入值一定不在表中,同時,因為 nemesis 的引入,對於那些返回 time-out 的插入值,它們可能出現也可能不會出現在表中,這屬於正常情況。
下面是測試進行中的某次截圖:
同樣,TiDB 通過了測試。
Register Test
這個測試很好理解,建一個表,然後插入一條值,然後我們把這個值看做是一個寄存器,然後在測試中並發地從各個節點對其進行 read、write 和 cas 操作。
然後利用 Jepsen 產生的一系列操作歷史(如上圖)進行 Linearizability 一致性驗證。這個演算法是 Jepsen 的核心,也是 Jepsen 被業界所熟知的原因之一,所以花時間去深入學習了下,我會在另一篇文章具體介紹這個演算法。
寫在最後
每次 TiDB 更新代碼,我們都會內部觸發 CI 來執行 Jepsen,通過 Jepsen 來保證 TiDB 的數據一致性。如果你對分散式測試,一致性驗證感興趣,歡迎參與開發。
TiDB Jepsen:https://github.com/pingcap/jepsen/tree/master/tidb
徐鵬
推薦閱讀:
※如何從零開始參與大型開源項目
※十七年的潤乾,壯士斷腕的變革,報表到計算的轉身
※為什麼我們需要區塊鏈
※Revit數據導出到資料庫