1. 程式人生 > >DM 原始碼閱讀系列文章(十)測試框架的實現

DM 原始碼閱讀系列文章(十)測試框架的實現

作者:楊非

本文為 DM 原始碼閱讀系列文章的第十篇,之前的文章已經詳細介紹過 DM 資料同步各元件的實現原理和程式碼解析,相信大家對 DM 的實現細節已經有了深入的瞭解。本篇文章將從質量保證的角度來介紹 DM 測試框架的設計和實現,探討如何通過多維度的測試方法保證 DM 的正確性和穩定性。

測試體系

DM 完整的測試體系包括以下四個部分:

1. 單元測試

主要用於測試每個 go 模組和具體函式實現的正確性,測試用例編寫和測試執行方式依照 go 單元測試的標準,測試程式碼跟隨專案原始碼一起釋出。具體測試用例編寫使用 pingcap/check 工具包,該工具包是在 go 原生測試工具基礎上進行的擴充套件,

按照 suite 分組進行測試,提供包括更豐富的檢測語法糖、並行測試、序列化測試在內的一些擴充套件特性。單元測試的設計出發點是白盒測試,測試用例中通過儘可能明確的測試輸入得到期望的測試輸出。

2. 整合測試

用於測試各個元件之間互動的正確性和完整資料同步流程的正確性,完整的 測試用例集合和測試工具在專案程式碼的 tests 目錄 釋出。整合測試首先自定義了一些 DM 基礎測試工具集,包括啟動 DM 元件,生成、匯入測試資料,檢測同步狀態、上下游資料一致性等 bash 指令碼,每個測試用例是一個完整的資料同步場景,通過指令碼實現資料準備、啟動 DM 叢集、模擬上游資料輸入、特定異常和恢復、資料同步校驗等測試流程。整合測試的設計出發點是確定性的模擬測試場景,為了能夠確定性的模擬一些特定的同步場景,為此我們還引入了

failpoint 來注入測試、控制測試流程, 以及 trace 機制來更準確地獲取程式記憶體狀態、輔助控制測試流程,具體的實現細節會在後文詳細介紹。

3. 破壞性測試

真實的軟體執行環境中會遇到各種各樣的問題,包括各類硬體故障、網路延遲和隔離、資源不足等等。DM 在資料同步過程中也同樣會遇到這些問題,藉助於 PingCAP 內部的自動化混沌測試平臺 schrodinger,我們設計了多個破壞性測試用例,包括在同步過程中隨機 kill DM-worker 節點,同步過程中重啟部分 DM-worker 節點,分發不相容 DDL 語句等測試場景。這一類測試的關注點是在各類破壞性操作之後資料同步能否正常恢復以及驗證在這些場景下資料一致性的保證,測試用例通常以黑盒的形式去執行,並且長期、反覆地進行測試。

4. 穩定性測試

目前該類測試執行在 PingCAP 內部的 K8s 叢集上,通常每個測試的應用規模會比較大,譬如有一些 100+ 上游例項,300+ 分庫分表合併的測試場景,資料負載也會相對較高,目標在於測試大規模 DM 叢集在高負載下長期執行的穩定性。該類測試也屬於黑盒測試,每個測試用例內會根據任務配置啟動上游的 MySQL 叢集、DM 叢集、下游 TiDB 叢集和資料匯入叢集。上游資料輸入工具有多種,包括 隨機 DML 生成工具,schrodinger 測試用例集等。具體的測試 case 和 K8s 部署指令碼可以在 dm-K8s 倉庫 找到。

5. 測試方法對比

我們通過以下的表格對比不同測試維度在測試體系中發揮的作用和它們之間的互補性。

測試名稱測試方法測試重點測試周期測試互補性
單元測試白盒測試,確定性的輸入、輸出模組和具體函式的正確性CI 自動化觸發,新程式碼提交前必須通過保證單個函式的正確性
整合測試確定性的同步場景和資料負載模組之間整體互動的正確性,可以有針對性的測試特定資料同步場景。CI 自動化觸發,新程式碼提交前必須通過測試在單元測試的基礎上,保證多個模組在一起組合起來工作的正確性
破壞性測試黑盒測試,隨機資料,隨機觸發的固定型別外部擾動系統在異常場景下的穩定性和正確性在內部測試平臺長期、反覆執行對已有確定輸入測試的補充,增加測試輸入的不確定性,通過未知、隨機的外部擾動發現系統潛在的問題
長期穩定性測試黑盒測試,確定性的同步場景,隨機資料負載系統長期執行的穩定性和正確性在內部 K8s 叢集長期執行補充整合測試的場景,測試系統在更高負載、更長執行時間內的表現

