0103-微服務架構中的進程間通訊

1、概述

  在單一應用程序中,組件經過語言級方法或函數調用相互調用。相反,基於微服務的應用程序是在多臺機器上運行的分佈式系統。每一個服務實例一般都是一個進程。所以,以下圖所示,服務必須使用進程間通訊(IPC: inter‑process communication)機制進行交互。nginx

  

2、交互樣式

2.一、兩個維度進行分類

  在爲服務選擇IPC機制時,首先考慮服務如何交互頗有用。有各類各樣的客戶端⇔服務交互樣式。它們能夠按照兩個維度進行分類。web

  第一個緯度是交互是一對一仍是一對多:編程

    一對一 : 每一個客戶端請求僅由一個服務實例處理。瀏覽器

    一對多 :每一個請求由多個服務實例處理。緩存

  第二個緯度面是交互是同步的仍是異步的:安全

    同步:客戶但願服務可以及時響應,甚至可能在等待時阻止服務器

    異步:客戶端在等待響應時不會阻塞,而且響應(若是有)不必定當即發送。網絡

2.二、交互樣式組合

    

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

2.2.一、一對一的交互模式有如下幾種方式:

  請求/響應 - 客戶端向服務發出請求並等待響應。客戶指望及時到達響應。在基於線程的應用程序中,發出請求的線程甚至可能在等待時阻塞。架構

  通知(又名單向請求) - 客戶端向服務器發送請求,但預期或未發送回覆。併發

  請求/異步響應 - 客戶端將請求發送給異步回覆的服務。客戶在等待時不會阻塞,而且假定響應可能不會到達一段時間。

2.2.二、一對多交互:

  發佈/訂閱 - 客戶發佈通知消息,該消息由零個或多個感興趣的服務消耗。

  發佈/異步響應 - 客戶端發佈請求消息,而後等待一段時間,以便從感興趣的服務響應。

每一個服務一般使用這些交互樣式的組合。對於某些服務,一個IPC機制就足夠了。其餘服務可能須要使用IPC機制的組合。下圖顯示了當用戶請求旅程時,出租車應用程序中的服務如何相互做用。

     

  這些服務使用通知,請求/響應和發佈/訂閱的組合。 例如,乘客的智能手機向Trip Management服務發送通知以請求取件。 旅行管理服務經過使用請求/響應來調用乘客服務來驗證乘客的帳戶是否有效。 旅程管理服務而後建立旅程,並使用發佈/訂閱通知其餘服務,包括查找可用驅動程序的調度程序。

  如今咱們已經研究了交互風格,讓咱們來看看如何定義API。 

3、API

3.一、定義API

   服務的API是服務與客戶端之間的契約。 不管您選擇何種IPC機制,使用某種接口定義語言(IDL)精肯定義服務的API都很重要。 使用API優先方法來定義服務甚至有很好的理由。 您經過編寫接口定義開始開發服務,並與客戶端開發人員一塊兒審查。 只有在API定義迭代後才能實現該服務。 事先作好這項設計能夠增長創建知足客戶需求的服務的機會。
   正如你將在本文後面看到的那樣,API定義的性質取決於你正在使用的IPC機制。 若是您使用消息傳遞,則API由消息通道和消息類型組成。 若是您使用HTTP,則API由URL和請求和響應格式組成。 稍後咱們將更詳細地介紹一些IDL。

3.二、不斷演變的API

  服務的API老是隨着時間而變化。在單一應用程序中,更改API並更新全部調用者一般很簡單。在基於微服務的應用程序中,即便API的全部消費者都是同一應用程序中的其餘服務,也要困可貴多。您一般沒法強制全部客戶端與服務同步升級。此外,您可能會逐步部署新版本的服務,以便舊版和新版服務同時運行。制定解決這些問題的策略很重要。

  如何處理API更改取決於更改的大小。一些更改是輕微的,並向後兼容之前的版本。例如,您可能會將屬性添加到請求或響應中。設計客戶和服務以便他們遵照健壯性原則是有道理的。使用舊API的客戶端應繼續使用新版本的服務。該服務爲缺乏的請求屬性提供默認值,而且客戶端忽略任何額外的響應屬性。使用IPC機制和消息傳遞格式很重要,這些機制和消息傳遞格式可使您輕鬆地發展您的API。

  可是,有時您必須對API進行重大且不兼容的更改。因爲您沒法強制客戶端當即升級,所以服務必須支持較早版本的API一段時間。若是您使用的是基於HTTP的機制(如REST),則一種方法是將版本號嵌入到URL中。每一個服務實例可能同時處理多個版本。或者,您能夠部署不一樣的實例來處理特定的版本。

