最近在對一些自建的數據庫 driver/client 基礎庫的健壯性做混沌(故障)測試, 去驗證了解業務的故障處理機制和恢復時長. 主要涉及到了 MongoDB 和 etcd 這兩個基礎組件. 本文會介紹下相關的測試方法.
MongoDB 中的故障測試
MongoDB 是比較世界上熱門的文檔型數據庫, 支持 ACID 事務、分布式等特性.
社區上大部分對 MongoDB 進行混沌(故障)測試的文章大多都是外圍通過對 monogd 或 mongos 進行做處理進行模擬的. 比如如果想要讓 MongoDB 自己觸發副本集切換, 可以通過一下這樣一段 shell 腳本:
# 將副本集主節點進程掛死
kill -s STOP <mongodb-primary-pid># 掛死之后, 業務受損, MongoDB 在幾秒到十幾秒應該會進程主備切換
# 切換完成后, 業務能自動將連接切換到新的工作正常的主節點, 無需人工干預, 業務恢復正常
# 這里一般驗證的是 Mongo Client Driver 的可靠性
上面提到的手段一般是系統層級的, 如果我們只是想要模擬某個 MongoDB command 命令遇到網絡問題了, 怎么做?進一步想要進行更細粒度的測試. 其實 MongoDB 在 4.x 以上版本內部已經實現了一套可控的故障點模擬機制 -> failCommand.
在測試環境部署 MongoDB 副本集的時候, 一般可以通過以下方式啟動這個特性:
mongod --setParameter enableTestCommands=1
然后我們可以通過 mongo shell 針對特定的 command 開啟故障點, 例如針對一次 find
操作讓其返回錯誤碼 2:
db.adminCommand({configureFailPoint: "failCommand",mode: {"times": 1,},data: {errorCode: 2, failCommands: ["find"]}
});
這些故障點模擬是可控的, 成本相對于必直接在機器上搞破壞比較低, 也很適合融入持續集成自動化流程. MongoDB 內置的故障點機制還支持了很多的特性, 比如讓某個故障概率發生、返回任意 MongoDB 支持的錯誤碼類型等等, 通過該機制, 我們可以很方便的在單元測試和集成測試中驗證我們自己實現的 MongoDB Client Driver 的可靠性.
如果想具體知道 MongoDB 支持哪些故障點, 可以詳細查看 MongoDB 提供的 specification, 里面有提到針對 MongoDB 每一個特性, driver 可以使用哪些故障點進行測試.
MongoDB 官方提供的 go 實現的 dirver 代碼倉庫中也有不少的例子可以參考 https://github.com/mongodb/mongo-go-driver/blob/345ea9574e28732ca4f9d7d3bb9c103c897a65b8/mongo/with_transactions_test.go#L122.
etcd 中的故障測試
etcd 是一個開源的、高可用的分布式鍵值存儲系統, 它主要用于共享配置和服務發現.
之前我們提到了 MongoDB 內置了可控的故障點注入機制方便我們做故障點測試, 那么 etcd 是否也提供了呢?
沒錯, etcd 官方也提供了內置的可控故障注入手段方便我們圍繞 etcd 做故障模擬測試, 不過官方提供的可供部署的二進制分發默認是沒有使用故障注入特性的, 區別于 MongoDB 提供了開關, etcd 需要我們手動從源碼編譯出包含故障注入特性的二進制出來去部署.
etcd 官方實現了一個 Go 包 gofail 去做 "可控" 的故障點測試, 可以控制特定故障發生的概率和次數. gofail 可以用于任意 Go 實現的程序中.
原理上通過注釋在源代碼中通過注釋 (// gofail:
) 去對可能發生問題的地方埋藏一些故障注入點, 偏于進行測試驗證, 例如:
if t.backend.hooks != nil {// gofail: var commitBeforePreCommitHook struct{}t.backend.hooks.OnPreCommitUnsafe(t)// gofail: var commitAfterPreCommitHook struct{}}
在使用 go build
構建出二進制之前, 使用 gofail 提供的命令行工具 gofail enable
可以取消這些故障注入相關代碼的注釋, 并生成故障點相關的代碼,這樣編譯出的二進制可以用于故障場景的細粒度測試. 使用 gofail disable
去注釋去除掉生成的故障點相關代碼, 再使用 go build
編譯出的二進制就可以在生產環境使用.
在執行二進制的時候, 可以通過環境變量 GOFAIL_FAILPOINTS
去喚醒故障點, 如果你的二進制程序是個永不停機的服務, 那么可以通過 GOFAIL_HTTP 環境變量在程序啟動的同時, 啟動一個 HTTP endpoint 去給外部測試工具喚醒埋藏的故障點.
具體的原理實現可以查看下 gofail 的設計文檔 -> design.
值的一提的是 pingcap 重新基于 gofail 重新造了個輪子, 做了不少優化: failpoint 相關代碼不應該有任何額外開銷; 不能影響正常功能邏輯,不能對功能代碼有任何侵入; failpoint 代碼必須是易讀、易寫并且能引入編譯器檢測; 最終生成的代碼必須具有可讀性; 生成代碼中,功能邏輯代碼的行號不能發生變化(便于調試); 如果想要了解它的實現原理, 可以查看這篇官方文章: Golang Failpoint 的設計與實現 這篇深度剖析的博客也值得一讀: 在 Go 中使用 Failpoint 注入故障
接下來我們看看如何在 etcd 中啟用這些故障埋點。
編譯出可供進行故障測試的 etcd
etcd 官方倉庫的 Makefile 已經內置了對應的指令幫我們快速編譯出包含故障點二進制 etcd server. 編譯步驟大致如下:
git clone git@github.com:etcd-io/etcd.git
cd etcd# 激活故障點注釋
make gofail-enable
make build
# 還原代碼
make gofail-disable
經過如上步驟之后, 編譯好的二進制文件直接可以在 bin
目錄下可以看到, 讓我們啟動 etcd 看一下:
# 開啟通過 HTTP 激活故障點的方式
GOFAIL_HTTP="127.0.0.1:22381" ./bin/etcd
使用 curl 看下有哪些故障點可以使用:
curl http://127.0.0.1:22381afterCommit=
afterStartDBTxn=
afterWritebackBuf=
applyBeforeOpenSnapshot=
beforeApplyOneConfChange=
beforeApplyOneEntryNormal=
beforeCommit=
beforeLookupWhenForwardLeaseTimeToLive=
beforeLookupWhenLeaseTimeToLive=
beforeSendWatchResponse=
beforeStartDBTxn=
beforeWritebackBuf=
commitAfterPreCommitHook=
commitBeforePreCommitHook=
compactAfterCommitBatch=
compactAfterCommitScheduledCompact=
compactAfterSetFinishedCompact=
compactBeforeCommitBatch=
compactBeforeCommitScheduledCompact=
compactBeforeSetFinishedCompact=
defragBeforeCopy=
defragBeforeRename=
raftAfterApplySnap=
raftAfterSave=
raftAfterSaveSnap=
raftAfterWALRelease=
raftBeforeAdvance=
raftBeforeApplySnap=
raftBeforeFollowerSend=
raftBeforeLeaderSend=
raftBeforeSave=
raftBeforeSaveSnap=
walAfterSync=
walBeforeSync=
知道了故障點, 就可以針對指定故障設置故障類型, 如下:
# beforeLookupWhenForwardLeaseTimeToLive 故障點處 sleep 1 秒
curl http://127.0.0.1:22381/beforeLookupWhenForwardLeaseTimeToLive -XPUT -d'sleep(10000)'
# 查看故障點狀態
curl http://127.0.0.1:22381/beforeLookupWhenForwardLeaseTimeToLive
sleep(1000)
故障點的描述語法見: https://github.com/etcd-io/gofail/blob/master/doc/design.md#syntax
至此, 已經可以利用 etcd 內置的故障點做一些故障模擬測試了, 具體怎么使用這些故障點可以參考下 etcd 官方的集成測試實現 -> etcd Robustness Testing. 通過故障點名稱搜索相關代碼即可.
除了上述這些 etcd 內置的故障點, etcd 的官方倉庫也提供了一份系統級的集成測試例子 -> etcd local-tester, 它模擬了 etcd 集群模式下的節點宕機測試.
好了, 本文的分享, 到此暫時結束啦 ?( ′・?・` )~
小廣告插播: 最近維護了可以維護多個 etcd server、etcdctl、etcductl 版本的工具 vfox-etcd, 你也可以用它來在機器上安裝多個包含 failpoint 的 etcd 版本進行混沌 (故障模擬) 測試哦~
本文由博客一文多發平臺 OpenWrite 發布!