測試 case 與測試工具的實現

1. 在單元測試中進行 mock

我們在單元測試執行過程中希望儘量減少外部環境或內部元件的依賴,譬如測試 relay 模組時我們並不希望從上游的 MySQL 拉取 binlog,或者測試到下游的一些資料庫讀寫操作並不希望真正部署一個下游 TiDB,這時候我們就需要對測試 case 進行適當的 mock。在單元測試中針對不同的場景採用了多種 mock 方案。接下來我們選取幾種具有代表性的方案進行介紹。

Mock golang interface

在 golang 中只要呼叫者本身實現了介面的全部方法,就預設實現了該介面,這一特性使得使用介面方法呼叫的程式碼具有良好的擴充套件性,對於測試也提供了天然的 mock 方法。以 worker 內部各 subtask 的 任務暫停、恢復的測試用例 為例,測試過程中會涉及到 dump unit 和 load unit 的執行、出錯、暫停和恢復等操作。我們定義 MockUnit 並且實現了 unit interface全部方法,就可以在單元測試裡模擬任務中 unit 的各類操作。還可以定義 各類注入函式,實現控制某些邏輯流程中的出錯測試和執行路徑控制。

自定義 binlog 生成工具

在前文已經介紹過 relay 處理單元從上游讀取 binlog 並寫入本地檔案 的實現細節,這一過程重度依賴於 MySQL binlog 的處理和解析。為了在單元測試中完善模擬 binlog 資料流,DM 中實現了一個 binlog 生成工具,該工具包提供了通用的 generator 用於連續生成 Event 以及相對底層的生成特定 Event 的介面,支援 MySQL 和 MariaDB 兩種資料庫的 binlog 協議。generator 提供的生成介面會返回一個 go-mysql 的 BinlogEvent 列表和 binlog 對應的 byte 陣列,同時在 generator 中自動更新 binlog 位置資訊和 GTID 資訊。類似的,更底層的生成 Event 介面會要求提供資料型別、serverIDlatestPoslatestGTID 以及可能需要的庫名、表名、SQL 語句等資訊,生成的結果是一個 DDLDMLResult 物件。

我們通過測試中的一個 case 來了解如何使用這個工具,以 relay 模組讀取到多個 binlog event 寫入檔案的正確性測試 這個 case 為例:

  1. 首先配置資料庫型別,serverIDGTIDXID 相關資訊,初始化 relay log 寫入目錄和檔名

  2. 初始化 allEvents 陣列,用於模擬從上游接收到的 replication.BinlogEvent初始化 allDataallData 儲存 binlog binary 資料,用於後續 relay log 寫入的驗證;初始化 generator

  3. 通過 generator GenFileHeader 介面生成 replication.BinlogEvent 和 binlog 資料(對應的 binlog 中包含 FormatDescriptionEventPreviousGTIDsEvent)。生成的 replication.BinlogEvent 儲存到 allEventsbinlog 資料儲存到 allData

  4. 按照 3 的操作流程分別生成 CREATE DATABASECREATE TABLE 和一條 INSERT 語句對應的 event/binlog 資料並儲存

  5. 建立 relay.FileWriter,按照順序讀取 3, 4 步驟中儲存的 replication.BinlogEvent,向配置的 relay log 檔案中寫入 relay log

  6. 檢查 relay log 檔案寫入的資料長度與 allData 儲存的資料長度相同

  7. 讀取 relay log 檔案,檢查資料內容和 allData 儲存的資料內容相同

至此我們就結合 binlog 生成工具完成了一個 relay 模組的測試 case。目前 DM 已經在很多 case 中使用 binlog 生成工具模擬生成 binlog,仍然存在的 少量 case 依賴上游資料庫生成 binlog,我們已經計劃藉助 binlog 生成工具移除這些外部依賴。