3.三、處理部分故障

  正如前面關於API網關的文章所述,在分佈式系統中,存在部分故障的風險。 因爲客戶和服務是獨立的流程,所以服務可能沒法及時響應客戶的請求。 服務可能因故障或維護而關閉。 或者服務可能超載而且對請求響應速度極慢。

  例如,考慮該文章中的產品詳細信息場景。 假設推薦服務沒有反應。 天真的客戶端實現可能會無限期地等待響應。 這不只會致使糟糕的用戶體驗,並且在許多應用程序中會消耗一些寶貴的資源,例如線程。 最終,運行時將耗盡線程並變得無響應,以下圖所示。 

     

  爲了防止出現這個問題,設計服務來處理部分故障相當重要。

  Netflix描述的方法很好。處理部分故障的策略包括:

    網絡超時 - 永遠不要無限期地阻塞,而且在等待響應時老是使用超時。使用超時確保資源不會無限期地捆綁在一塊兒。

    限制未完成請求的數量 - 強加客戶端可使用特定服務的未完成請求數量的上限。若是已達到限制,則發出額外請求可能毫無心義,而且這些嘗試須要當即失敗。

    斷路器模式 - 跟蹤成功和失敗請求的數量。若是錯誤率超過配置的閾值,則跳閘斷路器,以便進一步嘗試當即失敗。若是大量請求失敗,則代表該服務不可用,而且發送請求毫無心義。超時後,客戶應再試一次,若是成功,請關閉斷路器。

    提供回退 - 請求失敗時執行回退邏輯。例如,返回緩存數據或默認值,如空集建議。

  Netflix Hystrix是一個實現這些和其餘模式的開源庫。若是你使用的是JVM,你應該考慮使用Hystrix。並且,若是您在非JVM環境中運行,則應該使用等效庫。

4、IPC技術

4.一、IPC技術  

  有許多不一樣的IPC技術可供選擇。

  服務可使用基於HTTP的REST或Thrift等基於同步請求/響應的通訊機制。或者,他們可使用基於消息的異步通訊機制,例如AMQP或STOMP。還有各類不一樣的消息格式。服務可使用人類可讀的基於文本的格式,如JSON或XML。或者,他們可使用二進制格式(更高效),如Avro或協議緩衝區。稍後咱們將看看同步IPC機制,但首先讓咱們討論異步IPC機制。

4.二、異步,基於消息的通訊【基於消息傳遞的IPC】

  當使用消息傳遞時,進程經過異步交換消息進行通訊。客戶經過發送消息向服務發出請求。若是服務預計會回覆,則經過向客戶端發送單獨的消息來實現。因爲通訊是異步的,客戶端不會阻止等待回覆。相反,客戶端的編寫假定不會當即收到答覆。

  消息由頭部(發送者等元數據)和消息主體組成。 經過頻道交換消息。 任何數量的生產者均可以將消息發送到一個渠道。 一樣,任何數量的消費者均可以接收來自頻道的消息。 有兩種渠道,點對點和發佈 - 訂閱。 點對點通道向正在從該通道讀取的一個消費者傳遞消息。 服務使用點對點渠道來實現前面描述的一對一交互風格。 發佈 - 訂閱頻道將每條消息傳遞給全部附加的消費者。 服務使用發佈 - 訂閱頻道來得到上述的一對多互動風格。

  下圖顯示出租車應用程序如何使用發佈 - 訂閱頻道。

    

  旅程管理服務經過向發佈 - 訂閱頻道寫入旅行建立消息來通知感興趣的服務,例如調度員關於新旅程。 Dispatcher經過將驅動程序建議消息寫入發佈 - 訂閱通道來查找可用的驅動程序並通知其餘服務。

  有許多消息系統可供選擇。 你應該選擇一種支持各類編程語言的程序。 一些消息傳遞系統支持標準協議,例如AMQP和STOMP。 其餘消息傳遞系統具備專有但記錄的協議 有大量的開源消息系統可供選擇,包括RabbitMQ,Apache Kafka,Apache ActiveMQ和NSQ。 在較高的層面上,他們都支持某種形式的信息和渠道。 他們都努力作到可靠,高性能和可擴展。 可是,每一個經紀人的消息傳遞模型的細節存在顯着差別。

  使用消息傳遞具備許多優勢:

    將客戶端與服務分離 - 客戶端經過簡單地向相應的通道發送消息來發出請求。客戶端徹底不知道服務實例。它不須要使用發現機制來肯定服務實例的位置。

    消息緩衝 - 使用同步請求/響應協議(例如HTTP),客戶端和服務在交換期間都必須可用。相反,消息代理將寫入通道的消息排隊,直到消費者能夠處理它們。這意味着,例如,即便訂單履行系統緩慢或沒法使用,在線商店也能夠接受來自客戶的訂單。訂單消息只是排隊。

    靈活的客戶服務交互 - 消息傳遞支持前面描述的全部交互風格。

    顯式進程間通訊 - 基於RPC的機制試圖調用遠程服務看起來與調用本地服務相同。可是,因爲物理規律和部分失效的可能性,它們實際上徹底不一樣。消息傳遞使這些差別很是明確,所以開發人員不會陷入虛假的安全感。

   可是,使用消息傳遞存在一些缺點:

    額外的操做複雜性 - 消息傳遞系統是另外一個必須安裝,配置和運行的系統組件。 消息代理的高可用性相當重要,不然系統可靠性受到影響。

    實現基於請求/響應的交互的複雜性 - 請求/響應式交互須要一些工做來實現。 每一個請求消息必須包含回覆通道標識符和相關標識符。 該服務將包含關聯ID的響應消息寫入回覆通道。 客戶端使用關聯ID將響應與請求進行匹配。 使用直接支持請求/響應的IPC機制一般更容易。

  如今咱們已經看到了使用基於消息傳遞的IPC,讓咱們來看看基於請求/響應的IPC。

