Chris Richardson 微服務系列翻譯全7篇連接:html
原文連接:Building Microservices: Inter-Process Communication in a Microservices Architecturenginx
在單體應用中,模塊間使用編程語言級別的方法或函數彼此調用。而基於微服務架構的本質是是運行在多臺機器上的分佈式應用,每一個服務都是一個進程。以下圖所示,微服務之間必須使用進程間通訊(IPC)的機制實現交互:web
稍後咱們將討論 IPC 技術,先看下設計相關的問題。編程
當爲某個服務選擇 IPC 機制時,首先要考慮服務間如何交互。client 和 server 端有不少交互的方式,能夠按兩個維度分類:瀏覽器
第一個維度是一對一仍是一對多:緩存
第二個維度是交互是同步仍是異步:安全
下面表格展現了兩種方式的不一樣:網絡
一對一 | 一對多 | |
同步 | 請求/響應 | |
異步異步 | 通知 | 發佈/訂閱 |
請求/異步響應 | 發佈/異步響應 |
下面有幾種一對一的交互模式:架構
下面有幾種一對多的交互模式:app
每一個服務都是以上幾種模式的組合,對某些服務來講,一個 IPC 機制就能知足了,另一些服務可能須要多個 IPC 機制的組合。下圖展現了用戶叫車應用中,用戶請求行程時,服務是如何交互的:
上圖服務使用了通知、請求/響應、發佈/訂閱的方式。例如:乘客在移動端向『行程管理服務』發送接送需求的通知;『行程管理服務』使用 請求/響應 模式 調用『乘客服務』來驗證乘客帳號是否有效;而後『行程管理服務』建立行程並使用 發佈/訂閱 模式來通知其餘服務(定位可用司機的『調度服務』等)。
咱們討論了交互風格,下面看下如何定義 API。
API 是服務端和客戶端的契約。不管選擇選擇哪一種 IPC 機制,都須要使用接口定義語言(IDL)來定義 服務的API。開發服務前,先定義服務接口,並與 client端開發者一塊兒 review,後續再對 API 進行迭代。這樣設計能幫助你構建更符合客戶需求的服務。
文章後半段你會發現,API 的定義依賴選擇的 IPC 機制。若是使用消息機制,API 則由消息頻道和消息類型組成。若是使用 HTTP, API 則是由 URL 和 request/response 格式組成。後面咱們將討論 IDL 的細節。
服務的 API 不可避免的隨着時間進化。單體應用中,能夠直接修改 API 並更新全部的調用者。但在微服務應用中,即時 API 的全部調用者都在一個應用中,去更新其餘服務也是很困難的,一般不能強制讓全部 client 升級來保持和 server 端一致。此外,你可能還會增長部署新的服務版本,與老版本同時運行。瞭解處理這些問題的策略是很是重要的。
如何根據更改的大小來處理 API 呢?有的變化很小,一般能夠與舊版本作到向後兼容,例如:爲請求或響應添加了一個屬性。對此,設計服務時考慮魯棒性是頗有必要的:使用舊版本 API 的 client 在新版本的 API 下能正常工做;server 爲缺失的屬性提供默認值;client 忽略響應中額外添加的屬性。
有時候 API 不得不作一些大的、不兼容的變更,此時又不能強制讓全部 client 當即升級,所以,舊版本 API 還須要運行一段時間。若是使用的是基於 HTTP 的 IPC,能夠在 URL 裏嵌入服務版本,每一個服務實例能夠同時處理多個版本。另外一種方式也能夠選擇爲每一個版本單獨部署。
分佈式系統廣泛存在局部失敗的問題,因爲 client 和 server 是運行在獨立的進程中,server 可能由於掛了或維護而暫時不可用,不能及時響應 client 的請求,或者由於過載而致使響應很慢。
以上篇文章提到的商品詳情頁場景爲例,假設推薦服務沒有響應,client 可能無限期的等待服務響應而致使阻塞,這不只致使用戶體驗很糟糕,並且會佔用線程等寶貴資源,就像下圖所示,運行時線程耗盡,而沒法響應任何請求:
爲解決此類問題,設計時須要考慮局部故障的問題:
Netfilix 提供了較好的解決方案:
Netflix Hystrix 是一個實現相關模式的開源庫。若是使用 JVM,那麼推薦使用 Hystrix。若是使用的非 JVM 環境,也可使用相似的庫。
如今有不一樣的 IPC 技術可選擇:基於 請求/響應 的同步通訊模式,例如基於 HTTP 的 Rest 或 Thrift;也能夠選擇異步的、基於消息的通訊模式,例如AMQP、STOMP。這些通訊有着不一樣的消息格式,服務能夠選擇基於文本、方便閱讀的 JSON 或 XML格式,或者效率更高的二進制格式(例如 Avro、Protocol Buffers)。
使用消息模式時,進程間經過異步消息的方式來通訊,client 發送消息來請求 server,若是指望 server 響應,則 server 會發送另一條消息給 client。因爲通訊是異步的,client 不會由於等待響應而阻塞,同時 client 編程時也以服務不會當即響應來處理。
消息由消息頭(元數據和發送者)和消息體組成,消息經過頻道進行交換,任意數量的生產者均可以往頻道里發送消息,一樣,任意數量的消費者均可以從頻道里消費消息。頻道分爲點對點、訂閱/發佈兩種:
下圖展現了打車軟件中如何使用 發佈/訂閱 模式:
行程管理服務向『訂閱-發佈』頻道寫入『建立行程』的消息,通知調度服務有新的行程請求。調度服務查找空閒的司機,並經過『發佈-訂閱』頻道寫入『推薦司機』的消息,通知其餘服務。
有多種消息系統供咱們選擇,固然咱們儘量選擇支持多種編程語言的。一些消息系統支持 AMQP和 STOMP 這樣的標準協議,有的則支持專有的協議。開源的消息系統例如:RabbitMQ、Apacha Kafka、Apache ActiveMQ 和 NSQ。統一來看,他們都支持一些消息和頻道,都致力於高可用、高性能和高可擴展性。
使用消息系統有不少優勢:
固然,消息系統也有缺點:
使用同步、請求/響應的 IPC 時,client 請求 server 時有可能因爲等待 server 響應而被阻塞。另一些client 會使用異步、事件驅動的代碼,例如封裝好的 Future 或者 Rx Observable。這個模式最多見的協議是 Rest 和Thrift。
當前流行開發 RESTful 風格的 API。 Rest 是基於 HTTP 的 IPC 機制,其核心概念是使用 URL 來表示資源(用戶或產品的一組業務對象)。例如:GET 請求會返回一個資源的信息,多是 XML 文檔 或 JSON 對象格式;POST 請求會建立新的資源;PUT 請求會更新資源。REST 之父 Roy Fielding 曾經說過:
REST provides a set of architectural constraints that, when applied as a whole, emphasizes scalability of component interactions, generality of interfaces, independent deployment of components, and intermediary components to reduce interaction latency, enforce security, and encapsulate legacy systems.
Rest 提供了一些列架構系統參數做爲總體使用,強調組件交互的擴展性、接口的通用性、組件的獨立部署、減小交互延遲的中間件,他強化安全,也能封裝遺留系統。
下面展現打車軟件使用 Rest 的場景:
Leonard Richardson 爲 REST 定義了一個成熟度模型,分爲以下四個層次:
基於 HTTP 協議的優勢:
HTTP 不足之處:
Apache Thrift 是 REST 的一個有趣的替代品,實現了跨語言的客戶端和服務端RPC通訊的框架,Thrift 提供了 C 語言風格的接口定義語言來定義 API,能夠經過編譯生成客戶端Stub 和 服務端的骨架,能夠生成多種語言的代碼(包括 C++、Java、Python、PHP、Ruby、Erlang、Node.js)。
Thrift 接口一般包含一個或多個服務,服務定義與 Java 接口相似,是一組強類型方法的集合。Thrift 能返回值,也能夠定義爲單向通訊。若是須要返回值就須要實現 請求/響應風格的交互,客戶端等待響應時能夠拋出異常;單向通訊就是通知模式,服務端不須要返回響應。
Thrift 支持 JSON、二進制、壓縮二進制等不一樣的消息格式。二進制解碼比 JSON 更快,更爲高效;壓縮二進制比 JSON 空間利用率更高; JSON 則更易讀。Thrift 也支持不一樣的通訊協議:TCP 或 HTTP,TCP 比 HTTP 更加高效,而 HTTP 對防火牆、人及瀏覽器更加友好。
選擇一種支持多語言的消息格式很是重要,哪怕你只用一種語言實現微服務,誰又能保證之後不會使用新的語言呢?
目前有文本和二進制兩種格式。文本格式包括 JSON 和 XML。這種格式優勢不只可讀,並且是自描述的。JSON中,對象的屬性是鍵值對的集合;XML中,屬性表示爲命名的元素和值。消費者能選擇感興趣的值而忽略其餘部分,對格式的修改也能容易的向後兼容。
XML文檔的結構是 XML Schema 定義的,隨着時間的發展,開發者意識到 JSON 也須要一個相似的機制,方法一是使用 JSON Schema,要麼獨立使用,要麼做爲 Swagger 這類 IDL的一部分使用。
文本格式的一大缺點是消息會變的冗長,尤爲是 XML:由於消息是自描述的,每條消息除了值以外還包括屬性的名稱。另外一大缺點是解析文本的開銷略大,此時能夠考慮二進制格式。
二進制格式也不少,若是使用 Thrift,那麼能夠用二進制Thrift;若是使用其餘消息格式,經常使用的還包括 Protocol Buffers 和 Apache Avro,二者都提供了 IDL 來定義消息結構。差別之處在於 Protocol Buffers 使用標記字段,而 Avro 消費者須要瞭解 Schema 來解析消息,使用 Protocol Buffers 時,API進化比 Avro 更容易。Martin Kleppmann 的 博客文章 對Thrift、Protocol Buffers 和 Avor 進行了詳細的比較。
微服務須要使用進程間消息通訊機制來交互,設計服務的通訊模式時,須要考慮一下幾個問題:服務如何交互、如何定義 API、如何升級 API,如何處理局部故障。微服務架構有兩種 IPC 機制可用:異步消息機制和同步請求/響應機制。下篇文章中,咱們會討論微服務架構中的服務發現問題。