攜程微服務(wù)體系下的服務(wù)治理之道和優(yōu)化實踐
一、背景
微服務(wù)架構(gòu)在中大型互聯(lián)網(wǎng)公司中被廣泛應(yīng)用,隨著業(yè)務(wù)的發(fā)展,應(yīng)用數(shù)越來越多、調(diào)用關(guān)系也越來越復(fù)雜。中臺化后,交易系統(tǒng)要支持業(yè)務(wù)線多,系統(tǒng)復(fù)雜性高,原系統(tǒng)雖然能支撐業(yè)務(wù)量的持續(xù)增長,但在穩(wěn)定性、吞吐力和資源利用率上面,還存在優(yōu)化空間。
分享的目的
本文站在業(yè)務(wù)開發(fā)角度介紹開發(fā)在微服務(wù)架構(gòu)下遇到的相關(guān)問題(微服務(wù)架構(gòu)的優(yōu)缺點這里不再贅述),以門票活動預(yù)訂流程查詢引擎為例,分享微服務(wù)治理的實戰(zhàn)經(jīng)驗,希望能給遇到同樣問題的同學(xué)提供一些借鑒思路。
如下圖所示,藍(lán)色部分為本文的重點
圖1 微服務(wù)架構(gòu)關(guān)注點
在微服務(wù)治理之前,我們先簡單了解一下微服務(wù)歷史和陷阱。
二、微服務(wù)簡史
微服務(wù)概念在2005年被提出,2011年用來代表架構(gòu)風(fēng)格。
定義:微服務(wù)是一種架構(gòu)風(fēng)格,一個大型復(fù)雜軟件應(yīng)用由一個或多個微服務(wù)組成,每個微服務(wù)僅關(guān)注于完成一件任務(wù)。
2.1 微服務(wù)與SOA的關(guān)系
大家在使用SOA (Service-Oriented Architecture)的時候往往分不清和微服務(wù)有什么關(guān)系,總結(jié)如下:
- 微服務(wù)是SOA的實現(xiàn)方式
- 微服務(wù)是去掉ESB后的SOA
- 微服務(wù)是一種和SOA相似但是兩種不同的架構(gòu)設(shè)計風(fēng)格
?
?
?
圖2 微服務(wù)與SOA的關(guān)系
了解微服務(wù)與SOA關(guān)系后,再對比一下功能差異:
表1 微服務(wù)與SOA對比
2.2 攜程SOA從1.0到 2.0演進(jìn)
圖3 攜程SOA演進(jìn)
攜程微服務(wù):攜程SOA2.0是微服務(wù)架構(gòu),推薦單機(jī)、單應(yīng)用、單服務(wù)。
三、微服務(wù)的陷阱
微服務(wù)這個話術(shù)會將關(guān)注點錯誤的聚焦在“微”上,大家會誤以為服務(wù)越小越好,實際上大小并不是第一考慮因素。接下來我們來看看開發(fā)微服務(wù)應(yīng)用的時候容易踩到的陷阱。
下圖可以看出,服務(wù)拆分越細(xì),調(diào)用關(guān)系越復(fù)雜。
調(diào)用鏈路理論上有 n * (n-1) 條:
圖4 服務(wù)粒度越細(xì)調(diào)用關(guān)系越復(fù)雜
應(yīng)用粒度拆分過細(xì)容易帶來以下幾個問題:
3.1 重復(fù)調(diào)用
調(diào)用路徑 C - >D ->E 和 C ->E, 對于E的一次請求,可能會被調(diào)用了多次。
圖5 一次請求中服務(wù)E被重復(fù)調(diào)用
3.2 循環(huán)依賴
一條鏈路出問題,導(dǎo)致其他鏈路故障。當(dāng)服務(wù)B1或B2 性能變差時,最終導(dǎo)致鏈路A/B都會被影響,嚴(yán)重情況下導(dǎo)致宕機(jī)。
圖6 循環(huán)依賴
3.3 鏈路太長
服務(wù)層級過深,一次請求鏈路太長會導(dǎo)致性能下降,每層網(wǎng)絡(luò)延時和序列化反序列化時間都有性能損失,層級越深,下游性能越差。
鏈路太長,定位問題困難(效率低),當(dāng)服務(wù)F出現(xiàn)故障時,下游A~E 應(yīng)用 owner 需要排查原因。
圖7 鏈路越長,性能損失越大
以上這些問題,在日常開發(fā)中容易遇到,下面我們看看怎么解決這些問題。
四、微服務(wù)治理
從下圖中可以看到應(yīng)用之間調(diào)用關(guān)系復(fù)雜,并且有嚴(yán)重的循環(huán)依賴問題。
圖8 應(yīng)用調(diào)用關(guān)系圖(雙黃線表示循環(huán)依賴)
循環(huán)依賴是微服務(wù)里面容易忽視的問題,系統(tǒng)穩(wěn)定的情況下不會出現(xiàn)問題,由于某些原因,當(dāng)系統(tǒng)從穩(wěn)定變成非穩(wěn)定狀態(tài)時,循環(huán)依賴容易導(dǎo)致更嚴(yán)重的故障。我們先看1個生產(chǎn)案例:
案例:發(fā)布過程中下游超時,訂單下跌?
剛接入流量的機(jī)器因線程初始化、類加載鎖、JIT等會產(chǎn)生慢請求。
圖9 發(fā)布過程中的慢請求
當(dāng)流量接入時,請求在剛拉入的機(jī)器中多次來回調(diào)用,因多次慢請求疊加,導(dǎo)致接口越來越慢,機(jī)器資源耗盡,一臺一臺被拖垮,最終整個服務(wù)不可用,產(chǎn)生雪崩(如下圖)。
圖10 發(fā)布過程中循環(huán)依賴導(dǎo)致應(yīng)用雪崩
當(dāng)然如果應(yīng)用間循環(huán)依賴QPS很小,例如單機(jī)QPS在10以內(nèi),少量慢請求無法將資源耗盡,一般不導(dǎo)致故障,但是這種“壞味道”會給系統(tǒng)埋下隱患,嚴(yán)重的時候會演變?yōu)榻涌诩壍难h(huán)依賴,導(dǎo)致死循環(huán),并且這種死循環(huán)可能在測試環(huán)境由于命中緩存沒有被發(fā)現(xiàn),發(fā)布到生產(chǎn)后有些緩存穿透的請求就會導(dǎo)致循環(huán)調(diào)用,直到超時;如果單機(jī)QPS上百,產(chǎn)生的慢請求短時間內(nèi)耗盡資源,阻塞后續(xù)請求,導(dǎo)致性能下降,產(chǎn)生故障。
故障恢復(fù)期間,由于調(diào)用關(guān)系復(fù)雜,分不清上下游關(guān)系,無法根據(jù)調(diào)用關(guān)系來限流,導(dǎo)致定位困難,恢復(fù)時間長。
上述案例主要是由循環(huán)依賴引起,像一顆炸彈,為系統(tǒng)埋下隱患。
除了循環(huán)依賴,還有下面幾類問題可以優(yōu)化:
1)層級太深:?
- 透傳字段要改多個應(yīng)用,需求迭代效率低
- 每層網(wǎng)絡(luò)延時、序列化和反序列化都有性能損失,導(dǎo)致終端體驗差
2)重復(fù)緩存:同一個DB不同應(yīng)用重復(fù)構(gòu)建緩存
3)流量大:?
- 重復(fù)調(diào)用,直接調(diào)用或者間接調(diào)用,末尾服務(wù)壓力大
- 離線任務(wù)峰值波動太大
4)未隔離:?核心、非核心流量未隔離
5)效能低:人均應(yīng)用多/資源使用率低
針對上面的幾類問題,我們制定了微服務(wù)治理目標(biāo)、原則和治理策略。
4.1 治理目標(biāo)
1)穩(wěn)定:故障隔離,提升系統(tǒng)穩(wěn)定性
2)交付:獨立迭代、獨立擴(kuò)展、快速交付
橫向拆分:減少耦合,獨立迭代。
縱向拆分:減少應(yīng)用層級,提高開發(fā)效率,縮短交付周期。
3)重用:相同功能復(fù)用
不同系統(tǒng)重復(fù)功能復(fù)用,減少重復(fù)開發(fā),提升一致性。
4.2 治理原則
1)避免跨團(tuán)隊維護(hù)一套代碼。
2)服務(wù)粒度要與團(tuán)隊規(guī)模匹配,人均應(yīng)用數(shù)在3個以內(nèi)。
根據(jù)歷史經(jīng)驗,一個人在超過3個應(yīng)用之間來回切換開發(fā),開發(fā)效率會降低,日常處理告警繁瑣,業(yè)務(wù)和性能優(yōu)化也無法聚焦。
3)應(yīng)用分層:上一層可以依賴任意下一層級(不可反向依賴)。
4)層級深度:垂直域/小組內(nèi),應(yīng)用層級控制在5層以內(nèi)。
這里的“5層”是我們根據(jù)實際業(yè)務(wù)實際情況來定的。一個垂直域/小組內(nèi)應(yīng)用層級超過5層,一個需求上下游依賴太多,開發(fā)效率會降低。
4.3 構(gòu)建原則
1)業(yè)務(wù)領(lǐng)域拆分:單一職責(zé),業(yè)務(wù)建模(對人員要求高)
2)數(shù)據(jù)存儲:獨立的數(shù)據(jù)讀寫API
3)復(fù)用性:功能復(fù)用(比如基礎(chǔ)數(shù)據(jù)提供能力,提供給不同小組使用)
- 可靠性
- 核心與非核心隔離
??4)穩(wěn)定規(guī)則與易變動規(guī)則隔離
5)快速失?。涸O(shè)置合理的熔斷規(guī)則
6)異步通信:將與此次請求無關(guān)的操作/調(diào)用異步化
4.4 治理策略
1)去除循環(huán)依賴?
問題:服務(wù)B和服務(wù)C 循環(huán)依賴
策略?
- 應(yīng)用分層與定位:第一步劃分應(yīng)用層級(分層工具有傳統(tǒng)三層架構(gòu)、泛領(lǐng)域分層等),將應(yīng)用定位劃分到不同的層級。
- 確認(rèn)依賴關(guān)系:每一層內(nèi)如果有多個應(yīng)用,確認(rèn)上下游關(guān)系。這個根據(jù)業(yè)務(wù)場景來,根據(jù)父子關(guān)系,包含關(guān)系,依賴關(guān)系,確認(rèn)每一層內(nèi)的依賴關(guān)系和應(yīng)用職責(zé)。
?
圖11 循環(huán)依賴治理
2)縮短調(diào)用鏈路?
問題:服務(wù)BCD 鏈路太長(垂直域/小組內(nèi))
策略?
- 領(lǐng)域細(xì)分:將粗粒度的應(yīng)用按照業(yè)務(wù)領(lǐng)域垂直劃分,不同層級負(fù)責(zé)不同的職責(zé),讓系統(tǒng)更獨立。
- 減少透傳:每個層級職責(zé)清晰,減少不必要的透傳,讓開發(fā)效率更高。?
圖12 縮短調(diào)用鏈路
3)復(fù)用性
問題:服務(wù)BCD 對相同數(shù)據(jù)重復(fù)緩存(存在一致性問題)
策略?
下沉基礎(chǔ)服務(wù),提供基礎(chǔ)數(shù)據(jù):將相同的功能下沉為基礎(chǔ)服務(wù),例如:基礎(chǔ)數(shù)據(jù)服務(wù)提供緩存,翻譯等功能。避免不同的使用方重復(fù)緩存,重復(fù)接入翻譯。
圖13 重復(fù)功能下沉
效果:?下沉基礎(chǔ)數(shù)據(jù)服務(wù),統(tǒng)一緩存,翻譯等功能,提供給不同的開發(fā)組使用。?
4)流量治理?
a) 重復(fù)調(diào)用?
問題:一次請求,服務(wù)C同一個接口被重復(fù)調(diào)用
策略?
功能內(nèi)聚:將同一個功能對下游的依賴放到同一個服務(wù)內(nèi)調(diào)用。由于系統(tǒng)自身迭代導(dǎo)致的不合理調(diào)用,可以按照上述方法優(yōu)化。如果為了解耦將功能拆開,可以根據(jù)實際情況評估影響和收益。
圖14 功能內(nèi)聚合并重復(fù)調(diào)用
效果:功能內(nèi)聚,多次調(diào)用合并為一次調(diào)用。
b) 降低調(diào)用量?
問題:一個服務(wù)中,不同的接口功能拆分太細(xì),下游使用的時候都需要調(diào)用多個接口組裝結(jié)果。例如:一次請求服務(wù)B的a、b、c、d、e接口都被調(diào)用,下游為實現(xiàn)一個功能,需要調(diào)用太多小接口。
策略?
- 合并服務(wù)B中同一領(lǐng)域功能:將相同的功能合并到一個接口,減少調(diào)用量。
- 一個接口提供模塊參數(shù),按需調(diào)用:
- 支持按需使用,對不同業(yè)務(wù)場景非必須的功能,提供模塊參數(shù),按需傳參。
- 對于獨立的前端頁面接口,對外透明,內(nèi)部封裝對應(yīng)場景需要的模塊參數(shù),例如前端首屏請求。
圖15 請求合并
效果:聚合相同功能,合并小接口,多次調(diào)用合并為一次調(diào)用。
c) 流量隔離?
問題:非核心流量(例如:Job調(diào)度)大于用戶流量
策略?
流量隔離:一套代碼,隔離部署,將核心和非核心流量隔離。核心流量承載用戶請求,保證交易的穩(wěn)定性,非核心流量承載離線任務(wù)調(diào)度和非核心場景調(diào)用。
圖16 流量隔離
效果:總成本不變,核心鏈路穩(wěn)定性得到提升,非核心鏈路CPU使用率得到提升。
d) 離線調(diào)度流量消峰?
問題:單位時間內(nèi)調(diào)度過于集中(Job)
策略?
合理的延長調(diào)度時間:適當(dāng)延遲調(diào)度時間,降低每分鐘的調(diào)用峰值,讓每分鐘內(nèi)調(diào)用量更加平穩(wěn)。
圖17 離線調(diào)度流量消峰
效果:調(diào)度總時間在可接受范圍內(nèi),調(diào)度時間拉長,單位時間內(nèi)調(diào)用總量降低,降低服務(wù)端峰值壓力。
問題:每秒內(nèi)調(diào)度不均衡(Job),導(dǎo)致服務(wù)穩(wěn)定性差或為了能承載請求需要冗余更多服務(wù)器資源。
圖18 客戶端調(diào)度QPS不均衡
策略?
客戶端削峰填谷:調(diào)度波動太大,會導(dǎo)致請求到了服務(wù)端被限流或者服務(wù)端擴(kuò)縮容。對于調(diào)度不均衡的離線任務(wù),我們在客戶端控制每秒內(nèi)發(fā)送的請求量,讓每秒內(nèi)請求更加平穩(wěn),任務(wù)調(diào)度總時間不變。
圖19 客戶端調(diào)度從不均衡變?yōu)榫?/span>
效果:分鐘內(nèi)總的調(diào)用量不變,服務(wù)端調(diào)用量從波動變?yōu)槠椒€(wěn)。
5)降低人均應(yīng)用數(shù)/提升CPU使用率?
問題?
- 人均應(yīng)用過多,開發(fā)效率降低
- CPU使用率6%以下應(yīng)用數(shù)占比超過50% 且總核數(shù)占比超過30%
?
策略?
- 短期:縮容,將單邊服務(wù)器數(shù)縮容到SRE標(biāo)準(zhǔn)最小配置。
- 長期:合并拆分過細(xì)的應(yīng)用,參考?xì)v史、現(xiàn)狀和將來的規(guī)劃,將拆分過細(xì)、CPU使用率長期小于6%的應(yīng)用做合并。
圖20 應(yīng)用合并
五、實施效果
1)循環(huán)依賴(應(yīng)用分層,解除應(yīng)用間循環(huán)依賴)
- 去掉65條循環(huán)依賴鏈路,消除雪崩的風(fēng)險
- 超時類告警降低99%
- 排障效率提升至分鐘級別
?2)鏈路長(減少應(yīng)用層級):調(diào)用鏈深度縮短 40%
3)復(fù)用性(下沉基礎(chǔ)數(shù)據(jù)服務(wù),減少重復(fù)功能)
- 新增基礎(chǔ)數(shù)據(jù)服務(wù),緩存統(tǒng)一,解決一致性問題
- 緩存容量減少60%
?4)流量治理(降低水位線)
- 重復(fù)調(diào)用:功能內(nèi)聚,去除重復(fù)調(diào)用
- 調(diào)用量大:合并小接口、消除調(diào)用峰值;離線任務(wù)削峰填谷,降低峰值調(diào)用量
- 核心應(yīng)用調(diào)用量減少73%,核心系統(tǒng)峰值降低50%
5)開發(fā)效率(解耦&減少中間層)
- 水平拆分獨立功能,減少耦合,獨立開發(fā)
- 垂直領(lǐng)域減少3層,開發(fā)效率提升
?6)查詢引擎性能提升65%,QPS從8w提升至24w
- 減少了系統(tǒng)不穩(wěn)定導(dǎo)致的服務(wù)變慢
- 領(lǐng)域劃分,垂直優(yōu)化系統(tǒng),專注用戶端到底層的優(yōu)化
?7)人均應(yīng)用:人均應(yīng)用數(shù)控制在2個以內(nèi)
8)資源使用率(應(yīng)用合并,提升CPU使用率)
- 40+個應(yīng)用CPU使用率(加權(quán)平均)從18%提升至32%
- 治理前后查詢引擎鏈路對比:
圖21 門票活動查詢引擎微服務(wù)治理前后對比
六、總結(jié)
微服務(wù)架構(gòu)下服務(wù)拆分越細(xì),調(diào)用關(guān)系越復(fù)雜,層級越深,性能損耗越大,開發(fā)效率越低(垂直域/小組內(nèi)),所以服務(wù)不是越小越好,而是“合適的大小”。
在構(gòu)建微服務(wù)的時候,要根據(jù)業(yè)務(wù)體量、團(tuán)隊規(guī)模、成本等因素綜合考慮,按照合理的原則,構(gòu)建出適合的大小,以達(dá)到預(yù)期的目標(biāo)。
服務(wù)治理是一個長期的過程,制定目標(biāo)持續(xù)優(yōu)化,讓系統(tǒng)更快更穩(wěn)定,為業(yè)務(wù)賦能。