在Qcon London 2016上,Peter Alvaro和Kolton Andrus分享了一項(xiàng)企業(yè)與學(xué)院合作的成功案例,這次合作最終為Netflix找到了一條自動(dòng)化故障注入測(cè)試(failure injection testing)的嶄新途徑。在這一案例中他們收獲了許多寶貴經(jīng)驗(yàn),其中主要包括:
從已知熟悉的事物出發(fā)
通常這有助于縮小解決方案的搜索空間,比如,Netflix的價(jià)值觀非常重視用戶體驗(yàn)
求同存異
通過有效的合作,大家制定并朝著共同的目標(biāo)一起前進(jìn),這是非常必要的
將理論付諸于實(shí)踐
真實(shí)世界往往比實(shí)驗(yàn)室復(fù)雜得多
主講人Alvaro現(xiàn)任圣克魯斯大學(xué)的助理教授,雖然他個(gè)人更樂意被簡稱為“教授”;而另一位主講人Andrus曾經(jīng)是Netflix“混亂(chaos)工程”團(tuán)隊(duì)的一員,在離開Netflix后創(chuàng)立了Gremlin Inc。兩位主講人在開始時(shí)談到,企業(yè)和學(xué)院的合作,往往因?yàn)楦髯阅繕?biāo)上的差異而變得困難重重。比如,“教授”Alvaro十分享受建模以及嘗試各種解決方案的過程。他在學(xué)院內(nèi)是否成功,往往體現(xiàn)在其研究成果是否被大量引用(citation impact,h-index),有沒有廣泛的應(yīng)用前景,有時(shí)甚至只是一個(gè)學(xué)院內(nèi)的評(píng)級(jí)。而作為“實(shí)踐者”,Andrus更熱衷于積極尋找可以切實(shí)解決的問題,并將其解決方案落地施行。他在企業(yè)內(nèi)是否成功,則具體地體現(xiàn)在提高系統(tǒng)可得性(availability),減少各種事故的數(shù)量,以及降低運(yùn)維成本。
合作的契機(jī)出現(xiàn)在2014年。當(dāng)時(shí)還在Netfix任職的Andrus,負(fù)責(zé)在著名的“混亂工程”的基礎(chǔ)之上,搭建一套所謂故障即服務(wù)(failure as a service)的測(cè)試框架。在生產(chǎn)環(huán)境上進(jìn)行故障測(cè)試,主要有兩個(gè)關(guān)鍵的概念:故障范圍(failure scope)和注入點(diǎn)(injection points)。故障范圍指的是,把一次故障測(cè)試可能產(chǎn)生的影響,限制在一個(gè)可控的范圍內(nèi),這個(gè)范圍可以小到某個(gè)特定的用戶或者設(shè)備,也可以大到所有用戶的1%。而注入點(diǎn)指的是系統(tǒng)內(nèi)計(jì)劃會(huì)發(fā)生故障的組件,比如RPC層,緩存層,或者持久層。
故障測(cè)試的最終目的,是為了當(dāng)真的有故障發(fā)生時(shí),生產(chǎn)環(huán)境不會(huì)停止服務(wù),并且整套系統(tǒng)可以在沒有人為干預(yù)的情況下,非常優(yōu)雅地通過降級(jí)(degrade)將發(fā)生故障的部分組件排除出去。Andrus還描述了一幕,在整套測(cè)試框架正確執(zhí)行的情況下,發(fā)生在故障后的場(chǎng)景。
某天,你來到辦公室和同事聊天。他們問:“嗨,你知道昨晚什么什么服務(wù)跪了么?”你可以回答說:“不知道啊,你接到電話了么?” “沒有啊,誰接到了么?” “沒有啊……” 這種感覺真的是爽到了!
我喜歡聊各種故障,但是是在事后,是在上班時(shí)間,而不是半夜
依照Netflix的慣例,故障測(cè)試的流程是人工完成的。Andrus會(huì)找各種團(tuán)隊(duì)開會(huì),討論哪些地方發(fā)生故障,發(fā)生故障的場(chǎng)景是怎么樣的,然后手動(dòng)實(shí)現(xiàn)并執(zhí)行一系列對(duì)應(yīng)的故障測(cè)試。直覺告訴Andrus應(yīng)該有更好的辦法。在網(wǎng)上大量搜索之后,Andrus找到了一段Alvaro在RICON Talk上做的演講和論文,《路徑驅(qū)動(dòng)的故障注入(Lineage-driven fault injection)》。Andrus相信這就是他苦苦尋找的那種,能夠安全地自動(dòng)化故障注入測(cè)試的“更好”的辦法。
Andrus開始和Alvaro頻繁地交換各種奇思妙想,最終兩人決定一起合作,將故障測(cè)試領(lǐng)域的最前沿提升到新的高度。合作建立在一種企業(yè)和學(xué)院都奉行的信條之上,即“在各自明確的職責(zé)范圍內(nèi)保有最大程度的自由(freedom and responsibility)”。這次合作的主要目標(biāo)有以下這些:
證明Alvaro提出的方法可以應(yīng)用于真實(shí)世界 證明這種方法的應(yīng)用規(guī)模可以輕松擴(kuò)展 使用這種方法找到真實(shí)系統(tǒng)中存在的真實(shí)問題為了完成這些目標(biāo),Andrus和Alvaro決定在暑假的幾個(gè)月中非常緊密地協(xié)作。為此,Alvaro還以合同工的身份加入了Netflix,在Netflix的辦公室中工作。
合作開始之后,他們首先試圖回答這樣兩個(gè)問題:“在一套基于微服務(wù)(microservice)架構(gòu)的常規(guī)系統(tǒng)中,有多少種可能的故障?”以及“所有這些故障,能組合出多少種故障情景?”。Alvaro做了個(gè)估算,很快給出了大致的結(jié)論:假設(shè)Netflix有100種服務(wù),并且每種服務(wù)只會(huì)發(fā)生1種故障,那么總共會(huì)有種不同的故障場(chǎng)景。換言之,必須執(zhí)行這么多次故障注入測(cè)試之后,Andrus才能詳盡地檢查每一種故障場(chǎng)景,然后徹底地找到每個(gè)缺陷。從時(shí)間上來看,這幾乎是不可能的。退而求其次,如果限定同時(shí)只會(huì)有1種故障發(fā)生,那么只須要執(zhí)行100次故障注入測(cè)試就足夠了;如果這個(gè)限制放開到4種,那么須要執(zhí)行300萬次測(cè)試;如果進(jìn)一步放開到7種,那將需要160億次。
從某種抽象的學(xué)術(shù)角度來說,討論容錯(cuò)性根本是多余的。
理論上,一套擁有容錯(cuò)性的系統(tǒng),必須在任何可預(yù)見的故障發(fā)生時(shí),始終能自動(dòng)找到替代路徑來繞開故障,繼續(xù)正常工作。因此,如果一套系統(tǒng),可預(yù)見的故障場(chǎng)景多達(dá)種,那么這套系統(tǒng)就必須在滿足業(yè)務(wù)邏輯的基礎(chǔ)上,兼顧每種故障場(chǎng)景下的替代路徑。
測(cè)試數(shù)量的指數(shù)級(jí)增長,意味著必須有自動(dòng)化的實(shí)施方案,然而這并非易事。一種簡單的策略是說,如果無法做到遍歷所有故障場(chǎng)景,那可以隨機(jī)抽取故障場(chǎng)景進(jìn)行注入測(cè)試;但這無異于大海撈針,而且這種策略的耗時(shí)依舊太長。既然如此,可以考慮將隨機(jī)抽樣改為人為引導(dǎo)挑選,利用人在業(yè)務(wù)領(lǐng)域內(nèi)的專業(yè)意見以及直覺,來遴選甄別那些有潛在問題的故障場(chǎng)景;然而這樣做就失去了自動(dòng)化的意義,因?yàn)槠鋺?yīng)用規(guī)模受限于人的數(shù)量。
使用常規(guī)的驗(yàn)證手段時(shí),人們經(jīng)常思索的是一個(gè)很難回答,同時(shí)也沒有正確答案的問題:怎樣才會(huì)出故障呢?這個(gè)問題對(duì)尋找系統(tǒng)缺陷,幾乎沒有任何幫助。在《路徑驅(qū)動(dòng)的故障注入》中提到的重要概念之一,是說人們應(yīng)該從一套系統(tǒng)的無故障狀態(tài)出發(fā),然后試圖去回答說“系統(tǒng)是如何達(dá)到目前這種無故障的狀態(tài)的?”,以及“整套邏輯鏈條中,是不是有哪里出錯(cuò),就能導(dǎo)致系統(tǒng)發(fā)生故障?”。邏輯鏈條中的錯(cuò)誤會(huì)將鏈條打破,導(dǎo)致系統(tǒng)無法到達(dá)最終的無故障狀態(tài),從而發(fā)生故障;而從無故障狀態(tài)推演出的各種邏輯鏈條,它們組成的圖(graph)能幫助人們找到那些最有價(jià)值的故障場(chǎng)景。
當(dāng)圖里的每一條邏輯鏈條上,每一個(gè)能出錯(cuò)的環(huán)節(jié)都被找到之后,整張圖就能簡化為一個(gè)合取范式(conjunctive normal form);其中每個(gè)能出錯(cuò)的環(huán)節(jié)是一個(gè)布爾變量,而剩下的環(huán)節(jié)則被省略。得益于這個(gè)簡化,“系統(tǒng)出錯(cuò)”這一抽象的概念,就變成了一個(gè)具體的問題:“如何讓這個(gè)合取范式的結(jié)果為假(false)?”,而這個(gè)問題的答案就是故障注入測(cè)試須要檢查的那些目標(biāo)故障場(chǎng)景。再次得益于這個(gè)簡化,目標(biāo)故障場(chǎng)景的搜索變得非常高效,因?yàn)槭聦?shí)上這是一個(gè)布爾滿足性問題,數(shù)學(xué)上已經(jīng)有很多完備且高效的解法。
Alvaro在論文中提到了一套名為“Molly”的原型(prototype)系統(tǒng),這套系統(tǒng)可以通過以下算法來尋找目標(biāo)故障場(chǎng)景:
找到一個(gè)被測(cè)試系統(tǒng)給出的正確輸出 從這個(gè)正確輸出反推,找到并構(gòu)建支持其正確性的邏輯鏈條圖 將圖簡化為合取范式并求解 回到第1步繼續(xù)下一個(gè)正確輸出,直到遍歷完所有正確輸出在算法中的第3步中,存在兩種可能的結(jié)果:一種是Alvaro自嘲為“好”的結(jié)果,即找到了一些目標(biāo)故障場(chǎng)景;另一種則是沒有找到目標(biāo)故障場(chǎng)景,那算法將繼續(xù)尋找。如果邏輯鏈條圖的構(gòu)建是完備的,那么被測(cè)系統(tǒng)在算法找到的每個(gè)目標(biāo)故障場(chǎng)景中,都有很大的幾率無法正常工作,或者說存在問題。
在將這套理論應(yīng)用到Netflix故障測(cè)試的過程中,第一個(gè)遇到的挑戰(zhàn)是說,如何定義一次常規(guī)用戶請(qǐng)求是“正確”的,因?yàn)樵贜etflix的服務(wù)棧中,一個(gè)返回200的HTTP請(qǐng)求,其結(jié)果并不一定是正確的。Andrus提到,此時(shí)一條Amazon核心指引(leadership)原則啟發(fā)了他們,即“一切從用戶出發(fā)”,因此他們不再糾結(jié)于單次請(qǐng)求是否正確,轉(zhuǎn)而試圖回答這樣的問題:用戶有沒有看到正確的結(jié)果?
在Netflix的服務(wù)棧中,有一種名為“真實(shí)用戶監(jiān)控(real user monitoring,簡稱RUM)”的服務(wù),可以監(jiān)控系統(tǒng)特征以及用戶體驗(yàn)。RUM數(shù)據(jù)以流的形式,不斷地從各種客戶端異步地發(fā)送給Netflix的后臺(tái)服務(wù),并在那里與同樣來自客戶端的用戶請(qǐng)求數(shù)據(jù)聯(lián)接(join),從中判斷用戶是否看到了正確的結(jié)果。同時(shí),Netflix還使用一種帶故障點(diǎn)標(biāo)記的分布式追蹤系統(tǒng),這套系統(tǒng)可以判斷某個(gè)用戶當(dāng)前是否進(jìn)入了一個(gè),由故障注入測(cè)試生成的故障站點(diǎn),并能追蹤到當(dāng)前測(cè)試的目標(biāo)故障場(chǎng)景中,具體有哪些被注入的故障。
例如,如果一套Netflix的服務(wù)被部署到多個(gè)區(qū)域的不同可得帶(availability zone)中,那么這套服務(wù)必須擁有冗余性。當(dāng)某個(gè)可得帶中的一項(xiàng)服務(wù)不再工作時(shí),Netflix的Hystrix組件,會(huì)從代碼層面上自動(dòng)向其他可得帶的同一服務(wù)重播請(qǐng)求,從而做到了時(shí)間上的冗余(redundancy through history),即多次請(qǐng)求返回一次正確結(jié)果。這種冗余性因其應(yīng)用普遍,在Netflix服務(wù)的邏輯鏈條圖中,占到了相當(dāng)大的份額。
Alvaro表示,唯有通過企業(yè)和學(xué)院的緊密合作,大家在共同目標(biāo)的指引下求同存異,才有可能發(fā)現(xiàn)這樣創(chuàng)新的途徑。Andrus也強(qiáng)調(diào)說,只有一起協(xié)同工作,頻繁的討論以及在白板上交流想法,這樣才是有效的合作。
這就是并肩工作的優(yōu)點(diǎn)。我們能夠討論各種各樣的問題,通過在白板上作圖解來提高溝通質(zhì)量。這是寫email或者打電話無法做到的,只能依靠經(jīng)常性的肩并肩工作。
隨著故障注入點(diǎn)和邏輯鏈條圖的確定,接下來的步驟是實(shí)現(xiàn)這套算法。作為學(xué)術(shù)派的Alvro決定證明一下自己的軟件研發(fā)功力,而他實(shí)現(xiàn)的算法現(xiàn)在已經(jīng)被部署到Netflix生產(chǎn)環(huán)境下運(yùn)行。
Alvaro:我自豪地宣布,我提交的代碼現(xiàn)在正跑在Netflix上!
Andrus:嚴(yán)格來說,是你提交的刨去這些println之后的代碼...
Alvaro:呃,抱歉我忘了刪了...
算法實(shí)現(xiàn)之后,整個(gè)項(xiàng)目到了最終的執(zhí)行階段。每當(dāng)有一個(gè)用戶請(qǐng)求進(jìn)入系統(tǒng),并且在處理過程中沒有引發(fā)任何故障,那么這個(gè)請(qǐng)求就會(huì)成為系統(tǒng)的一次“正確輸出”,并被故障注入測(cè)試系統(tǒng)在各種故障場(chǎng)景下重播。但是,在Netflix的分布式系統(tǒng)中,并不是每一種服務(wù)都具有等冪性(idempotent),因此有在確認(rèn)不會(huì)造成意外后果之前,故障注入測(cè)試系統(tǒng)并不能簡單地重播所有能產(chǎn)生“正確輸出”的用戶請(qǐng)求。
解決這一問題的方法,并不是讓所有服務(wù)都擁有等冪性,而是將產(chǎn)生“正確輸出”的用戶請(qǐng)求進(jìn)行分類;故障注入測(cè)試并不是重播所有能產(chǎn)生“正確輸出”的用戶請(qǐng)求,而是從每一類用戶請(qǐng)求中重播一個(gè),從而將由等冪性缺失造成的意外后果降到最低。如果兩個(gè)用戶請(qǐng)求在系統(tǒng)中的處理路徑是相同的,或者出錯(cuò)的路徑是相同的,那么從故障注入測(cè)試的角度來說,這兩個(gè)請(qǐng)求就是相同的,在故障注入測(cè)試時(shí)可歸為一類。然而,一個(gè)用戶請(qǐng)求在系統(tǒng)中的路徑,只有處理完才能知道,但兩個(gè)用戶請(qǐng)求是否屬于同一類必須在開始處理他們之前就決定,否則故障注入測(cè)試無法恰當(dāng)?shù)匾龑?dǎo)用戶請(qǐng)求進(jìn)入故障場(chǎng)景。本質(zhì)上,我們須要建立一種映射,將用戶請(qǐng)求映射到Netflix系統(tǒng)內(nèi)的某條路徑上。
這種映射可能可以用機(jī)器學(xué)習(xí)來找,但從時(shí)間上來看這個(gè)方法并不可取。最終兩人找到一種替代方案,使用一套被稱作Falcor的框架(一套由Netflix開源,用來提高數(shù)據(jù)傳輸效率的JavaScript庫)來確定,某個(gè)用戶請(qǐng)求將會(huì)涉及到哪些后臺(tái)服務(wù),從而近似地找到從用戶請(qǐng)求到系統(tǒng)內(nèi)路徑的映射。雖然并不完美,Avlaro表示這一近似確實(shí)幫助他們將理論應(yīng)用于現(xiàn)實(shí),并有效地推進(jìn)了整個(gè)項(xiàng)目的進(jìn)行。在這套方案上線運(yùn)行幾周之后,Alvaro和Andrus確認(rèn)說,這條嶄新的故障注入測(cè)試的自動(dòng)化方法是非常成功的。
在最后,兩位主講人還展示了一個(gè)名為“Netflix AppBoot”的用例分析。在這個(gè)用例中,最新的自動(dòng)化故障注入測(cè)試被應(yīng)用于一個(gè)在Netflix app啟動(dòng)時(shí)所發(fā)出的用戶請(qǐng)求上,而這個(gè)用戶請(qǐng)求的故障搜索空間大約是100個(gè)服務(wù)。雖然徹底檢查整個(gè)故障搜索空間需要次測(cè)試,自動(dòng)化故障注入測(cè)試篩選并最終只針對(duì)200個(gè)故障場(chǎng)景進(jìn)行了測(cè)試,其測(cè)試結(jié)果幫助開發(fā)人員找到并修復(fù)了6個(gè)比較嚴(yán)重的問題。
從學(xué)院的角度來看,Alvaro后續(xù)可能會(huì)研究故障搜索的優(yōu)先級(jí),嘗試更復(fù)雜的邏輯鏈條,以及探索故障之間的時(shí)間交錯(cuò)(temporal interleavings);而從企業(yè)的角度,Andrus日后會(huì)更專注于豐富設(shè)備的指標(biāo),尋求更有效的辦法來對(duì)用戶請(qǐng)求進(jìn)行分類,以及優(yōu)化測(cè)試選擇的策略。
Peter Alvaro和Kolton Andrus在QCon London演講的視頻可以在InfoQ上找到。
查看英文原文:“Monkeys in Labs Coats”: Applied Failure Testing Research at Netflix