4.三、基於請求/響應的同步IPC

  當使用同步的基於請求/響應的IPC機制時,客戶端向服務發送請求。 該服務處理請求併發迴響應。 在許多客戶端中,請求阻塞的線程在等待響應時阻塞。 其餘客戶端可能會使用異步,事件驅動的客戶端代碼,這些客戶端代碼可能由期貨或Rx Observables封裝。 可是,與使用消息傳遞不一樣的是,客戶假定響應將及時到達。 有許多協議可供選擇。 兩種流行的協議是REST和Thrift。 咱們先來看看REST。

4.3.一、REST

  當前很流行開發 RESTful 風格的 API。REST 基於 HTTP 協議,其核心概念是資源典型地表明單一業務對象或者一組業務對象,業務對象包括「消費者」或「產品」。REST 使用 HTTP 協議來控制資源,經過 URL 實現。譬如,GET 請求會返回一個資源的包含信息,多是 XML 文檔或 JSON 對象格式。POST 請求會建立新資源,而 PUT 請求則會更新資源。REST 之父 Roy Fielding 曾經說過:

  「REST提供了一套體系結構約束,在總體應用時強調組件交互的可伸縮性,接口的通用性,組件的獨立部署和中間組件,以減小交互延遲,強化安全性並封裝遺留系統。」

    — Fielding, Architectural Styles and the Design of Network-based Software Architectures
  下圖展現了打車軟件如何使用 REST。

     

  乘客經過移動端向行程管理服務的 /trips 資源提交了一個 POST請求。行程管理服務收到請求以後,會發送一個 GET 請求到乘客管理服務以獲取乘客信息。當確認乘客信息以後,隨即建立一個行程,並向移動端返回 201 響應。

  不少開發者都表示他們基於 HTTP 的 API 是 RESTful 風格。可是,如同 Fielding 在他的博客中所說,並不是全部這些 API 都是 RESTful。Leonard Richardson(注:與本文做者 Chris 無任何關係)爲 REST 定義了一個成熟度模型,具體包含如下四個層次:

  • Level 0:本層級的 Web 服務只是使用 HTTP 做爲傳輸方式,實際上只是遠程方法調用(RPC)的一種具體形式。SOAP 和 XML-RPC 都屬於此類。
  • Level 1:Level 1 層級的 API 引入了資源的概念。要執行對資源的操做,客戶端發出指定要執行的操做和任何參數的 POST 請求。
  • Level 2:Level 2 層級的 API 使用 HTTP 語法來執行操做,譬如 GET 表示獲取、POST 表示建立、PUT 表示更新。若有必要,請求參數和主體指定操做的參數。這可以讓服務影響 web 基礎設施服務,如緩存 GET 請求。
  • Level 3:Level 3 層級的 API 基於 HATEOAS(Hypertext As The Engine Of Application State)原則設計,基本思想是在由 GET請求返回的資源信息中包含連接,這些連接可以執行該資源容許的操做。例如,客戶端經過訂單資源中包含的連接取消某一訂單,GET 請求被髮送去獲取該訂單。HATEOAS 的優勢包括無需在客戶端代碼中寫入硬連接的 URL。此外,因爲資源信息中包含可容許操做的連接,客戶端無需猜想在資源的當前狀態下執行何種操做。

  使用基於 HTTP 的協議有以下好處:

  • HTTP 很是簡單而且你們都很熟悉。
  • 可使用瀏覽器擴展(好比 Postman)或者 curl 之類的命令行來測試 API。
  • 內置支持請求/響應模式的通訊。
  • HTTP 對防火牆友好。
  • 不須要中間代理,簡化了系統架構。

  不足之處包括:

  • 只支持請求/響應模式交互。儘管可使用 HTTP 通知,可是服務端必須一直髮送 HTTP 響應。
  • 因爲客戶端和服務端直接通訊(沒有代理或者緩衝機制),在交互期間必須都保持在線。
  • 客戶端必須知道每一個服務實例的 URL。如前篇文章「API 網關」所述,這也是個煩人的問題。客戶端必須使用服務實例發現機制。

  開發者社區最近從新認識到了 RESTful API 接口定義語言的價值,因而誕生了包括 RAML 和 Swagger 在內的服務框架。Swagger 這樣的 IDL 容許定義請求和響應消息的格式,而 RAML 容許使用 JSON Schema 這種獨立的規範。對於描述 API,IDL 一般都有工具從接口定義中生成客戶端存根和服務端框架。

 4.3.二、Thrift

  Apache Thrift 是一個頗有趣的 REST 的替代品,實現了多語言 RPC 客戶端和服務端調用。Thrift 提供了一個 C 風格的 IDL 定義 API。經過 Thrift 編譯器可以生成客戶端存根和服務端框架。編譯器能夠生成多種語言的代碼,包括 C++、Java、Python、PHP、Ruby, Erlang 和 Node.js。

  Thrift 接口由一個或多個服務組成,服務定義與 Java 接口相似,是一組強類型方法的集合。Thrift 可以返回(可能無效)值,也能夠被定義爲單向。返回值的方法可以實現交互的請求/響應模式。客戶端等待響應,可能會拋出異常。單向方法與交互的通知模式相對應。服務端不會發送響應。

  Thrift 支持 JSON、二進制和壓縮二進制等多種消息格式。因爲解碼更快,二進制比 JSON 更高效;如名稱所稱,壓縮二進制格式能夠提供更高級別的壓縮效率;同時 JSON 則易讀。Thrift 也可以讓你選擇傳輸協議,包括原始 TCP 和 HTTP。原始 TCP 比 HTTP 更高效,然而 HTTP 對於防火牆、瀏覽器和使用者來講更友好。