其他 mock 工具

  • 在驗證資料庫讀寫操作邏輯正確性的測試中,使用了 go-sqlmock 來 mock sql driver 的行為。

  • 在驗證 gRPC 互動邏輯的正確性測試中,使用了 官方提供的 mock 工具,針對 gRPC 介面生成 mock 檔案,在此基礎上測試 gRPC 介面和應用邏輯的正確性。

2. 整合測試的方法和相關工具

Trace 資訊收集

DM 內部定義了一個簡單的資訊 trace 收集工具,其設計目標是在 DM 執行過程中,通過增加程式碼內部的埋點,定期收集系統執行時的各類資訊。trace 工具包含一個提供 gRPC 上報資訊介面和 HTTP 控制介面的 tracer 伺服器 和提供埋點以及後臺收集資訊上傳功能的 tracing 包。tracing 模組上傳到 tracer 伺服器的事件資料通過 protobuf 進行定義,BaseEvent 定義了最基本的 trace 事件,包含了執行程式碼檔名、程式碼行、事件時間戳、事件 ID、事件組 ID 和事件型別,使用者自定義的事件需要包含 BaseEvent。tracing 模組會 定期向 tracer 伺服器同步全域性時間戳,通過這種方式保證多節點不同的 trace 事件會保持大致的時間順序(注意這裡並不是嚴格的時間序,會依賴於每分鐘內本地時鐘的準確性,仍然有各種出現亂序的可能)。設計 tracing 模組的主要目的有以下兩點:

  • 對於同一個 DM 元件(DM-master/DM-worker),希望記錄一些重要記憶體資訊的資料流歷史。例如在 binlog replication 處理單元處理一條 query event 過程中會經歷處理 binlog event 、生成 ddl job、執行 job 這三個階段,我們將這三個處理邏輯抽象為三個事件,三個事件在時間上是有先後關係的,在邏輯上關聯了同一個 binlog 的處理流程,在 DM 中記錄這三個事件的 trace event 時使用了同一個 traceID處理 binlog event 生成一個新的 traceID,該 traceID 記錄在 ddl job 中,分發 ddl job 時記錄的 trace 事件會複用此 traceID在 executor 中最後執行 ddl job 的過程中記錄的 trace 事件也會複用此 traceID),這樣就將三個事件關聯起來,因為在同一個程序內,他們的時間戳真實反映了時間維度上的順序關係。

  • 由於 DM 提供了 shard DDL 的機制,多個 DM-worker 之間的資料會存在關聯,譬如在進行 shard DDL 的過程中,處於同一個 shard group 內的多個 DM-worker 的 DDL 是關聯在一起的。BaseEvent 定義中的 groupID 欄位就是用來解決多程序間 trace 事件關聯性的問題,定義具有相同 groupID 的事件屬於同一個事件組,表示它們之間在邏輯上有一定關聯性。舉一個例子,在 shard DDL 這個場景下,DM-master 協調 shard DDL 時會分別 向 DDL owner 分發執行 SQL 的請求,以及 向非 owner 分發忽略 DDL 的請求,在這兩組請求中攜帶了相同的 groupID,binlog replication 分發 ddl job 時會獲取到 groupID,這樣就將不同程序間 shard DDL 的執行關聯了起來。

我們可以利用收集的 trace 資訊輔助驗證資料同步的正確性。譬如在 驗證 safe_mode 邏輯正確性的測試 中,我們將 DM 啟動階段的 safe_mode 時間調短為 0s,期望驗證對於上游 update 操作產生的 binlog,如果該操作發生時上下游 shard DDL 沒有完全同步,那麼同步該 binlog 時的 safe_mode 為 true;反之如果該操作發生時上下游沒有進行 shard DDL 或 shard DDL 已經同步,那麼 safe_mode 為 false。通過 trace 機制,可以很容易從 tracer server 的介面獲取測試過程中的所有事件資訊並且抽取出 update DML,DDL 等對應的 trace event 資訊進一步通過這些資訊驗證 safe_mode 在 shard DDL 同步場景下工作的正確性

Failpoint 的使用

在整合測試中,為了對特定的同步流程或者特定的錯誤中斷做確定性測試,我們開發了一個名為 failpoint 的專案,用來在程式碼中注入特定的錯誤。現階段 DM 整合測試的 case 都是 提前設定環境變數,然後啟動 DM 相關程序來控制注入點的生效與否。目前我們正在探索將 trace 和 failpoint 結合的方案,通過 trace 獲取程序內部狀態,藉助 failpoint 提供的 http 介面動態調整注入點,以實現更智慧、更通用的錯誤注入測試。

