趙 鑫 李銘軒
1 北京郵電大學(xué) 北京 100876
2 中國(guó)聯(lián)通研究院 北京 100048
在傳統(tǒng)代碼架構(gòu)中,隨著新功能的增加,代碼庫會(huì)越變?cè)酱?。盡管代碼被分成各個(gè)模塊,但隨著時(shí)間推移,這些界限將變得模糊。代碼之間功能類似的模塊將越來越多,維護(hù)將變得越來越困難[1]。相對(duì)于傳統(tǒng)軟件,微服務(wù)是一種新型架構(gòu)方式。它提倡將單一應(yīng)用程序劃分成一組小的服務(wù),每個(gè)服務(wù)獨(dú)立完成一個(gè)很小的功能。服務(wù)之間相互協(xié)調(diào)、互相配合,為用戶提供最終價(jià)值。每個(gè)服務(wù)運(yùn)行在其獨(dú)立的進(jìn)程中,服務(wù)和服務(wù)之間采用輕量級(jí)的通信機(jī)制(通常是基于HTTP的Restful API)相互溝通[2]。但微服務(wù)架構(gòu)下軟件穩(wěn)定性不夠強(qiáng),故障率相對(duì)較高,且故障原因復(fù)雜,微服務(wù)的故障治理一直是一個(gè)很大的挑戰(zhàn)。
容器是一種虛擬化技術(shù),相比于VM,具有更好的性能,更輕量和更好的擴(kuò)展性[3]。每個(gè)容器上運(yùn)行一個(gè)進(jìn)程,容器內(nèi)打包這個(gè)進(jìn)程所需要的所有依賴環(huán)境,擁有良好的移植性。容器之間彼此獨(dú)立運(yùn)行,負(fù)責(zé)單一的功能。容器間也可以相互配合,相互調(diào)用,完成更大的功能。容器技術(shù)的特點(diǎn)使它與微服務(wù)架構(gòu)完美適配。這里也使用容器技術(shù)實(shí)現(xiàn)微服務(wù)架構(gòu)。
服務(wù)網(wǎng)格是用于處理服務(wù)與服務(wù)之間通信的專用基礎(chǔ)設(shè)施層。其主要思路是為每個(gè)微服務(wù)實(shí)例(往往以容器形式)都設(shè)置反向代理組件,即Sidecar。所有進(jìn)出微服務(wù)的流量會(huì)被Sidecar劫持,通過該組件進(jìn)行處理和轉(zhuǎn)發(fā)[4]。服務(wù)網(wǎng)格在不侵入業(yè)務(wù)代碼的情況下可以完成對(duì)微服務(wù)集群的監(jiān)控,為微服務(wù)故障治理提供了新的解決思路。本文基于服務(wù)網(wǎng)格技術(shù),對(duì)于微服務(wù)軟件進(jìn)行管理,深入探討微服務(wù)的故障特點(diǎn),提出一套完善的治理方案。
微服務(wù)帶來靈活性的同時(shí),也使得架構(gòu)變得復(fù)雜,原本一個(gè)單體軟件被分解成上百個(gè)微服務(wù),微服務(wù)之間調(diào)用關(guān)系復(fù)雜[5],服務(wù)之間協(xié)作也容易受到網(wǎng)絡(luò)層影響,這對(duì)故障的治理提出了很高的要求。微服務(wù)故障治理主要體現(xiàn)在以下幾個(gè)方面。
一個(gè)大型的應(yīng)用程序被拆分成幾十甚至上百個(gè)微服務(wù),分布在多臺(tái)服務(wù)器上。各個(gè)微服務(wù)之間相互協(xié)作,一層層進(jìn)行調(diào)用,調(diào)用拓?fù)洚惓?fù)雜。故障可能是某個(gè)微服務(wù)實(shí)例內(nèi)部發(fā)生故障(計(jì)算錯(cuò)誤、返回?cái)?shù)據(jù)錯(cuò)誤),可能是多個(gè)實(shí)例之間交互錯(cuò)誤(如實(shí)例運(yùn)行順序不對(duì)),也有可能是環(huán)境因素(如網(wǎng)絡(luò)延時(shí),使得請(qǐng)求無法及時(shí)響應(yīng);配置錯(cuò)誤,JVM配置和容器配置不一致)。
故障還可以分成功能性故障和非功能性故障,功能性故障會(huì)導(dǎo)致運(yùn)行結(jié)果的錯(cuò)誤,非功能性故障會(huì)使服務(wù)性能、穩(wěn)定性下降[6]。相比于功能性故障,非功能性故障更為隱秘,更不容易被發(fā)現(xiàn)。總之,在微服務(wù)架構(gòu)下,故障的種類繁多且原因復(fù)雜,定位也十分困難,尤其是在幾百臺(tái)服務(wù)器組成的大型集群中。
在單一架構(gòu)的軟件中,各個(gè)功能的協(xié)作在設(shè)計(jì)和開發(fā)之初就被設(shè)計(jì)好,大多數(shù)問題能夠在最初開發(fā)時(shí)被規(guī)避。當(dāng)軟件被拆分成一個(gè)個(gè)小的服務(wù)時(shí),服務(wù)之間、服務(wù)組件之間層層依賴,一個(gè)服務(wù)組件出現(xiàn)故障或者因?yàn)榫W(wǎng)絡(luò)層發(fā)生故障無法通信,會(huì)影響到所有與它協(xié)作的服務(wù)組件,導(dǎo)致這些組件積累大量請(qǐng)求,最終不可用,形成雪崩式的崩潰。
例如,上游發(fā)來一次請(qǐng)求,為了完成這個(gè)任務(wù),組件中形成了一系列的調(diào)用。這些調(diào)用將服務(wù)組件連接在一起,稱之為調(diào)用鏈[7]。假設(shè)每次調(diào)用服務(wù)出故障概率為Fone,整條調(diào)用鏈上出現(xiàn)故障的概率Fall,則Fall=1-(1-Fone)n,其中n為調(diào)用鏈長(zhǎng)度。
如圖1所示,當(dāng)調(diào)用鏈長(zhǎng)度增加時(shí),即使單個(gè)服務(wù)出現(xiàn)故障的概率極小,本次請(qǐng)求調(diào)用失敗的概率也會(huì)變得很大。
圖1 整體故障概率與調(diào)用鏈長(zhǎng)度關(guān)系
考慮到每個(gè)類型的服務(wù)有m個(gè)備份均衡流量,如果某一個(gè)服務(wù)組件故障,請(qǐng)求會(huì)轉(zhuǎn)移到備份服務(wù)組件,每種類型的服務(wù)不可用的概率是:Ftype=Fonem,整條調(diào)用鏈Fall=1-(1-Ftype)n=1-(1-Fonem)n,圖2是每種服務(wù)分別只有1個(gè)、5個(gè)、10個(gè)備份實(shí)例的情況,當(dāng)每種服務(wù)有多個(gè)備份時(shí)(m變大)能夠有效降低請(qǐng)求失敗的概率。但是在微服務(wù)架構(gòu)中,上層服務(wù)需要通過網(wǎng)絡(luò)層調(diào)用下層服務(wù)。網(wǎng)絡(luò)層是脆弱的,很容易因?yàn)橐恍┎豢煽挂蛩匕l(fā)生網(wǎng)絡(luò)連接失敗,或者服務(wù)沒有及時(shí)響應(yīng),使得上游一系列服務(wù)積壓了大量的請(qǐng)求,占用大量資源,F(xiàn)one數(shù)值激增,導(dǎo)致整個(gè)集群雪崩式地崩潰。因此在生產(chǎn)環(huán)境下,需要及時(shí)發(fā)現(xiàn)故障位置,防止整個(gè)集群發(fā)生崩潰。
圖2 服務(wù)有備份時(shí)整體故障概率與調(diào)用鏈長(zhǎng)度關(guān)系
測(cè)試對(duì)于提前發(fā)現(xiàn)錯(cuò)誤,驗(yàn)證軟件功能有著極其重要的意義,但是在微服務(wù)架構(gòu)下,軟件被拆分成多個(gè)服務(wù),需要為每個(gè)服務(wù)搭建測(cè)試環(huán)境,對(duì)每個(gè)服務(wù)功能進(jìn)行驗(yàn)證,過程繁瑣,消耗人力巨大。其次,微服務(wù)故障多在實(shí)例之間交互中產(chǎn)生,而且交互結(jié)果容易受到網(wǎng)絡(luò)層狀態(tài)(延遲、帶寬)影響。微服務(wù)本身和實(shí)例之間沒有固定的對(duì)應(yīng)關(guān)系,服務(wù)實(shí)例在整個(gè)集群中動(dòng)態(tài)地創(chuàng)建和銷毀。故障具有動(dòng)態(tài)性,難以重現(xiàn),測(cè)試環(huán)境下很難提前發(fā)現(xiàn)故障。
容器技術(shù)有很強(qiáng)的可移植性,一次打包隨處部署,相比于VM更加輕量級(jí),啟動(dòng)和銷毀十分容易[8],最重要的是容器之間能相互協(xié)作,共同完成更加復(fù)雜的任務(wù)。這些特點(diǎn)與微服務(wù)完美適配,目前工業(yè)界普遍以容器的形式部署微服務(wù)。如圖3所示,服務(wù)網(wǎng)格是在每個(gè)容器上增加一個(gè)反向代理Sidecar,所有進(jìn)出容器的流量完全經(jīng)過Sidecar,被Sidecar監(jiān)控和控制。Sidecar構(gòu)成了服務(wù)網(wǎng)格的Data Plane。比較新的服務(wù)網(wǎng)格在Data Plane的基礎(chǔ)之上增加Control Plane。Control Plane直接與Sidecar通信,將用戶策略轉(zhuǎn)發(fā)至Sidecar,實(shí)現(xiàn)對(duì)流量的更精準(zhǔn)地控制。
圖3 服務(wù)網(wǎng)格示意圖
服務(wù)網(wǎng)格在微服務(wù)實(shí)例上掛載代理,它與Spring Cloud、Dubbo這類侵入式框架不同,它與業(yè)務(wù)代碼耦合很小,業(yè)務(wù)代碼的技術(shù)選型、迭代升級(jí)都不會(huì)受到框架的制約[9]。服務(wù)網(wǎng)格有強(qiáng)大的監(jiān)控功能,能夠提供四個(gè)黃金指標(biāo)的監(jiān)控(延遲、流量、錯(cuò)誤、飽和),同時(shí)提供完善的日志功能,對(duì)于故障定位、原因分析有很大幫助。
但長(zhǎng)期以來服務(wù)網(wǎng)格的性能被人詬病,消耗過多的系統(tǒng)資源的同時(shí),對(duì)進(jìn)出流量也造成了比較大的延時(shí)。而且伴隨服務(wù)網(wǎng)格強(qiáng)大功能的是較高的復(fù)雜性,要熟練運(yùn)用服務(wù)網(wǎng)格需要投入一定的學(xué)習(xí)時(shí)間。
目前服務(wù)網(wǎng)格產(chǎn)品有很多,文章挑選了比較主流的幾款,分別是Istio、linkerd 2.0、Consul,AWS App Mesh和ASM。綜合比對(duì)他們的架構(gòu)設(shè)計(jì)、支持的功能、安全性和操作復(fù)雜度四個(gè)方面的信息,如表1所示。
表1 服務(wù)網(wǎng)格軟件綜合對(duì)比
在各種服務(wù)網(wǎng)格產(chǎn)品中,Istio的功能最為強(qiáng)大,最為靈活。它提供流量管理、擴(kuò)展性、安全和可觀察性四大方面功能,幾乎涵蓋微服務(wù)監(jiān)控所有需求。Istio從1.6版本開始支持虛擬機(jī)節(jié)點(diǎn),不再完全依賴Kubernetes平臺(tái),能更好地適應(yīng)異質(zhì)架構(gòu)的大型集群管理。同時(shí)Istio是一個(gè)開源項(xiàng)目,社區(qū)相比于其他的服務(wù)網(wǎng)格產(chǎn)品更加活躍,使用Istio作為微服務(wù)故障治理解決方案,更有代表意義。
整個(gè)故障預(yù)測(cè)流程如圖4所示。首先由手動(dòng)點(diǎn)擊或者使用postman模擬發(fā)送http請(qǐng)求,得到正常情況下和故障注入情況下兩種數(shù)據(jù),包括可視化監(jiān)控圖、服務(wù)調(diào)用日志,各個(gè)容器運(yùn)行指標(biāo)如表2所示。
表2 容器采集指標(biāo)
圖4 故障預(yù)測(cè)流程
最后將可視化監(jiān)控圖、日志和容器運(yùn)行時(shí)參數(shù)、定位故障,輸入故障預(yù)測(cè)模型,確定故障原因。
使用Istio提供的HTTP abort功能,該功能可以攔截并丟棄所有到達(dá)某個(gè)容器的流量,使得該容器對(duì)其他容器處于不可達(dá)狀態(tài)。同時(shí)故障注入分為服務(wù)級(jí)別和容器級(jí)別故障注入。一種微服務(wù)往往有多個(gè)容器備份,服務(wù)級(jí)別故障注入,讓該服務(wù)所對(duì)應(yīng)的所有容器全部處于故障狀態(tài),用于服務(wù)級(jí)別的故障定位驗(yàn)證;容器級(jí)別故障注入,讓該服務(wù)的某個(gè)容器處于故障狀態(tài),用于容器級(jí)別的故障定位驗(yàn)證。
在微服務(wù)中,為了完成一個(gè)任務(wù),微服務(wù)之間往往形成一個(gè)很長(zhǎng)的調(diào)用鏈,這個(gè)調(diào)用鏈上任意一個(gè)服務(wù)失敗都會(huì)導(dǎo)致本次請(qǐng)求失敗。當(dāng)故障出現(xiàn)時(shí),表現(xiàn)的是請(qǐng)求結(jié)果無回應(yīng)或者返回錯(cuò)誤結(jié)果。但具體錯(cuò)誤是出現(xiàn)哪個(gè)微服務(wù)上,需要消耗大量時(shí)間排查。這里提出調(diào)用鏈交叉累計(jì)法(Invoke Chain Intersection Accumulation Method,ICIAM)。在圖5中,綠色方塊代表正常服務(wù),紅色方塊代表故障服務(wù),灰色方塊代表因上游服務(wù)故障而導(dǎo)致下游故障的服務(wù)。
圖5 ICIAM示意圖
正常情況下,有三個(gè)請(qǐng)求,當(dāng)某個(gè)服務(wù)出現(xiàn)故障時(shí)(紅色),會(huì)影響到兩條調(diào)用鏈。對(duì)每條出現(xiàn)故障的調(diào)用鏈上所有服務(wù)實(shí)例分?jǐn)?shù)加1。分?jǐn)?shù)最高的服務(wù),往往是故障源頭。
每種服務(wù)可能存在多個(gè)容器備份,需要確定具體故障是否出現(xiàn)在容器[10]。其次,調(diào)用鏈交叉部分服務(wù)可能不止一個(gè),故障服務(wù)不一定會(huì)導(dǎo)致后續(xù)的服務(wù)不可用,使用ICIAM定位的故障源范圍較大,需要進(jìn)一步縮小。Istio集成Prometheus,可以對(duì)每個(gè)容器數(shù)據(jù)進(jìn)行監(jiān)控,收集容器CPU、內(nèi)存方面的數(shù)據(jù)。容器正常運(yùn)行和故障時(shí)的各項(xiàng)metric特性會(huì)有很大差異,可以通過分析這些參數(shù)(如表2所示),得到故障容器位置。
使用4臺(tái)8GB、4VCPU、操作系統(tǒng)為ubuntu18.04x64 的虛擬機(jī),搭建kubernetes-1.17.5 集群。在Kubernetes集群基礎(chǔ)上,搭建Istio 1.6.8,對(duì)Kubernetes上運(yùn)行的所有微服務(wù)實(shí)例進(jìn)行管理。
使用TrainTicket[11]軟件作為本次實(shí)驗(yàn)測(cè)試用例,檢驗(yàn)故障治理流程能否定位故障。這是由復(fù)旦大學(xué)實(shí)驗(yàn)室基于微服務(wù)架構(gòu)開發(fā)的應(yīng)用。它是由41個(gè)微服務(wù)組成,使用了Java、Node.js、Python、Go、Mongo DB和MySQL多種語言和技術(shù)。雖然復(fù)雜程度不及工業(yè)界軟件,但是服務(wù)間技術(shù)獨(dú)立,服務(wù)互相配合,使用多種語言開發(fā),幾乎體現(xiàn)了微服務(wù)架構(gòu)下軟件所有特點(diǎn),使用這款應(yīng)用進(jìn)行實(shí)驗(yàn)具有一定的代表性。
使用Postman模擬ts-preserve-service:preserve;ts-travel-plan-service:getByMinStation;ts-travel-service:queryInfo;Ts-preserv-otherservice:preserve;Ts-travel-plan-service:getByCheapest多種操作,這些請(qǐng)求都會(huì)調(diào)用ts-seatservice,用來獲得交叉調(diào)用。每次請(qǐng)求有50ms延時(shí),一共發(fā)送1 200次。使用Postman ts-travel-planservice:getByCheapest和ts-preserve-service:preserve兩種請(qǐng)求各300次,測(cè)試當(dāng)服務(wù)在實(shí)際生產(chǎn)情況下,調(diào)用鏈種類不夠豐富的情況下能否定位故障。
使用Istio中集成的Jaeger,每次有請(qǐng)求發(fā)生時(shí),將微服務(wù)之間調(diào)用關(guān)系,每個(gè)微服務(wù)所消耗時(shí)間以日志形式記錄。將服務(wù)的調(diào)用路徑、消耗時(shí)間等數(shù)據(jù)以json格式導(dǎo)出,同時(shí)Istio也支持微服務(wù)信息可視化,方便故障定位。
收集兩類數(shù)據(jù)集,一類是所有服務(wù)正常情況下的數(shù)據(jù)集,另外一類是故障注入下的數(shù)據(jù)集。使用Istio故障注入功能,調(diào)用ts-seat-service發(fā)生abort故障。請(qǐng)求到此類服務(wù)的HTTP請(qǐng)求,全部得到500的HTTP狀態(tài)碼。
4.4.1 ICIAM驗(yàn)證錯(cuò)誤服務(wù)環(huán)節(jié)
我們得到的正確數(shù)據(jù)集合一共是1 200次請(qǐng)求,錯(cuò)誤數(shù)據(jù)集也是1 200次請(qǐng)求。每種數(shù)據(jù)集中包含以下請(qǐng)求,ts-preserve-service:preserve;Ts-travel-planservice:getByMinStation;Ts-travel-service:queryInfo;Ts-preserv-other-service:preserve;Ts-travel-planservice:getByCheapest。
無故障情況下每種請(qǐng)求一次請(qǐng)求所引起的總共調(diào)用次數(shù),以及調(diào)用了ts-seat-service的次數(shù)如表3、表4所示。
表3 無故障時(shí)各類服務(wù)調(diào)用次數(shù)
表4 ts-seat-service故障時(shí)服務(wù)調(diào)用次數(shù)
總的調(diào)用次數(shù)少了,因?yàn)楫?dāng)請(qǐng)求到達(dá)ts-seatservice故障位置時(shí),得到錯(cuò)誤響應(yīng),后續(xù)服務(wù)全部終止。ts-seat-service被調(diào)用的次數(shù)為1,因?yàn)榈竭@里請(qǐng)求發(fā)生錯(cuò)誤,調(diào)用終止。
如圖6所示,同時(shí)發(fā)起5種請(qǐng)求,使用ICCA算法得分最高的前5名服務(wù)如表5所示。
圖6 5種請(qǐng)求故障調(diào)用鏈?zhǔn)疽鈭D
表5 5種請(qǐng)求時(shí)ICCA算法得分最高前5名服務(wù)
由于ts-seat-service是故障源頭,所有故障鏈的終點(diǎn)都會(huì)匯聚到這個(gè)服務(wù),增加ts-seat-service得分,最終故障源頭的得分最高。當(dāng)我們減少請(qǐng)求種類如圖7所示,只發(fā)起ts-travel-plan-service:getByCheapest和tspreserve-service:preserve兩種請(qǐng)求,使用ICIAM得分最高的前5名服務(wù)如表6所示。
表6 2種請(qǐng)求時(shí)ICCA算法得分最高前5名服務(wù)
圖7 2種請(qǐng)求故障調(diào)用鏈?zhǔn)疽鈭D
可以看到,當(dāng)故障服務(wù)上游服務(wù)一一對(duì)應(yīng)時(shí),可能出現(xiàn)故障服務(wù)得分和唯一上游服務(wù)相同的情況,但是這種情況比較少見,很少出現(xiàn)一種服務(wù)只是被一種服務(wù)調(diào)用。其次,即使出現(xiàn)這種情況,故障服務(wù)的得分也是故障調(diào)用鏈上最高之一,也能大概確定故障服務(wù)位置。
綜合比較,使用ICIAM,服務(wù)被越多種服務(wù)調(diào)用,發(fā)生故障時(shí),越容易被定位出來??傮w來說,ICIAM可以通過服務(wù)網(wǎng)格提供的調(diào)用路徑,定位到故障服務(wù)位置。
4.4.2 定位故障容器位置
一個(gè)服務(wù)往往有多個(gè)備份容器分?jǐn)偭髁浚枰ㄎ坏骄唧w哪個(gè)實(shí)例出現(xiàn)問題,采集容器CPU、內(nèi)存等方面指標(biāo),從這些指標(biāo)可以得到具體故障位置。
如表7、表8所示,使用Istio進(jìn)行故障注入,sidecar將所有請(qǐng)求攔截,返回http狀態(tài)碼500,此刻容器的CPU負(fù)載明顯低于正常情況,內(nèi)存也有少量降低,但是影響很小,分析原因是容器在啟動(dòng)時(shí)Request Memory設(shè)置在250MB,容器處于空閑狀態(tài)或處于較低負(fù)載水平時(shí),內(nèi)存保持在250MB左右,當(dāng)容器處理大量求量之時(shí),會(huì)額外申請(qǐng)內(nèi)存。通過容器各項(xiàng)指標(biāo)參數(shù),可以發(fā)現(xiàn)故障容器異常,從而判別故障。
表7 無故障情況下容器CPU、內(nèi)存情況
表8 故障注入情況下容器CPU,內(nèi)存情況
使用服務(wù)網(wǎng)格實(shí)現(xiàn)了對(duì)微服務(wù)全方位的監(jiān)控,包括服務(wù)調(diào)用路徑、在每個(gè)節(jié)點(diǎn)延時(shí)、容器運(yùn)行各項(xiàng)指標(biāo)。完善的監(jiān)控體系對(duì)于故障定位有著很大意義。
使用ICIAM方法能有效確定故障服務(wù)種類,而且在服務(wù)請(qǐng)求種類越多的情況下效果越明顯。因?yàn)榉?wù)請(qǐng)求種類越多,故障服務(wù)越有幾率被多種請(qǐng)求調(diào)用,得分更高。如果服務(wù)請(qǐng)求種類少,而且故障服務(wù)上游僅有少量服務(wù)甚至一條調(diào)用路徑,定位的故障范圍可能會(huì)擴(kuò)大,但基本能夠確定故障位置。通過容器各項(xiàng)運(yùn)行指標(biāo),定位具體故障容器。
然而,微服務(wù)故障治理不應(yīng)該僅僅定位故障位置,還應(yīng)該找到故障原因。這篇文章研究故障注入類型比較單一,只是使用istio對(duì)ts-seat-service在網(wǎng)絡(luò)連接上進(jìn)行故障注入,真實(shí)生產(chǎn)環(huán)境下故障種類十分復(fù)雜,有內(nèi)存負(fù)載過大、CPU負(fù)載過大、網(wǎng)絡(luò)環(huán)境故障、程序本身錯(cuò)誤、配置錯(cuò)誤等多種原因,需要進(jìn)一步細(xì)分。這也是未來需要研究的方向。