成功微服務實施的技術演進
在上一篇文章 ofollow,noindex">《我們如何衡量一個微服務實施的成功》 裡,我們介紹了衡量一個微服務改造成功的七個特徵,分別是:
- 很多個程式碼庫,以及一一對應的流水線。
- 應用可以隨時部署,並不需要等待。
- 大量的自動化測試。
- 更少的變更事故。
- 更低的釋出風險。
- 可以按需擴充套件。
- 更多的自動化手段。
而本篇文章所介紹的案例,也符合這篇文章中對“微服務實施成功”的定義。不過,我們將通過以下五個方面來介紹我們是如何做到達到這七點的:
- 通過度量驅動架構的微服務化;
- 微服務平臺的演進;
- 資料庫的獨立演進;
- 服務間的輕量級通訊;
- 微服務的全鏈路跟蹤;
微服務演進的技術背景
2013年,當我加入這個“微服務改造”專案中的時候,微服務遠沒有像今天這麼火。那個時候我還不知道這種架構演進的方式叫做“微服務”。直到我離開這個專案把其中的經驗帶到其它專案裡,才對敏捷,DevOps和微服務有了進一步的認識。
當時,我們剛剛協助客戶把應用程式從自建資料中心遷移到亞馬遜雲端計算服務(AWS)上,並通過 DevOps 等實踐做到了按月釋出。然而,新的挑戰接踵而至。當客戶決定開始做微服務之前,遇到了以下三點問題:
- 運維風險高,釋出的時候需要整體釋出。除了累積了應用變更以外,還有基礎設施的變更。
- 開發效率低,由於單體應用儲存在一個程式碼庫裡。導致各功能,專案,維護團隊之間產生依賴,交付效率很低。
- 內部多個應用系統之間需要整合,但缺乏單一可信資料來源(Single Source of Truth)。
作為很早就採用敏捷方式開發的企業來說,該企業很多敏捷實踐都做的非常成熟,並往往作為澳大利亞敏捷成功的案例標杆。在我加入的時候,客戶已經採用持續整合很長時間了。而遷移到 AWS,還需要將部署和運維部分自動化,從技術層面為 DevOps 做了很好的準備。那時候我們所依賴的仍然是用 Chef 去構建自動化的指令碼進行部署,並開始採用 Ansible 這種技術做釋出的標準化。
通過度量驅動架構的微服務化
我們所擁有的是一個基於 Spring 2.5 的 Java 遺留系統,各個系統之間由 ESB (Enterprise Service Bus 企業服務匯流排)串聯起來。多個不同的業務線(Line of Business,LoB)擁有各自獨立的產品元件,但都是基於同一套程式碼庫。
這樣的痛點很明顯:
- 每個業務線都要有自己的子產品,但大家都基於同一份程式碼庫。
- 每個業務線對自己產品的改動,會影響到其它的系統。
- 由於不同的系統的元件依賴於不同的環境和不同的資料庫,所以部署所帶來的風險很高。
隨著開發人員的不斷增加,以上的痛點越來越明顯,我們發現很多工作因為開發阻塞而無法前行。於是就有了一個最基礎的度量:釋出阻塞時間。
當我們把敏捷看板構建起來,我們可以很清楚的看到需求分析、開發、測試的各環節時間。當時並沒有採用 DevOps,我們的持續釋出也僅限於 Staging(準生產環境),而各個環節內可以採用更具有生產力的實踐我們可以縮短環節時間,降低浪費。但,阻塞時間則隨著需求的增加而增加。
當阻塞時間在上漲的時候,主觀的組織規劃已經和應用系統規劃不符了。於是,產品則根據業務線被劃分成了三個產品,如下圖所示:

產品根據業務線拆分
於是有了三個程式碼庫,和三條不同的流水線。每個業務線都負責構建自己的程式碼庫和周邊生態。這樣雖然會帶來程式碼的重複,讓很多有 DRY(Don't Repeat Yourself )癖的架構師難以接受。但畢竟各產品未來要走自己的路,因此,為了讓各業務線不阻塞,各自採用各自的程式碼庫進行釋出。於是原先的團隊隨著程式碼庫的分離而分隔成了不同的團隊。但是,Ops 團隊卻沒有分隔開,而是作為通用能力繼續支援著各產品線的發展。
這也就是康威定律所說的:“設計系統的組織,其產生的設計等同於組織之內、組織之間的溝通結構。”
這一次的拆分嚐到了甜頭,除了各個業務線開發阻塞時間縮短以外,各個業務線的產品的釋出失敗率和故障率也降低了。於是我們繼續採用工具提升釋出的成功率和效率,直到我們發現我們系統裡的 ESB 成為了我們的瓶頸。於是我們開始進行了微服務的拆分。
我們預期的策略是採用“拆遷者模式”:即新建一套子系統,再統一的進行遷移,一步到位。能夠這樣做的前提是要有足夠的自動化測試覆蓋當前所有的業務場景。於是我們根據我們需要拆分的功能先編寫自動化測試,在自動化測試通過的情況下可以保障我新編寫的程式碼不會影響現有的功能,包括資料遷移後的測試。
然而,拆遷者模式最大的挑戰來自於切換風險,為了避免切換造成的風險就要補全自動化測試,這樣的成本是巨大的。除非很早就開始做好了責任獨立的設計。否則,不要用拆遷者模式。
另外兩種模式就是“絞殺者模式”和“修繕者模式”。前者有一個別名,叫做“停止挖坑”,意思就是不要在當前的系統裡繼續增加功能,而是採用鬆耦合的方式增加新的功能,使得老的功能慢慢被絞殺掉。這種模式的前提就是要確認遺留系統不再進行功能新增,只做 Bug 修復和例行維護。這樣帶來的變更風險最小,但演進時間較長。對於“新遺留系統”——剛剛開始轉入維護不到半年的新系統,可以採用這種方式。
然而,我們所碰到的應用系統則是一堆執行時間超過5年的遺留系統。於是我們採用了“修繕者模式”。修繕者模式源於古老的軟體工程格言:“任何問題都可以通過增加一箇中間層解決”。
我們首先做了一個前後端分離,採用 RESTful API 作為後臺,向 PC 瀏覽器和手機 App 提供資料互動。這樣,無需為移動應用單獨編寫後臺應用,只需要複用之前寫好的 API 就可以了。這就是當前很多應用進行微服務改造的第一步。
到了後期,我們發現有些需要很多 API 需要進行轉化,所以我們當時做了一個叫做 Syndication API 的東西,它實際上一個一個 API 的集合。通過反向代理重新暴露後端的資料和介面。這當時是為了能夠給 Mobile 端提供資料所準備的過度方案。所以我們採用了 Ruby 這種可以快速開發的語言(講真,Ruby 開發的專案都不大好維護,一部分原因是 Ruby 程式設計師水平差異太大,另一部分原因是 Ruby 版本和各元件更新的問題)。後來我們發現,完成一個功能需要給 Web 和 Mobile 端做重複的開發。所以,我們決定在前後端分離的基礎上逐漸替換掉老的 Web 應用,即便它執行的很穩定。
為了降低風險,我們就再一次對 Syndication API 進行了拆分。把一個單體 API 集合根據 LoB 功能的依賴程度拆分成了 API 的組。這樣,我們可以在使用者端無感知的情況下修改後臺的部署架構。這時候,雖然沒有做到微服務,但我們通過各自的 API 分離出來了完整的業務並在遺留系統之間建立了一個適配層隔離風險。以後我們只需要寫程式碼替代原有的邏輯就可以了。
這樣,我們構建一個防腐層,將微服務和遺留系統隔離開。對新的微服務化元件採用獨立的程式碼庫,進行持續交付和持續部署。那個時候沒有 Docker 這樣成熟的技術,也沒有 Spring 全家桶這麼便利的框架。所以我們選擇了 Ruby 和 Ansible 進行開發和部署。大體過程如下:
applicationContext.xml
選擇模組也有講究,有以下幾個策略:
- 前後端拆分
- 把經常變更的部分拆分
- 把公共的部分進行拆分
- 根據業務拆分
- 根據領域模型拆分
當時我們是根據系統選單的導航進行拆分的,因為使用者選單本身就是分割好的業務。這是大粒度的拆分,然後就可以定下原則,針對不同的技術特點和運維特點進行小粒度的拆分。
此外,在選擇拆分策略的過程中,我們涉及兩方面的估算(度量),一方面是成本,一方面是收益。
成本里除了人員的成本以外,還包括風險。在度量風險之前,要問這幾個問題:
- 假設我們的拆分一定會失敗,這個失敗會帶來多大的影響?
- 假設我們要修復,成本最低的修復方案會花多少成本。
這裡的影響我們可以把“故障範圍”、“故障時間”、“單位範圍故障成本” 三者相乘得出來一個估算值。然後再加上微服務的開發成本和回退成本三個部分,構成微服務開發的總成本。這種演算法雖然簡單粗暴,但也能說明問題。
而微服務帶來的收益,則是將上文度量的阻塞時間(以人天為單位),乘以研發人數和系統執行時間構建出來的一個函式。而這個函式也可以說明微服務帶來的系統規模增長的投資回報週期。在不同的團隊裡,這個回報週期都是不一樣的。一般都在 6 個月以上。也就是說,投入微服務拆分後 6 個月,微服務所帶來的投資產出才會持平。
“重要的不是技術有多先進,而在於你清醒的認識到新技術引入帶來的成本和收益。”
有了以上幾方面的度量,我們就對相應系統和子系統是否要進行微服務有了一個相對清晰的認識。並不是所有的微服務拆分都划算,有些系統保持原樣,採用絞殺者模式慢慢遷移可能更好。如果採用拆遷者模式,急功近利帶來的問題可能更多。所以,一個成熟的微服務架構應該是一個混合型的系統,如下圖所示:

多語言架構
上圖 是一個分層架構,最上面一層是前臺,其次是後臺,後臺之間的不同微服務也採用了不同的語言。有些微服務則僅僅是一個對外的 API,有些系統並未轉化成微服務。例如上圖左側的 Monolith,就是一個 Java 的單塊應用。其中MS 代表 Microservice。
我遇到的很多想採用微服務的團隊往往糾結於自己“是不是”一個微服務架構。但我覺得你可以在架構中先“有一個微服務”,看看它帶來的投入和回報,再考慮如何擴充套件自己的微服務。
微服務平臺
在微服務的演進道路中,隨著微服務的數量增加,微服務的治理成為了一個突出的問題。而隨著技術的進步,微服務的治理思想和工具也發生了變化。我們的微服務演進經歷了以下四個階段:
- 階段一:自部署的生命週期管理工具
- 階段二:公共的微服務管理工具
- 階段三:基於 Docker 容器的微服務管理工具
- 階段四:基於 容器平臺的微服務管理工具
階段一:自部署的生命週期管理工具(2013年之前)
在微服務實踐早期的時候,我們希望我們新部署的應用可以沒有任何依賴。再加上 DevOps 實踐的影響,我們期望每個微服務團隊都是一個全功能的 DevOps 團隊。
我們會在程式碼庫裡建立一個 deploy 資料夾,把自動化的部署指令碼放在裡面,整合一些 APM 工具和日誌收集代理。並根據不同的環境構建配置檔案。利用基礎設施即程式碼技術自動化完成,並且整合到持續部署流水線中進行管理。那時候並沒有 Docker 這樣方便的技術。於是,我們採用一個程式碼庫,一條流水線,一個虛擬機器映象的方式進行部署。
這時候微服務的實踐還有一個重要的點是去容器化,這裡的容器指的是像 WebLogic 和 Tomcat 這樣的 Java 應用容器,而不是 Docker 這樣的容器技術。對於 Java 來說,當時可以選擇 DropWizard,它可以把 Web 應用打成一個可執行的 JAR 包執行,現在的做法就是用 Spring Boot。也可以選擇 Jetty,用 Gradle 把它整合到 build 檔案裡,作為一個任務執行。如果你是 Ruby,就可以用 Ruby On Rails 或者 Sinatra 這樣的框架來執行。虛擬機器映象裡只需要安裝一個語言執行時和固定的應用啟動點就可以了。
這樣就做到了獨立開發、獨立部署。降低了應用複雜度並減少了以來,這可以使得團隊更加高產。
階段二:公共的微服務管理工具(2013年——2014年)
隨著微服務數量的增長,你會發現每個微服務工程都有同樣的基礎設施管理部分。於是我們通過程式碼重構,把公共的部分提取出來,變成了一個公共的微服務管理工具,它支援微服務的全生命週期管理。這樣的工具一般是用 Gradle (Java應用)或者 Rake (Ruby 應用)來構建的。他們可以做到微服務從構建(build)到部署(deployment)的一系列任務。
通用的管理工具的另外一面就是應用的開發規範。兩方面必須同時存在,否則這樣的工具會失去意義。所以,微服務管理平臺的意義在於不光能夠節約很多重複建設的成本,更能夠將最佳實踐變成一種制度,進行快速複製和推廣。
你可能會有疑問,微服務本質上是要做去中心化的。但這麼做不就催生了新的中心化嗎?
這個問題,對了一半。關鍵的部分在於工具和應用的依賴程度。你可以把這一類工具單獨當做一個特殊的“微服務”來對待:它應該和其它應用之間鬆耦合的,提供所有應用都共享的功能,但必須要和業務部分獨立開,無侵入性的和微服務應用程式組合在一起。
催生這個工具誕生的另外一個原因就是組織記憶體在獨立且共享的運維團隊。大部分的企業應用系統的開發和執行平臺維護是不同的兩個團隊,這就導致了開發和運維的分離,這樣不利於 DevOps 的組織形成。然而,在我的課程《DevOps 轉型實戰中》,我介紹了這種組織形式下實踐 DevOps 的方式,就是讓運維團隊的成員去不同的開發團隊內“輪崗”:讓每一個微服務團隊在改造的過程中成為一個 DevOps 團隊,作為一個運維團隊的使者。在改造結束後迴歸到運維團隊,將經驗分享並帶給其它的開發團隊,這樣可以減少組織中的運維浪費。
然而,把業務程式碼和運維程式碼解耦並不是一件很容易的事情,當時也是微服務推廣的一個技術難點,困擾了很多人。直到 Docker 的出現。
階段三:基於 Docker 的微服務管理工具(2014年——2016年)
Docker 成為了 DevOps 和微服務事實上的推手。可以說,沒有 DevOps 的實踐和 Docker 這個工具,就沒有今天微服務的流行。
作為最早將 Docker 應用到生產環境的團隊,我們最初僅僅是把 Docker 作為一個“輕量級的虛擬機器”(Light Weight Virtual Machine)來看。它可以快速的構建一個乾淨、穩定的執行時環境(Runtime Environment)並且做到快速啟動和水平擴充套件,十分讓人興奮。
於是,我們把每個應用都通過 Docker 封裝。由於那時候沒有 docker-compose(其前身是一個名為 fig 的工具)和 k8s 的管理平臺。也只能用 Shell 指令碼來管理,然而 Shell 指令碼的結構化能力有限,於是我們的團隊的一個運維工程師在週末用 Python 自己擼了一個管理工具出來。那時是我們能找到的唯一容器編排工具,可惜作為最早的 Docker 編排工具沒有開源。於是就有了 fig 這樣相同的工具出現。隨著 Docker 後期再開源社群的一系列收購工作,Docker 的開發話語權慢慢被 Docker 公司回收,這都是後話。
我們把前端程式碼和後端程式碼封裝起來,通過 docker 的 link 功能構建了統一的內部 API 接入點。這樣就可以減少自動化部署程式碼中不同環境之間的配置。也可以很輕易的把運維和開發解耦,降低 DevOps 團隊中的溝通成本。
然而這裡有個非常不好的實踐就是不停的構建 Docker 映象。從運維的角度說,不斷的構建 Docker 映象會導致不必要的網路流量和儲存資源,特別是很多按流量和空間付費的雲端計算服務。另一方面,構建映象會增加部署流程時間,雖然 Docker 的構建和下載會啟用快取,但是一些基礎映象的變更就會帶來所有映象的重新構建。
這就是容器的狀態化,狀態化的容器不算是一個好的實踐。但是,這也比沒有容器之前的狀態要好很多。
所以,我推薦把 Docker 容器當做一個穩定的執行時,不要頻繁的構建映象。通過 Volume 引數將宿主機中的檔案掛載到容器裡,這就是將 Docker 映象裡的狀態移除,做到容器的無狀態化。這樣,容器映象會穩定且高效。
階段四:基於容器平臺的微服務平臺(2015年至今)
當微服務通過 Docker 承載之後,微服務藉由 Docker 的快速擴充套件和運維隔離兩項優勢快速發展起來。但同時也帶來了很多問題。就像我在前文中說的,微服務本質上把應用的內部複雜性轉化成外部複雜性。並且用具備彈性的基礎基礎設施來承載外部複雜性。很多公共元件,例如微服務的註冊和發現、一致性、日誌、APM等慢慢的容器化。包括容器的編排和資源伸縮等基礎能力、以及資料庫等都用容器化統一起來。
這樣,容器就成為了統一的抽象,作為所有應用的 通用執行連結格式 (Generic ELF,Executable and Linkable Format )和 通用執行時 (Generic Runtime)。
這也讓我們從“胖程序”(Rich Process)的角度來重新看待容器技術,和“輕量級虛擬機器”不同。我們把 Dockerfile 看做是語言無關的原始碼,Docker build 看做是編譯和連結,Docker 映象看做是構建出的可執行檔案,Docker 容器看做是程序。
到了這個階段,容器作為一等公民,就需要一個作業系統。而且,這種作業系統是跨平臺的。無論是物理機,虛擬機器還是雲端計算例項,都可以無縫的和容器進行整合,這時候就需要統一的容器解決方案。
於是,三大容器編排平臺應運而生,分別是基於 Docker Swarm 的 Docker EE,Mesosphare 和 Kubernetes。當然,Kubernetes 已經作為絕對的贏家,並且在 CNCF 的支援下,形成了完整的微服務生態圈。所以到了現在這個階段,CNCF 的相關技術才是微服務發展的方向,每個工具環環相扣,形成了一個完整的微服務生態。
階段五:標準化的微服務架構參考模型 ?
隨著微服務生態的漸漸成熟,特別是在開源社群和 CNCF 的作用下。微服務的基礎設施基本上已經不可能有太多的創新點,大部分的優秀實踐都被整合成為了開源專案,逐步從 CNCF 孵化畢業。2017年,很多實踐微服務的企業開始構建自己的微服務化產品。未來,微服務的基礎設施會標準化並且將提供更加透明且廉價的雲原生解決方案。隨之而來的是公共領域的應用解決方案,例如使用者管理和登入這樣的基礎元件會第一個被微服務化。
畢竟,我們沒有必要重新發明那麼多輪子……
獨立儲存/混合儲存

資料庫要根據情況拆分
成功的微服務的另一個特徵就是資料庫可以進行拆分和按需擴充套件,這樣你可以獨立維護。但是如果你的資料庫效能足夠好或者你資料庫結構並不是很好,你可以保持這種方式。然而,很多變更頻繁的系統會有很複雜的資料模型,而這樣的資料模型往往是制約應用架構演進的最大瓶頸。特別是,很多關係型資料庫的嚴格結構約束著應用的方式。
當我們通過領域驅動設計重新對應用架構進行劃分後,你會發現資料模型往往是一團糟:存在著很多重複的表和重複的記錄,包括一些臨時的方案,表之間的關係異常複雜,做一點改動都會“牽一髮動全身”。
這時會有兩種解決方案,但無論哪一種方案,都會帶來冗餘資料和資料遷移。但無論如何,都要堅持“單一可信資料來源”,也就是 Single Source of Truth 原則。
第一種方案比較簡單,就是採用 NoSQL 資料庫單獨承載一個或多個微服務的資料訪問請求,例如 MongoDB,CouchDB 等,用 key-value 這種鬆散的結構來儲存資料。並把對資料模型的約束放在應用程式碼裡而不是資料模型的定義裡。這樣就可以更靈活的組織資料,而不用擔心資料在資料庫內的定義。我們往往把經常變更結構的資料存放在 NoSQL 資料庫裡,而穩定的資料結構儲存在關係型資料庫裡,然後進行資料庫遷移。
第二種方案比較複雜,就是做關係型資料庫的遷移。這種比較複雜,但一定要記住一點:不要變更遺留庫!不要變更遺留庫!不要變更遺留庫!重要的話說三遍。
原因很簡單:不值得,特別是在一個經歷了不同架構師和開發人員流動的遺留系統上。你會發現你在變更的時候會遇到多方的阻力,而且驗證方案特別費時費力。這時候如果不停勸阻強硬執行,只會“大力出悲劇”。
這時候,我們只需要將應用和對應的資料版本化。為新應用建立一個數據庫,某個時間點之後的資料,全部進入新庫,老資料庫相對穩定,只讀不寫。這時候無論資料庫和程式碼,一定會有冗餘資料,但不要緊,你只要確定新庫是最終的單一可信資料來源即可。你可以增加增加新功能,讓使用者自己做資料遷移,遷移後要把老資料庫中對應的資料刪除,或者打標記。經過一段時間後,剩下的遺留資料就會越來越少,這就是資料庫遷移的“絞殺者模式”。
另外一種是資料庫遷移的“修繕者模式”:先構建一個新的 API,將它對應的資料結構作為一個數據模型進行直接查詢和儲存,如果查詢不到,則到老的資料庫中進行查詢,然後組裝一份新的資料儲存到新庫中,再進行操作。
另外一個注意點就是:應用和資料庫一起遷移!應用和資料庫一起遷移!應用和資料庫一起遷移!
很多架構師在遷移的時候為了快速將功能上線,不遷移資料庫,僅僅變更 API,會在程式碼中保留一部分相容性程式碼。這就留下了技術債。而且,這種相容性妥協會帶來 n 箇中間過渡版本,永遠到達不了彼岸。
並不是說資料庫的拆分是必須的。剛開始,我們往往會單一資料庫,多微服務訪問的形式。到後來,我們就會把它拆分成右邊的形式。
你可能會問我,為什麼一個庫會被多個微服務訪問,不是應該一個微服務對應一個庫嗎?要麼就是這幾個微服務不應該被拆分,而應該合併?
微服務和庫的對應關係受以下幾個因素影響:
- 不同的微服務的 SLA 是不同的。為了做到按需擴充套件,有必要在多個微服務可能會訪問同一個庫。
- 減少冗餘資料,為了保證“單一可靠資料來源”並減少冗餘資料,幾個微服務需要訪問同一個庫。
- 業務特性和效能特性。這方面主要的策略有讀寫分離、動靜分離等。
- 資料庫承載著服務間的通訊功能。
在資料庫拆分的時候,要注意資料的冗餘和一致性問題。為了提升效率,獨立資料庫裡的適當冗餘是必要的。但是,如果為了避免冗餘,而不斷跨庫和跨 API 查詢很密集的是偶,很有可能你的微服務拆分錯誤。畢竟,跨網路的訪問效能遠遠不如在一個程序上下文中的效能。
從資料庫的查詢頻率和效能來進行資料庫的拆分和重組也是拆分微服務的技巧之一。一般的原則是:“先拆表,後拆庫。關聯查詢先拆分後合併”。這會是一個反覆校準的過程,很難一次成功。另外,我比較推薦把關係寫到應用邏輯裡而不是資料庫裡,這樣就可以減少一些底層的依賴,可以隔離資料庫和表集中管理帶來的連鎖問題。
輕量級服務間通訊

輕量級服務間通訊
去 ESB 化
就像前文所述,最早的時候我們採用 ESB (企業訊息匯流排,Enterprise Service Bus)整合各個異構的系統。通過統一的接入方式將不同的系統整合到一起。在當時,這是流行的 SOA 架構的核心理念。但問題隨之而來:隨著整合到 ESB 上的系統越來越多,你會發現 ESB 要處理各種系統之間的協議轉換和資料轉換就會帶了很多額外的工作量和效能開銷。最主要的是 ESB 成為了核心的依賴,如果 ESB 維護,所有的服務都會被阻塞住。慢慢的,鬆散耦合的不同系統和服務會因為 ESB 耦合又成為了一整塊應用。
然而,當時並沒有新興的解決方案能夠解決這些痛點,直到移動網際網路技術的興起。當我們開始做前後端分離的時候大量採用基於 Ruby 和 Scala 的輕量級應用框架和 json 這樣的輕量級協議,這些低成本的快速專案給客戶和我們都帶來了信心。於是我們開始把整合在 ESB 上的系統根據業務線逐個剝離下來,使每一個單獨的應用可以獨立釋出和部署。
這樣,隨著引入輕量級的框架和協議不斷引入,我們就離傳統的,重量級的 SOA 越來越遠。這是後來我們“成為”微服務的重要標誌之一。
REST 還是 RPC ?
REST 還是 RPC,這是常見的兩種微服務通訊風格,最重要的區別是你看待微服務的方式。
我們並沒有採用 RPC,而採用了 REST。我們把微服務抽象成為了資源,基於資源對外提供服務。這樣,我們很清楚應該如果對外暴露什麼內容,同時應該隱藏什麼細節。這點很可能是和 AWS 學習的,它們把每一個服務都抽象成了一個狀態機,加上我們又基於 AWS 進行部署,所以 REST 成為了一個自然的選擇。
RPC 是另外一個選擇,但 RPC 的下述缺點是我們關注的:
- 除錯複雜性,RPC 有時候會隱藏很多細節,這些細節會變成日後除錯的深坑。
- 脆弱性,使用 RPC 的前提是“網路是可靠的”,事實上網路並不會那麼可靠,尤其是異構系統中。
- 技術強繫結,例如 Java RMI 這種技術,就需要雙方使用同樣的技術棧,這實際上破壞了微服務架構本身的好處。
這也並不是說 RPC 一無是處,RPC 很容易使用,你很容易就可以給 RPC 的客戶端和服務端打樁並開始開發。而且很多 RPC 的協議本質上是 二進位制的,能夠自己處理序列化,效能會比 REST 高很多。
採用 MQ 通訊
釋出者 - 訂閱者 本身就是一個比較理想的系統間鬆耦合的通訊方式。然而,ESB 的侵入性實現使之成為了負擔。然而基於訊息佇列的方式則會輕量很多。
微服務傾向於統一的資料交流格式和非同步的呼叫,減少了阻塞的發生並提高了系統的效能。於是我們就將 ESB 換成了訊息佇列和 API 呼叫。而訊息佇列和 RESTful API Call 本身就算是一種系統拆分的重要方式。它把系統的內部依賴轉化到了外部。
一個成功的微服務拆分,服務間的依賴會少。與此伴隨著應用系統內的同步呼叫減少,非同步呼叫增多。採用非同步呼叫取代同步呼叫也可以看作是微服務拆分的一種方式。
現在,大家都會採用 Kafka 作為訊息佇列。但是,請警惕不要 把 Kafka 變成了另外一種 ESB 。
採用資料庫進行通訊
雖然我並不推薦這種資料傳遞的方式,因為這本質上就是一種底層的耦合。但採用資料庫的表作為一種過渡手段在複雜的資料庫拆分的時候起到了關鍵作用。然而,在這種模式下一定要確立“寫的微服務”和“讀的微服務”分離,否則會變成雙向依賴。一般會採用 CQRS (命令查詢責任分離)模式來拆分微服務。諸如“增、刪、改”這樣的命令服務要和查詢服務分開來。
選擇最適合你的通訊方式
這裡需要說明的是,我們要從業務場景和系統的實際情況出發來選擇合適的通訊場景。微服務傾向於透明的格式和輕量級的協議。重要的是,我們選擇方法時要尊重“網路不總是很穩定”的事實來進行設計。
另一方面,為了快速定位風險和問題,我們可以通過訊息佇列和 RESTful API 的方式是把內部的依賴暴露到了外部,使得內部的“暗知識”可以充分暴露。減少了系統的風險和修復時間。
而如果你的微服務通訊太頻繁了並帶來了額外的風險和成本,有可能你的微服務就已經拆錯了。在這種情況下,你可能需要考慮合併微服務。這取決於系統的效能和穩定性,也取決於你的維護成本。
一個成功的微服務拆分也帶來了開發的獨立性,由於你的服務是非同步呼叫的,應該可以獨立部署和釋出,沒有其他的依賴且不會造成額外的影響。如果你的微服務拆分之後,開發的流程仍然很長,就要考慮組織流程上的拆分了。
微服務全鏈路監控

服務間監控
上圖這是我們採用 NewRelic 做的微服務的監控,可以看到左邊是一些業務系統,接著中間的微服務,最後右是資料庫。為了保密,我把微服務的名字用藍色的框覆蓋了,你可以看到。這是一個 NodeJS + PHP 和 MySQL 的混合架構。
你的微服務運維也需要有微服務相互的健康檢查的連線圖。以前可能只需要關注一個應用的表現。而到了微服務架構後,你需要關注每一個節點。只有一條依賴上的所有服務都健康了才算健康,但是中間如果有一個不可用的話,哪怕它的失敗率很低,如果你的依賴鏈很長的話,你的應用的健康度就是這些節點的可用率相乘的結果。比如你有三個依賴的微服務,他們的可用率都是99%,那麼業務的可用率就是 99% * 99% * 99% = 0.970299,也就是 97.02%。但三個微服務如果不相互依賴,它們的可用率仍然是 99%。所以,服務間依賴越多,系統的風險越高。這也從數學的角度上證明了微服務的優勢。
當微服務數量多了之後,我們就不能考慮單個應用的成功性,我們考慮的是這一塊叢集的失敗率做整體監控,得到系統監控的可用率。當原先的系統內部依賴暴露到外部之後,運維的工作就不僅僅是關注之前的“大黑盒”了,這就需要開發和運維共同合作,這也是微服務團隊必須是 DevOps 團隊的主要原因。
此外,從開發的角度講,由於一個業務跨多個微服務,我們很難跟蹤業務的使用情況。我們所採用的方式是為每一類事物設計狀態上下文。包括 id 和錯誤編碼,錯誤上下文,錯誤編碼根據類別分為:0 正常,正數:服務請求端錯誤,負數:服務響應端錯誤,然後根據每個場景設計回滾流程。你也可以通過 HTTP 狀態來區分,HTTP 200 就向下傳遞。HTTP 4XX 或者 5XX 就向上回滾並記錄錯誤資訊。這樣你就知道是哪個微服務出現了錯誤。
另外一個技巧就是把自動化測試當做生產環境的業務監控,我們在生產環境通過執行功能性的自動化測試來驗證生產環境業務的正確性。你只需要為它分配一個特殊的賬號就可以了。
我們做微服務最簡單的路徑一般來說都是先做投入產出的分析,然後可能做前後端的分離,並做到前後端的持續部署。這裡的要點是需要採用自動化的方式做好基礎設施治理,如果沒有 DevOps 的組織這一點就很難。我們通常的做法是給微服務單獨開闢一套新的設計,並把微服務團隊單獨剝離,因為這是兩種不同的文化和流程。最後做到全功能的 DevOps 團隊和微服務模板,而這些基礎設施的自動化是可以複製的。可以為我們批量拆分微服務提供一個很好的開始。
技術只是一個方面
微服務的轉型往往是一個結果,而不是原因。特別是微服務被當做一個“技術”被引進的時候。然而,技術方案的落地是離不開人的,他是組織和技術相互結合發展的一個結果。如何為微服務的運營提供高效的組織結構?請看下一篇《成功微服務實施的組織演進》