3. 破壞性測試和大規模測試的原理與展望

破壞性測試中的錯誤注入

目前破壞性測試的測試 case 並沒有對外開源,我們在這裡介紹 DM 破壞性測試中所使用的部分故障注入

  • 使用 kill -9 強制終止 DM-worker 程序,或者使用 kill 來優雅地終止程序,然後重新啟動

  • 模擬上游寫入 TiDB 不相容的 DDL,通過 sql-skip/sql-replace 跳過或替換不相容 DDL 恢復同步的場景

  • 模擬上游發生主從切換時 DM 進行主從切換處理的正確性

  • 模擬下游 TiDB/TiKV 故障不可寫入的場景

  • 模擬網路出現丟包或高延遲的場景

  • 在未來 DM 提供高可用支援之後,還會增加更多的高可用相關測試場景,譬如磁碟空間寫滿、DM-worker 節點宕機自動恢復等

大規模測試

大規模測試中的上游負載複用了很多在 TiDB 中的測試用例,譬如銀行轉賬、大規模 DDL 操作等測試場景。該測試所有 case 均執行在 K8s 中,基於 K8s deployment yaml 部署一系列的 statefuset,通過 configmap 傳遞拓撲資訊。目前 DM 正在規劃實現 DM-operator 以及運行於 K8s 之上的完整解決方案,預期在未來可以更便捷地部署在 K8s 環境上,後續的大規模測試也會基於此繼續展開。

總結

本篇文章詳細地介紹了 DM 的測試體系,測試中使用到的工具和一些 case 的例項分析,分析如何通過多維度的測試保證 DM 的正確性、穩定性。然而儘管已經有了如此多的測試,我們仍不能保證 bug free,也不能保證測試 case 對於各類場景和邏輯路徑進行了百分之百的覆蓋,對於測試方法和測試 case 的完善仍需要不斷的探索。

至此 DM 的原始碼閱讀系列就暫時告一段落了,但是 DM 還在不斷地發展演化,DM 中長期的規劃中有很多激動人心的改動和優化,譬如高可用方案的落地、DM on K8s、實時資料校驗、更易用的資料遷移平臺等(未來對於 DM 的一些新特性可能會有番外篇)。希望感興趣的小夥伴可以持續關注 DM 的發展,也歡迎大家提供改進的建議和提 PR

原文閱讀https://www.pingcap.com/blog-cn/dm-source-code-reading-10/

相關推薦

DM 原始碼閱讀系列文章測試框架實現

作者:楊非 本文為 DM 原始碼閱讀系列文章的第十篇,之前的文章已經詳細介紹過 DM 資料同步各元件的實現原理和程式碼解析,相信大

DM 原始碼閱讀系列文章定製化資料同步功能的實現

作者:王相 本文為 DM 原始碼閱讀系列文章的第七篇,在 上篇文章 中我們介紹了 relay log 的實現,主要包括 relay

DM 原始碼閱讀系列文章Online Schema Change 同步支援