4.四、消息格式

  瞭解 HTTP 和 Thrift 後,咱們要考慮消息格式的問題。若是使用消息系統或者 REST,就須要選擇消息格式。像 Thrift 這樣的 IPC 機制可能只支持少許消息格式,或許只支持一種格式。不管哪一種狀況,使用跨語言的消息格式很是重要。即使你如今使用單一語言實現微服務,但頗有可能將來須要用到其它語言。

  目前有文本和二進制這兩種主要的消息格式。文本格式包括 JSON 和 XML。這種格式的優勢在於不只可讀,並且是自描述的。在 JSON 中,對象的屬性是名稱-值對的集合。與此相似,在 XML 中,屬性則表示爲命名的元素和值。消費者可以從中選擇感興趣的值同時忽略其它部分。相應地,對消息格式的小幅度修改也能容易地向後兼容。

  XML 的文檔結構由 XML schema 定義。隨着時間發展,開發者社區意識到 JSON 也須要一個相似的機制。方法之一是使用 JSON Schema,要麼獨立使用,要麼做爲 Swagger 這類 IDL 的一部分。

  文本消息格式的一大缺點是消息會變得冗長,特別是 XML。因爲消息是自描述的,因此每一個消息都包含屬性和值。另一個缺點是解析文本的負擔過大。因此,你可能須要考慮使用二進制格式。

  二進制的格式也有不少。若是使用的是 Thrift RPC,那可使用二進制 Thrift。若是選擇消息格式,經常使用的還包括 Protocol Buffers 和 Apache Avro,兩者都提供類型 IDL 來定義消息結構。差別之處在於 Protocol Buffers 使用添加標記的字段(tagged fields),而 Avro 消費者須要瞭解模式來解析消息。

總結

微服務必須使用進程間通訊機制來交互。在設計服務的通訊模式時,你須要考慮幾個問題:服務如何交互,每一個服務如何標識 API,如何升級 API,以及如何處理局部失敗。微服務架構異步消息機制和同步請求/響應機制這兩類 IPC 機制可用。在下一篇文章中,咱們將會討論微服務架構中的服務發現問題。

 原文地址:

https://www.nginx.com/blog/service-discovery-in-a-microservices-architecture/

http://blog.daocloud.io/microservices-3/

相關文章
相關標籤/搜索