在單體式應用中,各個子產品之間的調用是通過程式設計語言級别的方法或者函數來實作的。但是一個基于微服務的分布式應用是運作在多台機器上的。一般來說,每個服務執行個體都是一個程序。是以,如下圖所示,服務之間的互動必須通過程序間通信(IPC)來實作。

後面我們将會詳細介紹IPC技術,現在我們先來看下設計相關的問題。
當為某一個服務選擇IPC時,首先需要考慮服務之間如何互動。用戶端和伺服器之間有很多的互動模式,我們可以從兩個次元進行歸類。第一個次元是一對一還是一對多:
• 一對一:每個用戶端請求有一個服務執行個體來響應。
• 一對多:每個用戶端請求有多個服務執行個體來響應
第二個次元是這些互動式同步還是異步:
• 同步模式:用戶端請求需要服務端即時響應,甚至可能由于等待而阻塞。
• 異步模式:用戶端請求不會阻塞程序,服務端的響應可以是非即時的。
下表顯示了不同互動模式:
一對一的互動模式有以下幾種方式:
• 請求/響應:一個用戶端向伺服器端發起請求,等待響應。用戶端期望此響應即時到達。在一個基于線程的應用中,等待過程可能造成線程阻塞。
• 通知(也就是常說的單向請求):一個用戶端請求發送到服務端,但是并不期望服務端響應。
• 請求/異步響應:用戶端發送請求到服務端,服務端異步響應請求。用戶端不會阻塞,而且被設計成預設響應不會立刻到達。
一對多的互動模式有以下幾種方式:
• 釋出/ 訂閱模式:用戶端釋出通知消息,被零個或者多個感興趣的服務消費。
• 釋出/異步響應模式:用戶端釋出請求消息,然後等待從感興趣服務發回的響應。
每個服務都是以上這些模式的組合,對某些服務,一個IPC機制就足夠了;而對另外一些服務則需要多種IPC機制組合。下圖展示了在一個打車服務請求中服務之間是如何通信的。
上圖中的服務通信使用了通知、請求/響應、釋出/訂閱等方式。例如,乘客通過移動端給『行程管理服務』發送通知,希望申請一次出租服務。『行程管理服務』發送請求/響應消息給『乘客服務』以确認乘客賬号是有效的。緊接着建立此次行程,并用釋出/訂閱互動模式通知其他服務,包括定位可用司機的排程服務。
現在我們了解了互動模式,接下來我們一起來看看如何定義API。
在本文後半部分你将會看到,API定義實質上依賴于選擇哪種IPC。如果使用消息機制,API則由消息頻道(channel)和消息類型構成;如果選擇使用HTTP機制,API則由URL和請求、響應格式構成。後面将會較長的描述IDL。
但是有時候,API需要進行大規模的改動,并且可能與之前版本不相容。因為你不可能強制讓所有的用戶端立即更新,是以支援老版本用戶端的服務還需要再運作一段時間。如果你正在使用基于基于HTTP機制的IPC,例如REST,一種解決方案是把版本号嵌入到URL中。每個服務都可能同時處理多個版本的API。或者,你可以部署多個執行個體,每個執行個體負責處理一個版本的請求。
為了預防這種問題,設計服務時候必須要考慮部分失敗的問題。
• 網絡逾時:當等待響應時,不要無限期的阻塞,而是采用逾時政策。使用逾時政策可以確定資源不會無限期的占用。
• 限制請求的次數:可以為用戶端對某特定服務的請求設定一個通路上限。如果請求已達上限,就要立刻終止請求服務。
• 提供復原:當一個請求失敗後可以進行復原邏輯。例如,傳回緩存資料或者一個系統預設值。
現在有很多不同的IPC技術。服務之間的通信可以使用同步的請求/響應模式,比如基于HTTP的REST或者Thrift。另外,也可以選擇異步的、基于消息的通信模式,比如AMQP或者STOMP。除以之外,還有其它的消息格式供選擇,比如JSON和XML,它們都是可讀的,基于文本的消息格式。當然,也還有二進制格式(效率更高)的,比如Avro和Protocol Buffer。接下來我們将會讨論異步的IPC模式和同步的IPC模式,首先來看異步的。
當使用基于異步交換消息的程序通信方式時,一個用戶端通過向服務端發送消息送出請求。如果服務端需要回複,則會發送另外一個獨立的消息給用戶端。因為通信是異步的,用戶端不會因為等待而阻塞,相反,用戶端理所當然的認為響應不會立刻接收到。
下圖展示了打車軟體如何使用釋出/訂閱:
行程管理服務在釋出-訂閱channel内建立一個行程消息,并通知排程服務有一個新的行程請求,排程服務發現一個可用的司機然後向釋出-訂閱channel寫入司機建議消息(Driver Proposed message)來通知其他服務。
使用消息機制有很多優點:
• 解耦用戶端和服務端:用戶端隻需要将消息發送到正确的channel。用戶端完全不需要了解具體的服務執行個體,更不需要一個發現機制來确定服務執行個體的位置。
• Message Buffering:在一個同步請求/響應協定中,例如HTTP,所有的用戶端和服務端必須在互動期間保持可用。而在消息模式中,消息broker将所有寫入channel的消息按照隊列方式管理,直到被消費者處理。也就是說,線上商店可以接受客戶訂單,即使下單系統很慢或者不可用,隻要保持下單消息進入隊列就好了。
• 彈性用戶端-服務端互動:消息機制支援以上說的所有互動模式。
• 直接程序間通信:基于RPC機制,試圖喚醒遠端服務看起來跟喚醒本地服務一樣。然而,因為實體定律和部分失敗可能性,他們實際上非常不同。消息使得這些不同非常明确,開發者不會出現問題。
然而,消息機制也有自己的缺點:
• 額外的操作複雜性:消息系統需要單獨安裝、配置和部署。消息broker(代理)必須高可用,否則系統可靠性将會受到影響。
• 實作基于請求/響應互動模式的複雜性:請求/響應互動模式需要完成額外的工作。每個請求消息必須包含一個回複管道ID和相關ID。服務端發送一個包含相關ID的響應消息到channel中,使用相關ID來将響應對應到送出請求的用戶端。也許這個時候,使用一個直接支援請求/響應的IPC機制會更容易些。
現在我們已經了解了基于消息的IPC,接下來我們來看看基于請求/響應模式的IPC。
當使用一個同步的,基于請求/響應的IPC機制,用戶端向服務端發送一個請求,服務端處理請求,傳回響應。一些用戶端會由于等待服務端響應而被阻塞,而另外一些用戶端也可能使用異步的、基于事件驅動的用戶端代碼(Future或者Rx Observable的封裝)。然而,不像使用消息機制,用戶端需要響應及時傳回。這個模式中有很多可選的協定,但最常見的兩個協定是REST和Thrift。首先我們來看下REST。
REST
當需要一個整體的、重視子產品互動可擴充性、接口概括性、元件部署獨立性和減小延遲、提供安全性和封裝性的系統時,REST可以提供這樣一組滿足需求的架構。
下圖展示了打車軟體是如何使用REST的。
乘客通過移動端向行程管理服務的<code>/trips</code>資源送出了一個POST請求。行程管理服務收到請求之後,會發送一個GET請求到乘客管理服務以擷取乘客資訊。當确認乘客資訊之後,緊接着會建立一個行程,并向移動端傳回201(譯者注:狀态碼)響應。
第一個層次(Level 0)的 Web 服務隻是使用 HTTP 作為傳輸方式,實際上隻是遠端方法調用(RPC)的一種具體形式。SOAP 和 XML-RPC 都屬于此類。
第二個層次(Level 1)的 Web 服務引入了資源的概念。每個資源有對應的辨別符和表達。
第三個層次(Level 2)的 Web 服務使用不同的 HTTP 方法來進行不同的操作,并且使用 HTTP 狀态碼來表示不同的結果。如 HTTP GET 方法來擷取資源,HTTP DELETE 方法來删除資源。
第四個層次(Level 3)的 Web 服務使用 HATEOAS。在資源的表達中包含了連結資訊。用戶端可以根據連結來發現可以執行的動作。
使用基于HTTP的協定有如下好處:
• HTTP非常簡單并且大家都很熟悉。
• 内置支援請求/響應模式的通信。
• HTTP對防火牆友好的。
• 不需要中間代理,簡化了系統架構。
不足之處包括:
• 隻支援請求/響應模式互動。可以使用HTTP通知,但是服務端必須一直發送HTTP響應才行。
• 因為用戶端和服務端直接通信(沒有代理或者buffer機制),在互動期間必須都線上。
Thrift
Thrift接口包括一個或者多個服務。服務定義類似于一個JAVA接口,是一組方法。Thrift方法可以傳回響應,也可以被定義為單向的。傳回值的方法其實就是請求/響應類型互動模式的實作。用戶端等待響應,并可能抛出異常。單向方法對應于通知類型的互動模式,服務端并不傳回響應。
Thrift支援多種消息格式:JSON、二進制和壓縮二進制。二進制比JSON更高效,因為二進制解碼更快。同樣原因,壓縮二進制格式可以提供更進階别的壓縮效率。JSON,是易讀的。Thrift也可以在裸TCP和HTTP中間選擇,裸TCP看起來比HTTP更加有效。然而,HTTP對防火牆,浏覽器和人來說更加友好。
了解完HTTP和Thrift後,我們來看下消息格式方面的問題。如果使用消息系統或者REST,就可以選擇消息格式。其它的IPC機制,例如Thrift可能隻支援部分消息格式,也許隻有一種。無論哪種方式,我們必須使用一個跨語言的消息格式,這非常重要。因為指不定哪天你會使用其它語言。
有兩類消息格式:文本和二進制。文本格式的例子包括JSON和XML。這種格式的優點在于不僅可讀,而且是自描述的。在JSON中,一個對象就是一組鍵值對。類似的,在XML中,屬性是由名字和值構成。消費者可以從中選擇感興趣的元素而忽略其它部分。同時,小幅度的格式修改可以很容器向後相容。
XML文檔結構是由XML schema定義的。随着時間發展,開發者社群意識到JSON也需要一個類似的機制。一個選擇是使用JSON Schema,要麼是獨立的,要麼是例如Swagger的IDL。
基于文本的消息格式最大的缺點是消息會變得冗長,特别是XML。因為消息是自描述的,是以每個消息都包含屬性和值。另外一個缺點是解析文本的負擔過大。是以,你可能需要考慮使用二進制格式。
微服務必須使用程序間通信機制來互動。當設計服務的通信模式時,你需要考慮幾個問題:服務如何互動,每個服務如何辨別API,如何更新API,以及如何處理部分失敗。微服務架構有兩類IPC機制可選,異步消息機制和同步請求/響應機制。在下一篇文章中,我們将會讨論微服務架構中的服務發現問題。
原文釋出時間為:2015-08-01
本文作者:hokingyang
本文來自雲栖社群合作夥伴DockerOne,了解相關資訊可以關注DockerOne。
原文标題:微服務實戰(三):深入微服務架構的程序間通信