作者:lan 本文為 DM 原始碼閱讀系列文章的第八篇,上篇文章 對 DM 中的定製化資料同步功能進行詳細的講解,包括庫表路由(T

DM 原始碼閱讀系列文章shard DDL 與 checkpoint 機制的實現

作者:張學程 本文為 DM 原始碼閱讀系列文章的第九篇,在 上篇文章 中我們詳細介紹了 DM 對 online schema ch

TiDB 原始碼閱讀系列文章tikv-client

上篇文章 中,我們介紹了資料讀寫過程中 tikv-client 需要解決的幾個具體問題,本文將繼續介紹 tikv-client 裡的兩個主要的模組——負責處理分散式計算的 copIterator 和執行二階段提交的 twoPhaseCommitter。 copIterator cop

TiDB 原始碼閱讀系列文章初識 TiDB 原始碼

本文為 TiDB 原始碼閱讀系列文章的第二篇,第一篇文章介紹了 TiDB 整體的架構,知道 TiDB 有哪些模組,分別是做什麼的,從哪裡入手比較好,哪些可以忽略,哪些需要仔細閱讀。 這篇文章是一篇入門文件,難度係數比較低,其中部分內容可能大家在其他渠道已經看過

TiDB Binlog 原始碼閱讀系列文章Pump server 介紹

作者: satoru 在 上篇文章 中,我們介紹了 TiDB 如何通過 Pump client 將 binlog 發往 Pump,

TiDB 原始碼閱讀系列文章Table Partition

作者:肖亮亮 Table Partition 什麼是 Table Partition Table Partition 是指根據一定規則,將資料庫中的一張表分解成多個更小的容易管理的部分。從邏輯上看只有一張表,但是底層卻是由多個物理分割槽組成。相信對有關係型資料庫使用背景的使用者來

TiDB 原始碼閱讀系列文章基於規則的優化 II

在 TiDB 原始碼閱讀系列文章(七)基於規則的優化 一文中,我們介紹了幾種 TiDB 中的邏輯優化規則,包括列剪裁,最大最小消除,投影消除,謂詞下推和構建節點屬性,本篇將繼續介紹更多的優化規則:聚合消除、外連線消除和子查詢優化。 聚合消除 聚合消除會檢查 SQL 查詢中 Group By 語句所使用的列是否

TiKV 原始碼解析系列文章Storage

作者:張金鵬 背景知識 TiKV 是一個強一致的支援事務的分散式 KV 儲存。TiKV 通過 raft 來保證多副本之間的強一致,

TiKV 原始碼解析系列文章Prometheus

開發十年,就只剩下這套架構體系了! >>>   

TiKV 原始碼解析系列文章gRPC Server 的初始化和啟動流程

作者:屈鵬 本篇 TiKV 原始碼解析將為大家介紹 TiKV 的另一週邊元件—— grpc-rs。grpc-rs 是 PingCA

集合框架原始碼分析(jdK1.7) 集合框架對比總結

oracle java tutorial官方文件1.核心集合介面層次關係同類對比Map系列名稱 特點 資料結構 初始容量(預設) 擴容方式 HashMap 鍵值對,存Null值 散列表 1

讀logback原始碼系列文章——記錄日誌

今天晚上本來想來寫一下Logger怎麼記錄日誌,以及Appender元件。不過9點才從丈母孃家回來,又被幾個兄弟喊去喝酒,結果回來晚了,所以時間就只夠寫一篇Logger類的原始碼分析了。Appender找時間再寫 上篇部落格介紹了LoggerContext怎麼生成Logger

讀logback原始碼系列文章——記錄日誌的實際工作類Encoder

本系列的部落格從logback怎麼對接slf4j開始,逐步介紹了LoggerContext、Logger、ContextInitializer、Appender、Action等核心元件。跟讀logback的原始碼到這個程度,雖然不能說精通,不過至少日常的配置,和簡單的自定義擴

Java系列文章

java 學習JVMJVM系列:類裝載器的體系結構 JVM系列:Class文件檢驗器JVM系列:安全管理器JVM系列:策略文件Java垃圾回收機制深入剖析Classloader(一)--類的主動使用與被動使用深入剖析Classloader(二)-根類加載器,擴展類加載器與系統類加載器深入理解JVM—JVM內存

openstack系列文章

cnblogs 調度器 5.5 min 代碼位置 虛機 inux latest 階段 學習 openstack 的系列文章 - Nova Nova 基本概念 Nova 架構 openstack Log Nova 組件介紹 Nova 操作介紹 1. Nova 基本概念

小鑫の日常系列故事——排名次-2741

原題連結 Java語言沒有c/c++中的struct,沒辦法還是得用Java來寫,只能從其他的地方借鑑了,其中,類是一個不錯的選擇,雖然還不會, 但其實可以理解為一個函式,內部包含實現的功能。 import java.util.Arrays; import java.util.Sca

[搬運工系列]-JMeter 命令列非GUI模式

前文 講述了JMeter分散式執行指令碼,以更好的達到預設的效能測試(併發)場景。同時,在前文的第一章節中也提到了 JMeter 命令列(非GUI)模式,那麼此文就繼續前文,針對 JMeter 的命令列模式進行詳細解說。   一、應用場景 1、無需互動介面或受環境限制(linux t

SVM系列理論 SVR支援向量迴歸

1 敏感度損失函式 2 支援向量迴歸模型的匯出 3 對偶形式的匯出 4 KKT條件匯出支援向量 5 KKT條件匯出b的值