Chris Richardson微服務翻譯:構建微服務之微服務架構的進程通信

Chris Richardson 微服務系列翻譯全7篇連接:html

原文連接:Building Microservices: Inter-Process Communication in a Microservices Architecturenginx


簡介

在單體應用中,模塊間使用編程語言級別的方法或函數彼此調用。而基於微服務架構的本質是是運行在多臺機器上的分佈式應用,每一個服務都是一個進程。以下圖所示,微服務之間必須使用進程間通訊(IPC)的機制實現交互:web

稍後咱們將討論 IPC 技術,先看下設計相關的問題。編程

交互模式

當爲某個服務選擇 IPC 機制時,首先要考慮服務間如何交互。client 和 server 端有不少交互的方式,能夠按兩個維度分類:瀏覽器

第一個維度是一對一仍是一對多:緩存

  • 一對一:每一個 client 請求只會被一個 server 處理
  • 一對多:每一個 client 請求會被多個 server 處理

第二個維度是交互是同步仍是異步:安全

  • 同步模式:client 指望來自 server 的及時響應,甚至可能因爲等待而阻塞
  • 異步模式:client 等待響應時不會阻塞,不須要及時響應

下面表格展現了兩種方式的不一樣:網絡

  一對一 一對多
同步 請求/響應  
異步異步 通知 發佈/訂閱
請求/異步響應 發佈/異步響應

 

 

 

 

下面有幾種一對一的交互模式:架構

  • 請求/響應:client 向 server 發送請求並等待響應,client 指望響應能及時到達。在一個基於線程的應用中,請求的線程可能在等待時阻塞線程的執行。
  • 通知(單向請求):client 往 server 發送請求,但不指望響應。
  • 請求/異步響應:client 往 server 發送請求,server 異步響應。client 不會阻塞,由於設計時就默認請求不會當即返回。

下面有幾種一對多的交互模式:app

  • 發佈/訂閱模式:client 發佈一個通知消息,消息會被 0 或多個感興趣的服務消費。
  • 發佈/異步響應模式:client 發佈一個請求消息,在必定時間內等待感興趣服務的響應。

每一個服務都是以上幾種模式的組合,對某些服務來講,一個 IPC 機制就能知足了,另一些服務可能須要多個 IPC 機制的組合。下圖展現了用戶叫車應用中,用戶請求行程時,服務是如何交互的:

上圖服務使用了通知、請求/響應、發佈/訂閱的方式。例如:乘客在移動端向『行程管理服務』發送接送需求的通知;『行程管理服務』使用 請求/響應 模式 調用『乘客服務』來驗證乘客帳號是否有效;而後『行程管理服務』建立行程並使用 發佈/訂閱 模式來通知其餘服務(定位可用司機的『調度服務』等)。

咱們討論了交互風格,下面看下如何定義 API。

定義API

API 是服務端和客戶端的契約。不管選擇選擇哪一種 IPC 機制,都須要使用接口定義語言(IDL)來定義 服務的API。開發服務前,先定義服務接口,並與 client端開發者一塊兒 review,後續再對 API 進行迭代。這樣設計能幫助你構建更符合客戶需求的服務。

文章後半段你會發現,API 的定義依賴選擇的 IPC 機制。若是使用消息機制,API 則由消息頻道和消息類型組成。若是使用 HTTP, API 則是由 URL 和 request/response 格式組成。後面咱們將討論 IDL 的細節。

API進化

服務的 API 不可避免的隨着時間進化。單體應用中,能夠直接修改 API 並更新全部的調用者。但在微服務應用中,即時 API 的全部調用者都在一個應用中,去更新其餘服務也是很困難的,一般不能強制讓全部 client 升級來保持和 server 端一致。此外,你可能還會增長部署新的服務版本,與老版本同時運行。瞭解處理這些問題的策略是很是重要的。

如何根據更改的大小來處理 API 呢?有的變化很小,一般能夠與舊版本作到向後兼容,例如:爲請求或響應添加了一個屬性。對此,設計服務時考慮魯棒性是頗有必要的:使用舊版本 API 的 client 在新版本的 API 下能正常工做;server 爲缺失的屬性提供默認值;client 忽略響應中額外添加的屬性。

有時候 API 不得不作一些大的、不兼容的變更,此時又不能強制讓全部 client 當即升級,所以,舊版本 API 還須要運行一段時間。若是使用的是基於 HTTP 的 IPC,能夠在 URL 裏嵌入服務版本,每一個服務實例能夠同時處理多個版本。另外一種方式也能夠選擇爲每一個版本單獨部署。

處理局部故障

分佈式系統廣泛存在局部失敗的問題,因爲 client 和 server 是運行在獨立的進程中,server 可能由於掛了或維護而暫時不可用,不能及時響應 client 的請求,或者由於過載而致使響應很慢。

