2016-08-23 李林鋒 聊聊架構(gòu)
隨著業(yè)務(wù)的發(fā)展,代碼量的膨脹和團(tuán)隊(duì)成員的增加,傳統(tǒng)單體式架構(gòu)的弊端越來越凸顯,嚴(yán)重制約了業(yè)務(wù)的快速創(chuàng)新和敏捷交付。為了解決傳統(tǒng)單體架構(gòu)面臨的挑戰(zhàn),先后演進(jìn)出了SOA服務(wù)化架構(gòu)、RPC框架、分布式服務(wù)框架,最后就是當(dāng)今非常流行的微服務(wù)架構(gòu)。
微服務(wù)化架構(gòu)并非銀彈,它的實(shí)施本身就會(huì)面臨很多陷阱和挑戰(zhàn),涉及到設(shè)計(jì)、開發(fā)、測(cè)試、部署、運(yùn)行和運(yùn)維等各個(gè)方面,一旦使用不當(dāng),則會(huì)導(dǎo)致整個(gè)微服務(wù)架構(gòu)改造的效果大打折扣,甚至失敗。
本文從微服務(wù)的生命周期全過程,闡述微服務(wù)架構(gòu)的改造如何實(shí)施,以及如何避開各種陷阱,提升實(shí)施效率。 注:本文內(nèi)容是華為近幾年來的服務(wù)化經(jīng)驗(yàn)分享實(shí)錄,如果你看的不酸爽,可以直接調(diào)到文末報(bào)名我們的線下活動(dòng),9月華為的同學(xué)會(huì)做一個(gè)線下的workshop,限額80人!
在實(shí)施微服務(wù)架構(gòu)改造之前,我們的產(chǎn)品線遇到一個(gè)很大挑戰(zhàn),就是需求的交付周期越來越短,采用的傳統(tǒng)MVC單體架構(gòu)越來越難滿足特性快速交付和上線的需求。傳統(tǒng)的電信項(xiàng)目,團(tuán)隊(duì)規(guī)模往往都非常大,甚至?xí)绲赜???鐖F(tuán)隊(duì)、跨地域的分布式協(xié)同開發(fā),代碼的重用和共享是個(gè)難題。
例如我們的支付功能需要新增一個(gè)限額保護(hù), 短短十幾行代碼的一個(gè)小需求,評(píng)估之后竟然需要9個(gè)星期才能上線。原因就是限額保護(hù)功能需要同時(shí)在9個(gè)不同的功能模塊中修改, 新增900多個(gè)測(cè)試用例用來做全量的回歸測(cè)試,示例如下:
通過對(duì)已有的MVC單體架構(gòu)進(jìn)行分析,我們發(fā)現(xiàn)主要存在如下幾個(gè)問題:
研發(fā)成本高:代碼重復(fù)率高,需求變更困難,無法滿足新業(yè)務(wù)快速上線和敏捷交付。
測(cè)試、部署成本高:業(yè)務(wù)運(yùn)行在一個(gè)進(jìn)程中,因此系統(tǒng)中任何程序的改變,都需要對(duì)整個(gè)系統(tǒng)重新測(cè)試并部署。
可伸縮性差:水平擴(kuò)展只能基于整個(gè)系統(tǒng)進(jìn)行擴(kuò)展,無法針對(duì)某一個(gè)功能模塊按需擴(kuò)展。
可靠性差:某個(gè)應(yīng)用BUG,例如死循環(huán)、OOM等,會(huì)導(dǎo)致整個(gè)進(jìn)程宕機(jī),影響其它合設(shè)的應(yīng)用。
代碼維護(hù)成本高:本地代碼在不斷的迭代和變更,最后形成了一個(gè)個(gè)垂直的功能孤島,只有原來的開發(fā)者才理解接口調(diào)用關(guān)系和功能需求,新加入人員或者團(tuán)隊(duì)其它人員很難理解和維護(hù)這些代碼。
依賴關(guān)系無法有效管理:服務(wù)間依賴關(guān)系變得錯(cuò)蹤復(fù)雜,甚至分不清哪個(gè)應(yīng)用要在哪個(gè)應(yīng)用之前啟動(dòng),架構(gòu)師都不能完整的描述應(yīng)用的架構(gòu)關(guān)系。
以上問題的應(yīng)對(duì)策略,就是服務(wù)化。
首先,需要對(duì)業(yè)務(wù)進(jìn)行拆分。當(dāng)業(yè)務(wù)量大了以后,特別是當(dāng)不同的功能耦合在一起的時(shí)候,任何一個(gè)地方的改動(dòng)都是非常困難的,必須對(duì)業(yè)務(wù)進(jìn)行拆分,拆分的策略有兩種:
橫向拆分。按照不同的業(yè)務(wù)域進(jìn)行拆分,例如訂單、商品、庫(kù)存、號(hào)卡資源等。形成獨(dú)立的業(yè)務(wù)領(lǐng)域微服務(wù)集群。
縱向拆分。把一個(gè)業(yè)務(wù)功能里的不同模塊或者組件進(jìn)行拆分。例如把公共組件拆分成獨(dú)立的原子服務(wù),下沉到底層,形成相對(duì)獨(dú)立的原子服務(wù)層。這樣一縱一橫,就可以實(shí)現(xiàn)業(yè)務(wù)的服務(wù)化拆分。
其次,要做好微服務(wù)的分層:梳理和抽取核心應(yīng)用、公共應(yīng)用,作為獨(dú)立的服務(wù)下沉到核心和公共能力層,逐漸形成穩(wěn)定的服務(wù)中心,使前端應(yīng)用能更快速的響應(yīng)多變的市場(chǎng)需求。
完成服務(wù)的拆分和分層工作之后,就會(huì)涉及到分布式的部署和調(diào)用。如何透明化、高效的發(fā)現(xiàn)服務(wù),需要一個(gè)服務(wù)注冊(cè)中心,通過服務(wù)化和訂閱、發(fā)布機(jī)制對(duì)應(yīng)用調(diào)用關(guān)系解耦,支持服務(wù)的自動(dòng)注冊(cè)和發(fā)現(xiàn)。
服務(wù)化架構(gòu)的演進(jìn)歷史
在實(shí)施微服務(wù)架構(gòu)之前,我們一起回顧下服務(wù)化架構(gòu)的演進(jìn)歷史。
MVC
MVC架構(gòu)大部分人都用過,它主要用來解決前后端、界面、控制邏輯和業(yè)務(wù)邏輯分層問題。比較流行的技術(shù)堆棧就是Spring + Struts + iBatis(Hibernate)+ Tomcat(JBoss)。
RPC
隨著業(yè)務(wù)特別是互聯(lián)網(wǎng)的發(fā)展,業(yè)務(wù)規(guī)模的擴(kuò)大,模塊化逐步成為一種趨勢(shì),此時(shí)解決模塊之間遠(yuǎn)程調(diào)用的RPC框架應(yīng)運(yùn)而生。RPC需要解決模塊之間跨進(jìn)程通信的問題,不同的團(tuán)隊(duì)開發(fā)不同的模塊,通過一個(gè)RPC框架實(shí)現(xiàn)遠(yuǎn)程調(diào)用,RPC框架幫業(yè)務(wù)把通信細(xì)節(jié)給屏蔽掉,但是RPC框架也有自身的缺點(diǎn)。
RPC本身不負(fù)責(zé)服務(wù)化,例如:服務(wù)的自動(dòng)發(fā)現(xiàn)不管、服務(wù)的應(yīng)用和發(fā)布不管、服務(wù)的運(yùn)維和治理也不管。沒有透明化、服務(wù)化的能力,對(duì)整個(gè)應(yīng)用層的侵入還是比較深的。
SOA
SOA服務(wù)化架構(gòu),企業(yè)級(jí)資產(chǎn)重用和異構(gòu)系統(tǒng)間的集成對(duì)接,SOA架構(gòu)的現(xiàn)狀:在傳統(tǒng)企業(yè)IT領(lǐng)域,主要是解決異構(gòu)系統(tǒng)之間的互通和粗粒度的標(biāo)準(zhǔn)化(WebService)。互聯(lián)網(wǎng)領(lǐng)域,提供一套高效支撐應(yīng)用快速開發(fā)迭代的服務(wù)化架構(gòu)。例如各個(gè)互聯(lián)網(wǎng)公司自研或者開源的分布式服務(wù)框架。
微服務(wù)架構(gòu)
首先看一下微服務(wù)架構(gòu)的定義:微服務(wù)(MSA)是一種架構(gòu)風(fēng)格,旨在通過將功能分解到各個(gè)離散的服務(wù)中以實(shí)現(xiàn)對(duì)解決方案的解耦。它有如下幾個(gè)特征:
小,且只干一件事情。
獨(dú)立部署和生命周期管理。
異構(gòu)性
輕量級(jí)通信,RPC或者Restful。
微服務(wù)架構(gòu)的拆分原則
微服務(wù)架構(gòu)的實(shí)施過程中,首先遇到的最大的難題,就是它的拆分原則。
微服務(wù)拆分原則:圍繞業(yè)務(wù)功能進(jìn)行垂直和水平拆分。大小粒度是難點(diǎn),也是團(tuán)隊(duì)爭(zhēng)論的焦點(diǎn)。
不好的實(shí)踐
以代碼量作為衡量標(biāo)準(zhǔn),例如500行以內(nèi)。
拆分的粒度越小越好,例如以單個(gè)資源的操作粒度為劃分原則。
建議的原則
功能完整性、職責(zé)單一性。
粒度適中,團(tuán)隊(duì)可接受。
迭代演進(jìn),非一蹴而就。
API的版本兼容性優(yōu)先考慮。
代碼量多少不能作為衡量微服務(wù)劃分是否合理的原則,因?yàn)槲覀冎劳瑯右粋€(gè)服務(wù),功能本身的復(fù)雜性不同,代碼量也不同。還有一點(diǎn)需要重點(diǎn)強(qiáng)調(diào),在項(xiàng)目剛開始的時(shí)候,不要期望微服務(wù)的劃分一蹴而就。
微服務(wù)架構(gòu)的演進(jìn),應(yīng)該是一個(gè)循序漸進(jìn)的過程。在一個(gè)公司、一個(gè)項(xiàng)目組,它也需要一個(gè)循序漸進(jìn)的演進(jìn)過程。一開始劃不好,沒有關(guān)系。當(dāng)演進(jìn)到一個(gè)階段時(shí),微服務(wù)的部署、測(cè)試和運(yùn)維等成本都非常低的時(shí)候,這對(duì)于你的團(tuán)隊(duì)來說就是一個(gè)好的微服務(wù)。
微服務(wù)架構(gòu)的開發(fā)原則
微服務(wù)的開發(fā)還會(huì)面臨依賴滯后的問題。例如:A要做一個(gè)身份證號(hào)碼校驗(yàn),依賴服務(wù)提供者B。由于B把身份證號(hào)碼校驗(yàn)服務(wù)的開發(fā)優(yōu)先級(jí)排的比較低,無法滿足A的交付時(shí)間點(diǎn)。A會(huì)面臨要么等待,要么自己實(shí)現(xiàn)一個(gè)身份證號(hào)碼校驗(yàn)功能。
以前單體架構(gòu)的時(shí)候,大家需要什么,往往喜歡自己寫什么,這其實(shí)是沒有太嚴(yán)重的依賴問題。但是到了微服務(wù)時(shí)代,微服務(wù)是一個(gè)團(tuán)隊(duì)或者一個(gè)小組提供的,這個(gè)時(shí)候一定沒有辦法在某一個(gè)時(shí)刻同時(shí)把所有的服務(wù)都提供出來,“需求實(shí)現(xiàn)滯后”是必然存在的。
一個(gè)好的實(shí)踐策略就是接口先行,語言中立,服務(wù)提供者和消費(fèi)者解耦,并行開發(fā),提升產(chǎn)能。無論有多少個(gè)服務(wù),首先需要把接口識(shí)別和定義出來,然后雙方基于接口進(jìn)行契約驅(qū)動(dòng)開發(fā),利用Mock服務(wù)提供者和消費(fèi)者,互相解耦,并行開發(fā),實(shí)現(xiàn)依賴解耦。
采用契約驅(qū)動(dòng)開發(fā),如果需求不穩(wěn)定或者經(jīng)常變化,就會(huì)面臨一個(gè)接口契約頻繁變更的問題。對(duì)于服務(wù)提供者,不能因?yàn)閾?dān)心接口變更而遲遲不對(duì)外提供接口,對(duì)于消費(fèi)者要擁抱變更,而不是抱怨和抵觸。要解決這個(gè)問題,一種比較好的實(shí)踐就是管理 + 技術(shù)雙管齊下:
允許接口變更,但是對(duì)變更的頻度要做嚴(yán)格管控。
提供全在線的API文檔服務(wù)(例如Swagger UI),將離線的API文檔轉(zhuǎn)成全在線、互動(dòng)式的API文檔服務(wù)。
API變更的主動(dòng)通知機(jī)制,要讓所有消費(fèi)該API的消費(fèi)者能夠及時(shí)感知到API的變更。
契約驅(qū)動(dòng)測(cè)試,用于對(duì)兼容性做回歸測(cè)試。
微服務(wù)架構(gòu)的測(cè)試原則
微服務(wù)開發(fā)完成之后需要對(duì)其進(jìn)行測(cè)試。微服務(wù)的測(cè)試包括單元測(cè)試、接口測(cè)試、集成測(cè)試和行為測(cè)試等,其中最重要的就是契約測(cè)試:
利用微服務(wù)框架提供的Mock機(jī)制,可以分別生成模擬消費(fèi)者的客戶端測(cè)試樁和提供者的服務(wù)端測(cè)試樁,雙方可以基于Mock測(cè)試樁對(duì)微服務(wù)的接口契約進(jìn)行測(cè)試,雙方都不需要等待對(duì)方功能代碼開發(fā)完成,實(shí)現(xiàn)了并行開發(fā)和測(cè)試,提高了微服務(wù)的構(gòu)建效率?;诮涌诘钠跫s測(cè)試還能快速的發(fā)現(xiàn)不兼容的接口變更,例如修改字段類型、刪除字段等。
微服務(wù)架構(gòu)的部署原則
測(cè)試完成之后,需要對(duì)微服務(wù)進(jìn)行自動(dòng)化部署。微服務(wù)的部署原則:獨(dú)立部署和生命周期管理、基礎(chǔ)設(shè)施自動(dòng)化。需要有一套類似于CI/CD的流水線來做基礎(chǔ)設(shè)施自動(dòng)化,具體可以參考Netflix開源的微服務(wù)持續(xù)交付流水線Spinnaker:
最后一起看下微服務(wù)的運(yùn)行容器:微部署可以部署在Dorker容器、PaaS平臺(tái)(VM)或者物理機(jī)上。使用Docker部署微服務(wù)會(huì)帶來很多優(yōu)先:
一致的環(huán)境,線上線下環(huán)境一致。
避免對(duì)特定云基礎(chǔ)設(shè)施提供商的依賴。
降低運(yùn)維團(tuán)隊(duì)負(fù)擔(dān)。
高性能接近裸機(jī)性能。
多租戶。
相比于傳統(tǒng)的物理機(jī)部署,微服務(wù)可以由PaaS平臺(tái)實(shí)現(xiàn)微服務(wù)自動(dòng)化部署和生命周期管理。除了部署和運(yùn)維自動(dòng)化,微服務(wù)云化之后還可以充分享受到更靈活的資源調(diào)度:
云的彈性和敏捷。
云的動(dòng)態(tài)性和資源隔離。
微服務(wù)架構(gòu)的治理原則
微服務(wù)部署上線之后,最重要的工作就是服務(wù)治理。微服務(wù)治理原則:線上治理、實(shí)時(shí)動(dòng)態(tài)生效。
微服務(wù)常用的治理策略:
流量控制:動(dòng)態(tài)、靜態(tài)流控制。
服務(wù)降級(jí)。
超時(shí)控制。
優(yōu)先級(jí)調(diào)度。
流量遷移。
調(diào)用鏈跟蹤和分析。
服務(wù)路由。
服務(wù)上線審批、下線通知。
SLA策略控制。
微服務(wù)治理模型如下所示:
最上層是為服務(wù)治理的UI界面,提供在線、配置化的治理界面供運(yùn)維人員使用。SDK層是提供了微服務(wù)治理的各種接口,供服務(wù)治理Portal調(diào)用。最下面的就是被治理的微服務(wù)集群,集群各節(jié)點(diǎn)會(huì)監(jiān)聽服務(wù)治理的操作去做實(shí)時(shí)刷新。例如:修改了流控閾值之后,服務(wù)治理服務(wù)會(huì)把新的流控的閾值刷到服務(wù)注冊(cè)中心,服務(wù)提供者和消費(fèi)者監(jiān)聽到閾值變更之后,獲取新的閾值并刷新到內(nèi)存中,實(shí)現(xiàn)實(shí)時(shí)生效。由于目前服務(wù)治理策略數(shù)據(jù)量不是特別大,所以可以將服務(wù)治理的數(shù)據(jù)放到服務(wù)注冊(cè)中心(例如etcd/ZooKeeper),沒有必要再單獨(dú)做一套。
微服務(wù)最佳實(shí)踐
介紹完微服務(wù)實(shí)施之后,下面我們一起學(xué)習(xí)下微服務(wù)的最佳實(shí)踐。
服務(wù)路由:本地短路策略。關(guān)鍵技術(shù)點(diǎn):優(yōu)先調(diào)用本JVM內(nèi)部服務(wù)提供者,其次是相同主機(jī)或者VM的,最后是跨網(wǎng)絡(luò)調(diào)用。通過本地短路,可以避免遠(yuǎn)程調(diào)用的網(wǎng)絡(luò)開銷,降低服務(wù)調(diào)用時(shí)延、提升成功率。原理如下所示:
服務(wù)調(diào)用方式:同步調(diào)用、異步調(diào)用、并行調(diào)用。一次服務(wù)調(diào)用,通常就意味著會(huì)掛一個(gè)服務(wù)調(diào)用線程。采用異步調(diào)用,可以避免線程阻塞,提升系統(tǒng)的吞吐量和可靠性。但是在實(shí)際項(xiàng)目中異步調(diào)用也有一些缺點(diǎn),導(dǎo)致使用不是特別廣泛:
需要寫異步回調(diào)邏輯,與傳統(tǒng)的接口調(diào)用使用方式不一致,開發(fā)難度大一些。
一些場(chǎng)景下需要緩存上下文信息,引入可靠性問題。
并行調(diào)用適用于多個(gè)服務(wù)調(diào)用沒有上下文依賴,邏輯上可以并行處理,類似JDK的Fork/Join, 并行服務(wù)調(diào)用涉及到同步轉(zhuǎn)異步、異步轉(zhuǎn)同步、結(jié)果匯聚等,技術(shù)實(shí)現(xiàn)難度較大,目前很多服務(wù)框架并不支持。采用并行服務(wù)調(diào)用,可以把傳統(tǒng)串行的服務(wù)調(diào)用優(yōu)化成并行處理,能夠極大的縮短服務(wù)調(diào)用時(shí)延。三種服務(wù)調(diào)用方式的原理圖如下:
微服務(wù)故障隔離:線程級(jí)、進(jìn)程級(jí)、容器級(jí)、VM級(jí)、物理機(jī)級(jí)等。關(guān)鍵技術(shù)點(diǎn):
支持服務(wù)部署到不同線程/線程池中。
核心服務(wù)和非核心服務(wù)隔離部署。
為了防止線程膨脹,支持共享和獨(dú)占兩種線程池策略。
談到分布式,就繞不開事務(wù)一致性問題:大部分業(yè)務(wù)可以通過最終一致性來解決,極少部分需要采用強(qiáng)一致性。
具體的策略如下:
最終一致性,可以基于消息中間件實(shí)現(xiàn)。
強(qiáng)一致性,使用TCC框架。服務(wù)框架本身不會(huì)直接提供“分布式事務(wù)”,往往根據(jù)實(shí)際需要遷入分布式事務(wù)框架來支持分布式事務(wù)。
微服務(wù)的性能三要素:
I/O模型,這個(gè)通常會(huì)選用非堵塞的,Java里面可能用java原生的。
線程調(diào)度模型。
序列化方式。
公司內(nèi)部服務(wù)化,對(duì)性能要求較高的場(chǎng)景,建議使用異步非阻塞I/O(Netty) + 二進(jìn)制序列化(Thrift壓縮二進(jìn)制等) + Reactor線程調(diào)度模型。
最后我們一起看下微服務(wù)的接口兼容性原則:技術(shù)保障、管理協(xié)同。
制定并嚴(yán)格執(zhí)行《微服務(wù)前向兼容性規(guī)范》,避免發(fā)生不兼容修改或者私自修改不通知周邊的情況。
接口兼容性技術(shù)保障:例如Thrift的IDL,支持新增、修改和刪除字段、字段定義位置無關(guān)性,碼流支持亂序等。
持續(xù)交付流水線的每日構(gòu)建和契約化驅(qū)動(dòng)測(cè)試,能夠快速識(shí)別和發(fā)現(xiàn)不兼容。
作者介紹
李林鋒,2007年畢業(yè)于東北大學(xué),2008年加入華為,從事電信軟件的架構(gòu)設(shè)計(jì)和開發(fā)。8年Java NIO通信框架、網(wǎng)關(guān)平臺(tái)和中間件設(shè)計(jì)和開發(fā)經(jīng)驗(yàn),精通Java NIO、Netty和Mina等NIO通信框架,《Netty權(quán)威指南》作者,目前從事云平臺(tái)相關(guān)的架構(gòu)設(shè)計(jì)和開發(fā)。