1. 進程間通信(RPC)
在單體式應用中,各個模塊之間的調用是通過編程語言級別的方法或者函數來實現的。但是一個基於微服務的分布式應用是運行在多台機器上的。
一般來說,每個服務實例都是一個進程。因此,如下圖所示,服務之間的交互必須通過進程間通信(RPC)來實現。

2. 客戶端與微服務的交互模式
交互模式可以從兩個維度進行歸類。(1)第一個維度是一對一還是一對多:
- 一對一:每個客戶端請求有一個服務實例來響應。
- 一對多:每個客戶端請求有多個服務實例來響應。
(2)第二個維度是這些交互式同步還是異步:
- 同步模式:客戶端請求需要服務端即時響應,甚至可能由於等待而阻塞。
- 異步模式:客戶端請求不會阻塞進程,服務端的響應可以是非即時的。
(3)一對一的交互模式有以下幾種方式:
- 請求/響應:一個客戶端向服務器端發起請求,等待響應。客戶端期望此響應即時到達。在一個基於線程的應用中,等待過程可能造成線程阻塞。
- 通知(也就是常說的單向請求):一個客戶端請求發送到服務端,但是並不期望服務端響應。
- 請求/異步響應:客戶端發送請求到服務端,服務端異步響應請求。客戶端不會阻塞,而且被設計成默認響應不會立刻到達。
(4)一對多的交互模式有以下幾種方式:
- 發布/訂閱模式:客戶端發布通知消息,被零個或者多個感興趣的服務消費。
- 發布/異步響應模式:客戶端發布請求消息,然后等待從感興趣服務發回的響應。
下表顯示了不同交互模式:

每個服務都是以上這些模式的組合,對某些服務,一個RPC機制就足夠了;而對另外一些服務則需要多種RPC機制組合。下圖展示了在一個打車服務請求中服務之間是如何通信的。

上圖中的服務通信使用了通知、請求/響應、發布/訂閱等方式。例如,乘客通過移動端給『行程管理服務』發送通知,希望申請一次出租服務。『行程管理服務』發送請求/響應消息給『乘客服務』以確認乘客賬號是有效的。緊接着創建此次行程,並用發布/訂閱交互模式通知其他服務,包括定位可用司機的調度服務。
3.客戶端與服務端接口API
不管選擇了什么樣的RPC機制,重要的是使用某種交互式定義語言(IDL)來精確定義一個服務的接口API。接口API的定義實質上依賴於選擇哪種RPC。如果使用消息機制,API則由消息頻道(channel)和消息類型構成;如果選擇使用HTTP機制,API則由URL和請求、響應格式構成。API的變化是不可避免的,微小的改變可以和之前版本兼容。比如,你可能只是為某個請求和響應添加了一個屬性。這時,客戶端使用舊版API應該也能和新版本一起工作。但是有時候,API需要進行大規模的改動,並且可能與之前版本不兼容。因為你不可能強制讓所有的客戶端立即升級,所以支持老版本客戶端的服務還需要再運行一段時間。如果你正在使用基於基於HTTP機制的RPC,例如REST,一種解決方案是把版本號嵌入到URL中。每個服務都可能同時處理多個版本的API。或者,你可以部署多個實例,每個實例負責處理一個版本的請求。
4.容錯處理
分布式系統中部分失敗是普遍存在的問題。因為客戶端和服務端是都是獨立的進程,一個服務端有可能因為故障或者維護而停止服務,或者此服務因為過載而停止或者反應很慢。假設推薦服務無法響應請求,那客戶端就會由於等待響應而阻塞,這不僅會給客戶帶來很差的體驗,而且在很多應用中還會占用很多資源,比如線程,以至於到最后由於等待響應被阻塞的客戶端越來越多,線程資源被耗費完了。如下圖所示:

Netfilix Hystrix提供了一個比較好的解決方案,具體的應對措施包括:
- 網絡超時:當等待響應時,不要無限期的阻塞,而是采用超時策略。使用超時策略可以確保資源不會無限期的占用。
- 限制請求的次數:可以為客戶端對某特定服務的請求設置一個訪問上限。如果請求已達上限,就要立刻終止請求服務。
- 斷路器模式(Circuit Breaker Pattern):記錄成功和失敗請求的數量。如果失效率超過一個閾值,觸發斷路器使得后續的請求立刻失敗。如果大量的請求失敗,就可能是這個服務不可用,再發請求也無意義。在一個失效期后,客戶端可以再試,如果成功,關閉此斷路器。
- 提供回滾:當一個請求失敗后可以進行回滾邏輯。例如,返回緩存數據或者一個系統默認值。
5.RPC實現技術
服務之間的通信采用同步的請求/響應模式,可以選擇基於HTTP的REST或者Thrift。服務之間的通信采用異步的、基於消息的通信模式,可以選擇AMQP或者STOMP。大量開源消息中間件可供選擇,比如RabbitMQ、Apache Kafka、Apache ActiveMQ和NSQ。消息格式可以選擇基於文本的,比如 JSON和XML;二進制格式(效率更高)的,比如Avro和Protocol Buffer。
5.1 采用異步的,基於消息的通信模式
下圖展示了打車軟件如何使用消息發布/訂閱:

行程管理服務在發布-訂閱channel內創建一個行程消息,並通知調度服務有一個新的行程請求,調度服務發現一個可用的司機然后向發布-訂閱channel寫入司機建議消息(Driver Proposed message)來通知其他服務。
5.2 采用同步的,基於請求/響應的通信模式
下圖展示了打車軟件如何使用REST:

乘客通過移動端向行程管理服務的/trips資源提交了一個POST請求。行程管理服務收到請求之后,會發送一個GET請求到乘客管理服務以獲取乘客信息。當確認乘客信息之后,緊接着會創建一個行程,並向移動端返回201狀態碼響應。
使用基於HTTP的協議的好處:
- HTTP非常簡單並且大家都很熟悉。
- 可以使用瀏覽器擴展(比如Postman)或者curl之類的命令行來測試API。
- 內置支持請求/響應模式的通信。
- HTTP對防火牆友好。
- 不需要中間代理,簡化了系統架構。
使用基於HTTP的協議的不足之處:
- 只支持請求/響應模式交互。可以使用HTTP通知,但是服務端必須一直發送HTTP響應才行。
- 因為客戶端和服務端直接通信(沒有代理或者buffer機制),在交互期間必須都在線。
- 客戶端必須知道每個服務實例的URL。客戶端必須使用服務實例發現機制。