不用說香拉,這又是一篇帶有作者濃厚感情色彩的招聘帖子香浩。這次開的招聘貼是 Test 架構(gòu)開發(fā)工程師德挣。根據(jù)這么多年的觀察,我發(fā)現(xiàn)很多程序員是不喜歡測試的圈匆,總覺得測試是一個(gè)費(fèi)時(shí)費(fèi)力不討好漠另,不能提升自己開發(fā)能力、技術(shù)能力的事情跃赚。對于這個(gè)笆搓,我只能說,『呵呵纬傲,too young满败,too simple!』叹括。
我本人甚至堅(jiān)信:『無論做什么系統(tǒng)葫录,尤其是分布式系統(tǒng),測試是基石领猾。尤其是在我們這邊米同,測試是一個(gè)非常核心的產(chǎn)品競爭力∷じ停』作為一個(gè)分布式數(shù)據(jù)庫面粮,你如何讓用戶放心把數(shù)據(jù)存到你的系統(tǒng)上面,讓用戶相信他們的數(shù)據(jù)是安全的继低,這個(gè)很大程度上面只能靠測試。所以你看袁翁,測試的重要性不言而喻了柴底。
那么,作為一個(gè)測試架構(gòu)開發(fā)的工程師柄驻,到底要做什么事情呢鸿脓?
常規(guī)測試
Unit Test
首先野哭,我們來看看最基本的測試拨黔,也就是大家非常熟悉的篱蝇,單元測試。單元測試看起來很簡單疟位,但門道可不少瞻润,要寫好其實(shí)不容易。弄得不好甜刻,就容易陷入一個(gè)誤區(qū)绍撞,就是為了提高測試覆蓋率,加了很多自以為很不錯(cuò)但其實(shí)特別復(fù)雜的測試得院,給維護(hù)帶來了很嚴(yán)重的負(fù)擔(dān)傻铣。
關(guān)于如何寫單元測試,網(wǎng)上有很多介紹祥绞,就不詳細(xì)說明了非洲。這里提一個(gè)我自己喜歡的方式鸭限,就是 Table Driven Test。對于一個(gè)函數(shù)两踏,可能有很多不同的輸入?yún)?shù)败京,會 cover 到不同的分支,對應(yīng)不同的輸出結(jié)果梦染,通常的寫法很可能就是為每一種輸入赡麦,調(diào)用對應(yīng)函數(shù),在判斷結(jié)果帕识,重復(fù)代碼很多泛粹,這里用 Table Driven 的方法就比較清晰明了。
另外肮疗,現(xiàn)在我們還碰到的一個(gè)測試問題其實(shí)就是對一些邏輯里面 timeout 的處理晶姊,最通用的做法就是在外面 sleep 一段時(shí)間,等到 timeout 觸發(fā)相關(guān)的邏輯族吻,再判斷帽借。但這個(gè)其實(shí)是有問題的,在一些 CI 系統(tǒng)超歌,尤其是 Travis CI砍艾,給你的測試資源是非常少的,有可能 timeout 的那個(gè)線程跑的慢巍举,導(dǎo)致外面 sleep 結(jié)束的時(shí)候還沒觸發(fā)到相應(yīng)的邏輯脆荷,結(jié)果測試失敗。如何更優(yōu)雅的去解決這些問題懊悯,都是單元測試的挑戰(zhàn)蜓谋。
Integration Test
說完了,單元測試炭分,我們來說說集成測試桃焕,畢竟單元測試只能是針對特定的模塊的,整體能夠一起工作還是要靠集成測試捧毛。集成測試其實(shí)就跟自己的業(yè)務(wù)相關(guān)度比較大了观堂。但因?yàn)槲覀兪?MySQL 兼容協(xié)議,所以非常幸運(yùn)的呀忧,我們能重用市面上面很多的測試案例师痕,當(dāng)跑過了這些測試案例,就很大程度上面能讓我們確定而账,我們的兼容性已經(jīng)很不錯(cuò)了胰坟,譬如我們在很早的時(shí)候就跑過了 sqllogic test,這個(gè)的 case 集可是非常的恐怖的泞辐。
當(dāng)然笔横,光有現(xiàn)成的還是不夠的竞滓,我們還要學(xué)著自己造輪子,也就是根據(jù)我們實(shí)際的業(yè)務(wù)特別吹缔,自動的生成輸入輸出虽界,打到我們的系統(tǒng)里面,驗(yàn)證結(jié)果涛菠。譬如,我們可以構(gòu)造一個(gè)銀行轉(zhuǎn)賬的 case撇吞,100 個(gè)人俗冻,每個(gè)人初始 100 塊錢,兩兩互相隨機(jī)轉(zhuǎn)賬牍颈,無論什么時(shí)候迄薄,總的金額一定是 10000 元,如果多了或者少了煮岁,證明我們的程序就有 bug 了讥蔽。
Hello Chaos
上面的測試,頂多就是功能性的測試画机,驗(yàn)證下我們的程序在正常的情況下面是不是有問題冶伞,如果只是這樣,那這個(gè)產(chǎn)品是不靠譜的步氏。尤其是在分布式領(lǐng)域响禽,系統(tǒng)的不確定性太多了,以至于我們需要更加激進(jìn)一點(diǎn)荚醒,給系統(tǒng)里面放一只猴子來搗亂芋类。
Failpoint
最開始前面我提到了單元測試,雖然單元測試的覆蓋率是一個(gè)很重要的指標(biāo)界阁,但有些時(shí)候侯繁,一些邏輯無論你的單元測試怎么寫,都很難覆蓋到泡躯,因?yàn)樗枰芏噱e(cuò)誤條件來觸發(fā)贮竟。這里,我們就可以直接使用另一個(gè)機(jī)制精续,F(xiàn)ailpoint坝锰。
Failpoint 的原理很簡單,就是在一些關(guān)鍵代碼地方重付,注入一個(gè) failure 代碼顷级,里面有一些條件,當(dāng)系統(tǒng)運(yùn)行到這些 failure 代碼里面并且發(fā)現(xiàn)相應(yīng)的條件已經(jīng)滿足确垫,就直接執(zhí)行 failure 定義的操作弓颈,可能是 sleep 10帽芽,也可能是直接 panic 這樣的。
更進(jìn)一步翔冀,當(dāng)我們程序里面注入了非常多的 failure 之后导街,我們可以顯示的在程序運(yùn)行的時(shí)候,準(zhǔn)備好一系列 failure list纤子,依次的發(fā)給程序去觸發(fā) failure搬瑰,如果這時(shí)候程序出現(xiàn)了非預(yù)期的行為,譬如我上面提到的轉(zhuǎn)賬總金額不對控硼,那么我們就知道程序有 bug泽论,而剛好,我們可以用發(fā)現(xiàn)問題的這個(gè) failure list 來復(fù)現(xiàn)卡乾。
Failure Injection
Failpoint 這是代碼級別的注入翼悴,除開代碼,我們還可以做的更多幔妨。對于分布式系統(tǒng)來說鹦赎,網(wǎng)絡(luò)分區(qū)其實(shí)是一個(gè)很常見的問題,所以我們可以直接在測試的時(shí)候使用 iptables 和 tc 這樣的工具來進(jìn)行網(wǎng)絡(luò)干擾误堡,譬如增加 latency古话,將一些節(jié)點(diǎn)隔離,或者將一些節(jié)點(diǎn)弄成單向不通這樣埂伦∩范睿或者用 iperf,nc 這樣的工具把網(wǎng)絡(luò)帶寬打滿沾谜,看系統(tǒng)的反映膊毁。
另一個(gè)要考慮的就是磁盤損壞,畢竟壞盤太常見了基跑,這里我們可以讓系統(tǒng)跑在 fuse filesystem婚温,隨機(jī)的讓文件 API 返回錯(cuò)誤,也可以使用 debugfs 來干這個(gè)事情媳否。當(dāng)然栅螟,還有更極端的,直接 rm 干掉一些數(shù)據(jù)文件篱竭,或者強(qiáng)制將一些文件弄壞力图,觀察系統(tǒng)的反映。
Linearizability Test
因?yàn)槲覀兊南到y(tǒng)是一個(gè)強(qiáng)一致性的系統(tǒng)掺逼,所以一定要滿足線性一致性〕悦剑現(xiàn)階段,Jepsen 是這塊測試的最通用的選擇,Jepsen 的原理也是很簡單的赘那,啟動集群刑桑,對系統(tǒng)注入 failure,同時(shí)跑一系列的操作募舟,最后恢復(fù)集群祠斧,驗(yàn)證這些操作是否滿足線性一致性。雖然簡單拱礁,但 Jepsen 已經(jīng)給很多系統(tǒng)發(fā)現(xiàn)了 bug琢锋,以至于幾乎所有的分布式系統(tǒng)現(xiàn)在都要有 Jepsen 的測試了,當(dāng)然也包括我們呢灶。
后面我們會在 Jepsen 上面添加更多的 case吩蔑,同時(shí)也會基于一個(gè)純 Go 的 Porcupine 項(xiàng)目來進(jìn)行線性一致性的驗(yàn)證,畢竟 Go 是我們這邊最熟悉的語言填抬。
Chaos Engineering
好了,我們再進(jìn)一步了隧期,直接將上面這些上升到工程吧飒责。這個(gè)也就是我之前提到的混沌工程,我們先觀察系統(tǒng)的正常狀態(tài)仆潮,然后開始根據(jù)一些預(yù)先設(shè)計(jì)的 case 來干擾系統(tǒng)宏蛉,觀察系統(tǒng)的反映,從而在系統(tǒng)真的出現(xiàn)問題的時(shí)候性置,能快速知道到底是哪里引起的拾并。
更牛逼的,是學(xué)習(xí) Netflix鹏浅,根據(jù)現(xiàn)有系統(tǒng)的穩(wěn)態(tài)嗅义,直接學(xué)習(xí),自動的生成 failure injection隐砸,觀察系統(tǒng)是不是出現(xiàn)了問題之碗。
可以看到,在這里季希,只要你敢想褪那,沒什么不能做的,就怕你的想象力不豐富式塌,想的東西沒把系統(tǒng)搞死博敬。
Enhancement
通常弄到 Chaos Engineering 這一個(gè)階段,系統(tǒng)的穩(wěn)定性已經(jīng)很不錯(cuò)了峰尝,但我個(gè)人覺得我們還應(yīng)該做的更多偏窝,所以也開始了一些理論研究的工作。
TLA+
對于很多功能實(shí)現(xiàn)來說,第一個(gè)問題囚枪,可能就是派诬,『你如何證明你的設(shè)計(jì)是對的?』對于這個(gè)链沼,TLA+ 以及相關(guān)的 Coq 等就是一個(gè)非常好的工具默赂。
關(guān)于 TLA+,大家可以去看相關(guān)的介紹括勺,現(xiàn)階段最流行的兩個(gè)一致性協(xié)議缆八,Paxos 和 Raft 都經(jīng)過了 TLA+ 的認(rèn)證,我們這邊也進(jìn)行了很多相關(guān)的工作疾捍,譬如使用 TLA+ 來證明分布式事務(wù)的設(shè)計(jì)的正確性奈辰。當(dāng)然,后面我們還需要做的更多乱豆,我們的目標(biāo)是對于現(xiàn)有系統(tǒng)的很多設(shè)計(jì)奖恰,以及后續(xù)的設(shè)計(jì),都使用 TLA+ 來進(jìn)行證明宛裕。
Symbolic Execution
當(dāng)我們代碼里面有很多的異常處理瑟啃,或者 panic 的時(shí)候,一個(gè)比較關(guān)心的問題就是我們在什么時(shí)候才會進(jìn)入到這樣的異常邏輯揩尸。當(dāng)然蛹屿,可以通過 failpoint 來,但有時(shí)候岩榆,如果我們直接通過分析代碼错负,就能知道什么樣的參數(shù)能觸發(fā)到這樣的邏輯,這個(gè)就很減輕很多測試工作∮卤撸現(xiàn)階段犹撒,我們正在研究 symbolic execution 的就是在干這個(gè)事情。
總結(jié)
上面就是現(xiàn)在我們正在進(jìn)行的粒褒,以及未來后面會做的事情油航,可以看到,東西還是非常多的怀浆,挑戰(zhàn)還是非常大的谊囚。這些如果都弄出來,對大家的能力的鍛煉會非常的大执赡,當(dāng)然也能更好的提升我們自己產(chǎn)品的質(zhì)量镰踏。
如果你對這塊有很濃厚的興趣,喜歡折騰瞎搞系統(tǒng)沙合,同時(shí)喜歡理論證明奠伪,歡迎聯(lián)系我。我的郵箱 tl@pingcap.com。