華為內部如何實施微服務架構?基本就靠這5大原則
隨著業務的發展,程式碼量的膨脹和團隊成員的增加,傳統單體式架構的弊端越來越凸顯,嚴重製約了業務的快速創新和敏捷交付。為了解決傳統單體架構面臨的挑戰,先後演進出了SOA服務化架構、RPC框架、分散式服務框架,最後就是當今非常流行的微服務架構。
微服務化架構並非銀彈,它的實施本身就會面臨很多陷阱和挑戰,涉及到設計、開發、測試、部署、執行和運維等各個方面,一旦使用不當,則會導致整個微服務架構改造的效果大打折扣,甚至失敗。
本文從微服務的生命週期全過程,闡述微服務架構的改造如何實施,以及如何避開各種陷阱,提升實施效率。 注:本文內容是華為近幾年來的服務化經驗分享實錄,如果你看的不酸爽,可以直接調到文末報名我們的線下活動,9月華為的同學會做一個線下的workshop,限額80人!
在實施微服務架構改造之前,我們的產品線遇到一個很大挑戰,就是需求的交付週期越來越短,採用的傳統MVC單體架構越來越難滿足特性快速交付和上線的需求。傳統的電信專案,團隊規模往往都非常大,甚至會跨地域。跨團隊、跨地域的分散式協同開發,程式碼的重用和共享是個難題。
例如我們的支付功能需要新增一個限額保護, 短短十幾行程式碼的一個小需求,評估之後竟然需要9個星期才能上線。原因就是限額保護功能需要同時在9個不同的功能模組中修改, 新增900多個測試用例用來做全量的迴歸測試,示例如下:
通過對已有的MVC單體架構進行分析,我們發現主要存在如下幾個問題:
- 研發成本高:程式碼重複率高,需求變更困難,無法滿足新業務快速上線和敏捷交付。
- 測試、部署成本高:業務執行在一個程序中,因此係統中任何程式的改變,都需要對整個系統重新測試並部署。
- 可伸縮性差:水平擴充套件只能基於整個系統進行擴充套件,無法針對某一個功能模組按需擴充套件。
- 可靠性差:某個應用BUG,例如死迴圈、OOM等,會導致整個程序宕機,影響其它合設的應用。
- 程式碼維護成本高:原生代碼在不斷的迭代和變更,最後形成了一個個垂直的功能孤島,只有原來的開發者才理解介面呼叫關係和功能需求,新加入人員或者團隊其它人員很難理解和維護這些程式碼。
- 依賴關係無法有效管理:服務間依賴關係變得錯蹤複雜,甚至分不清哪個應用要在哪個應用之前啟動,架構師都不能完整的描述應用的架構關係。
以上問題的應對策略,就是服務化
首先,需要對業務進行拆分。當業務量大了以後,特別是當不同的功能耦合在一起的時候,任何一個地方的改動都是非常困難的,必須對業務進行拆分,拆分的策略有兩種:
- 橫向拆分。按照不同的業務域進行拆分,例如訂單、商品、庫存、號卡資源等。形成獨立的業務領域微服務叢集。
- 縱向拆分。把一個業務功能裡的不同模組或者元件進行拆分。例如把公共元件拆分成獨立的原子服務,下沉到底層,形成相對獨立的原子服務層。這樣一縱一橫,就可以實現業務的服務化拆分。
其次,要做好微服務的分層:梳理和抽取核心應用、公共應用,作為獨立的服務下沉到核心和公共能力層,逐漸形成穩定的服務中心,使前端應用能更快速的響應多變的市場需求。
完成服務的拆分和分層工作之後,就會涉及到分散式的部署和呼叫。如何透明化、高效的發現服務,需要一個服務註冊中心,通過服務化和訂閱、釋出機制對應用呼叫關係解耦,支援服務的自動註冊和發現。
服務化架構的演進歷史
在實施微服務架構之前,我們一起回顧下服務化架構的演進歷史。
MVC
MVC架構大部分人都用過,它主要用來解決前後端、介面、控制邏輯和業務邏輯分層問題。比較流行的技術堆疊就是Spring + Struts + iBatis(Hibernate)+ Tomcat(JBoss)。
RPC
隨著業務特別是網際網路的發展,業務規模的擴大,模組化逐步成為一種趨勢,此時解決模組之間遠端呼叫的RPC框架應運而生。RPC需要解決模組之間跨程序通訊的問題,不同的團隊開發不同的模組,通過一個RPC框架實現遠端呼叫,RPC框架幫業務把通訊細節給遮蔽掉,但是RPC框架也有自身的缺點。
RPC本身不負責服務化,例如:服務的自動發現不管、服務的應用和釋出不管、服務的運維和治理也不管。沒有透明化、服務化的能力,對整個應用層的侵入還是比較深的。
SOA
SOA服務化架構,企業級資產重用和異構系統間的整合對接,SOA架構的現狀:在傳統企業IT領域,主要是解決異構系統之間的互通和粗粒度的標準化(WebService)。網際網路領域,提供一套高效支撐應用快速開發迭代的服務化架構。例如各個網際網路公司自研或者開源的分散式服務框架。
微服務架構
首先看一下微服務架構的定義:微服務(MSA)是一種架構風格,旨在通過將功能分解到各個離散的服務中以實現對解決方案的解耦。它有如下幾個特徵:
- 小,且只幹一件事情。
- 獨立部署和生命週期管理。
- 異構性
- 輕量級通訊,RPC或者Restful。
微服務架構的實施過程中,首先遇到的最大的難題,就是它的拆分原則。
微服務拆分原則:圍繞業務功能進行垂直和水平拆分。大小粒度是難點,也是團隊爭論的焦點。
不好的實踐
- 以程式碼量作為衡量標準,例如500行以內。
- 拆分的粒度越小越好,例如以單個資源的操作粒度為劃分原則。
建議的原則
- 功能完整性、職責單一性。
- 粒度適中,團隊可接受。
- 迭代演進,非一蹴而就。
- API的版本相容性優先考慮。
程式碼量多少不能作為衡量微服務劃分是否合理的原則,因為我們知道同樣一個服務,功能本身的複雜性不同,程式碼量也不同。還有一點需要重點強調,在專案剛開始的時候,不要期望微服務的劃分一蹴而就。
微服務架構的演進,應該是一個循序漸進的過程。在一個公司、一個專案組,它也需要一個循序漸進的演進過程。一開始劃不好,沒有關係。當演進到一個階段時,微服務的部署、測試和運維等成本都非常低的時候,這對於你的團隊來說就是一個好的微服務。
微服務架構的開發原則
微服務的開發還會面臨依賴滯後的問題。例如:A要做一個身份證號碼校驗,依賴服務提供者B。由於B把身份證號碼校驗服務的開發優先順序排的比較低,無法滿足A的交付時間點。A會面臨要麼等待,要麼自己實現一個身份證號碼校驗功能。
以前單體架構的時候,大家需要什麼,往往喜歡自己寫什麼,這其實是沒有太嚴重的依賴問題。但是到了微服務時代,微服務是一個團隊或者一個小組提供的,這個時候一定沒有辦法在某一個時刻同時把所有的服務都提供出來,“需求實現滯後”是必然存在的。
一個好的實踐策略就是介面先行,語言中立,服務提供者和消費者解耦,並行開發,提升產能。無論有多少個服務,首先需要把介面識別和定義出來,然後雙方基於介面進行契約驅動開發,利用Mock服務提供者和消費者,互相解耦,並行開發,實現依賴解耦。
採用契約驅動開發,如果需求不穩定或者經常變化,就會面臨一個介面契約頻繁變更的問題。對於服務提供者,不能因為擔心介面變更而遲遲不對外提供介面,對於消費者要擁抱變更,而不是抱怨和抵觸。要解決這個問題,一種比較好的實踐就是管理 + 技術雙管齊下:
- 允許介面變更,但是對變更的頻度要做嚴格管控。
- 提供全線上的API文件服務(例如Swagger UI),將離線的API文件轉成全線上、互動式的API文件服務。
- API變更的主動通知機制,要讓所有消費該API的消費者能夠及時感知到API的變更。
- 契約驅動測試,用於對相容性做迴歸測試。
微服務架構的測試原則
微服務開發完成之後需要對其進行測試。微服務的測試包括單元測試、介面測試、整合測試和行為測試等,其中最重要的就是契約測試:
用微服務框架提供的Mock機制,可以分別生成模擬消費者的客戶端測試樁和提供者的服務端測試樁,雙方可以基於Mock測試樁對微服務的介面契約進行測試,雙方都不需要等待對方功能程式碼開發完成,實現了並行開發和測試,提高了微服務的構建效率。基於介面的契約測試還能快速的發現不相容的介面變更,例如修改欄位型別、刪除欄位等。
微服務架構的部署原則
測試完成之後,需要對微服務進行自動化部署。微服務的部署原則:獨立部署和生命週期管理、基礎設施自動化。需要有一套類似於CI/CD的流水線來做基礎設施自動化,具體可以參考Netflix開源的微服務持續交付流水線Spinnaker:
最後一起看下微服務的執行容器:微部署可以部署在Dorker容器、PaaS平臺(VM)或者物理機上。使用Docker部署微服務會帶來很多優先:
- 一致的環境,線上線下環境一致。
- 避免對特定雲基礎設施提供商的依賴。
- 降低運維團隊負擔。
- 高效能接近裸機效能。
- 多租戶。
相比於傳統的物理機部署,微服務可以由PaaS平臺實現微服務自動化部署和生命週期管理。除了部署和運維自動化,微服務雲化之後還可以充分享受到更靈活的資源排程:
- 雲的彈性和敏捷。
- 雲的動態性和資源隔離。
微服務架構的治理原則
微服務部署上線之後,最重要的工作就是服務治理。微服務治理原則:線上治理、實時動態生效。
微服務常用的治理策略:
- 流量控制:動態、靜態流控制。
- 服務降級。
- 超時控制。
- 優先順序排程。
- 流量遷移。
- 呼叫鏈跟蹤和分析。
- 服務路由。
- 服務上線審批、下線通知。
- SLA策略控制。
微服務治理模型如下所示:
最上層是為服務治理的UI介面,提供線上、配置化的治理介面供運維人員使用。SDK層是提供了微服務治理的各種介面,供服務治理Portal呼叫。最下面的就是被治理的微服務叢集,叢集各節點會監聽服務治理的操作去做實時重新整理。例如:修改了流控閾值之後,服務治理服務會把新的流控的閾值刷到服務註冊中心,服務提供者和消費者監聽到閾值變更之後,獲取新的閾值並重新整理到記憶體中,實現實時生效。由於目前服務治理策略資料量不是特別大,所以可以將服務治理的資料放到服務註冊中心(例如etcd/ZooKeeper),沒有必要再單獨做一套。
微服務最佳實踐
介紹完微服務實施之後,下面我們一起學習下微服務的最佳實踐。
服務路由:本地短路策略。關鍵技術點:優先呼叫本JVM內部服務提供者,其次是相同主機或者VM的,最後是跨網路呼叫。通過本地短路,可以避免遠端呼叫的網路開銷,降低服務呼叫時延、提升成功率。原理如下所示:
服務呼叫方式:同步呼叫、非同步呼叫、並行呼叫。一次服務呼叫,通常就意味著會掛一個服務呼叫執行緒。採用非同步呼叫,可以避免執行緒阻塞,提升系統的吞吐量和可靠性。但是在實際專案中非同步呼叫也有一些缺點,導致使用不是特別廣泛:
- 需要寫非同步回撥邏輯,與傳統的介面呼叫使用方式不一致,開發難度大一些。
- 一些場景下需要快取上下文資訊,引入可靠性問題。
並行呼叫適用於多個服務呼叫沒有上下文依賴,邏輯上可以並行處理,類似JDK的Fork/Join, 並行服務呼叫涉及到同步轉非同步、非同步轉同步、結果匯聚等,技術實現難度較大,目前很多服務框架並不支援。採用並行服務呼叫,可以把傳統序列的服務呼叫優化成並行處理,能夠極大的縮短服務呼叫時延。三種服務呼叫方式的原理圖如下:
微服務故障隔離:執行緒級、程序級、容器級、VM級、物理機級等。關鍵技術點:
- 支援服務部署到不同執行緒/執行緒池中。
- 核心服務和非核心服務隔離部署。
- 為了防止執行緒膨脹,支援共享和獨佔兩種執行緒池策略。
談到分散式,就繞不開事務一致性問題:大部分業務可以通過最終一致性來解決,極少部分需要採用強一致性。
具體的策略如下:
- 最終一致性,可以基於訊息中介軟體實現。
- 強一致性,使用TCC框架。服務框架本身不會直接提供“分散式事務”,往往根據實際需要遷入分散式事務框架來支援分散式事務。
微服務的效能三要素:
- I/O模型,這個通常會選用非堵塞的,Java裡面可能用java原生的。
- 執行緒排程模型。
- 序列化方式。
公司內部服務化,對效能要求較高的場景,建議使用非同步非阻塞I/O(Netty) + 二進位制序列化(Thrift壓縮二進位制等) + Reactor執行緒排程模型。
最後我們一起看下微服務的介面相容性原則:技術保障、管理協同。
- 制定並嚴格執行《微服務前向相容性規範》,避免發生不相容修改或者私自修改不通知周邊的情況。
- 介面相容性技術保障:例如Thrift的IDL,支援新增、修改和刪除欄位、欄位定義位置無關性,碼流支援亂序等。
- 持續交付流水線的每日構建和契約化驅動測試,能夠快速識別和發現不相容。