以上篇文章提到的商品詳情頁場景爲例,假設推薦服務沒有響應,client 可能無限期的等待服務響應而致使阻塞,這不只致使用戶體驗很糟糕,並且會佔用線程等寶貴資源,就像下圖所示,運行時線程耗盡,而沒法響應任何請求:

爲解決此類問題,設計時須要考慮局部故障的問題:

Netfilix 提供了較好的解決方案:

  • 網絡超時:等待響應時不設置無期限阻塞,而採用超時策略,保證資源不會無限被佔用。
  • 限制請求數量:爲 client 對某個服務的請求設置訪問上限,若是請求達到上限,則再也不處理任何請求,作到快速失敗。
  • 熔斷器模式:記錄成功和失敗的請求數量,若是失敗率超過一個閥值,觸發熔斷器使得後面的請求馬上失敗。若是大量請求失敗,那這個服務可認爲不可用,繼續請求也沒有意義。一段時間後,client 能夠再次重試,若是成功,則關閉熔斷器。
  • 提供 fallback 機制:請求失敗時提供 fallback,例如:返回緩存或一個默認值

Netflix Hystrix 是一個實現相關模式的開源庫。若是使用 JVM,那麼推薦使用 Hystrix。若是使用的非 JVM 環境,也可使用相似的庫。

IPC 技術

如今有不一樣的 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。統一來看,他們都支持一些消息和頻道,都致力於高可用、高性能和高可擴展性。

使用消息系統有不少優勢:

  • client 和 server 解耦,client 只須要將消息發送到合適的頻道,徹底不須要感知 server 的存在,所以不須要再去使用服務發現機制來肯定服務實例的位置。
  • 消息緩衝:在 HTTP 這樣的請求/響應協議下,client 和 server 交互期間須要保證雙方的可用性。然而在消息模式中,消息組件會將消息按照隊列方式進行管理,直到消息被消費者消費。例如:即便訂單系統很慢或不可用,在線商店仍舊能夠接受客戶的下單請求,只須要將下單消息放入隊列便可。
  • 靈活的 client-server 交互方式:消息支持前面提到的全部交互風格。
  • 清晰的進程間通訊:基於 RPC 的通訊機制視圖使調用遠程服務像調用本地服務同樣,然而,因爲局部故障的可能,他們大不相同。消息機制使這些差別直觀明顯,開發者不會產生安全錯覺。

固然,消息系統也有缺點:

  • 額外的運維複雜度:消息系統組件的安裝、部署、運維等工做,消息系統的高可用保障,不然會影響到系統的可用性。
  • 實現 請求/響應 交互模式的複雜度:每條請求消息須要包含一個 回覆渠道ID 和 關聯ID,server 發送包含關聯ID的響應消息到渠道中,client 使用關聯ID 去匹配對應的響應。這種狀況下,使用支持請求/響應的 IPC 機制會更容易些。

同步,請求/響應 IPC

使用同步、請求/響應的 IPC 時,client 請求 server 時有可能因爲等待 server 響應而被阻塞。另一些client 會使用異步、事件驅動的代碼,例如封裝好的 Future 或者 Rx Observable。這個模式最多見的協議是 Rest 和Thrift。

Rest

當前流行開發 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 的場景:

乘客向行程管理服務的 /trips 資源發送了 POST 請求,行程管理服務而後向乘客管理服務發送 GET 請求獲取乘客信息,當乘客認證完成後,建立一個行程,並返回 201 響應。

Leonard Richardson 爲 REST 定義了一個成熟度模型,分爲以下四個層次:

  • Level 0:web 服務使用 HTTP 做爲傳輸方式,調用固定的 URL,每次請求指定方法和參數
  • Level 1:引入了資源的概念,要執行對資源的操做,請求經過 POST,指定要執行的操做和參數
  • Level 2:使用 HTTP 的語法來執行操做,例如:GET 表示獲取,POST 表示建立,PUT 表示更新
  • Level 3:API 定義按照 HATEOAS(Hypertext As The Engine Of Application State)設計原則,基本思想 GET 請求返回資源的一些對資源容許操做的連接。例如:client 使用 GET 訂單資源中包含的連接取消某一訂單。HATEOAS 的一個優勢就是無需在 client 代碼中寫入硬連接的 URL。此外,返回的資源信息中包含了對資源容許操做的連接,client 無需再猜想當前資源下所能作哪些操做了

基於 HTTP 協議的優勢:

  • 簡單,爲你們所熟悉
  • 可以使用瀏覽器、postman,curl 之類的命令行測試 API
  • 支持 請求/響應 模式的通訊
  • 不須要中間代理,減價系統架構

HTTP 不足之處:

  • 只支持 請求/響應的交互
  • client 和 server 之間沒有消息緩衝機制,要求交互時雙方必須同時運行
  • client 須要知道每一個 server實例 的url
Thrift

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 機制可用:異步消息機制和同步請求/響應機制。下篇文章中,咱們會討論微服務架構中的服務發現問題。

相關文章
相關標籤/搜索