《微服務架構設計模式》讀書筆記 | 第3章 微服務架構中的進程間通訊


前言

這是一本關於微服務架構設計方面的書,這是本人閱讀的學習筆記。首先對一些符號作些說明:數據庫

()爲補充,通常是書本里的內容;
[]符號爲筆者筆注;編程

微服務架構將應用程序構建爲一組服務,這些服務必須常常協做才能處理各類外部請求。而服務的實例一般是在多臺機器上運行的進程,因此它們必須使用進程間通訊進行交互。api

當前有多種進程間通訊機制,比較流行的是REST(使用JSON)。選擇合適的進程間通訊機制是一個重要的架構決策,它影響應用程序的可用性。瀏覽器


1. 微服務架構中的進程間通訊概述

進程間通訊技術有:基於同步請求/響應、異步的基於消息的通訊機制等。緩存

1.1 交互方式的兩個維度

  • 第一個維度
    • 一對一:每一個客戶端請求由一個服務實例來處理;
    • 一對多:每一個客戶端請求由多個服務實例來處理;
  • 第二個維度
    • 同步模式:客戶端請求須要服務端實時響應,客戶端等待響應時可能致使堵塞;
    • 異步模式:客戶端請求不會阻塞進程,服務端的響應能夠是非實時的;

交互方式的兩個維度

1.2 交互方式的類型

  • 一對一交互
    • 請求/響應:一個客戶端向服務端發起請求,等待響應;客戶端指望服務端很快就會發送響應。在一個基於線程的應用中,等待過程可能形成線程阻塞。這樣的方式會致使服務的緊耦合;
    • 異步請求/響應:客戶端發送請求到服務端,服務端異步響應請求。客戶端在等待時不會阻塞線程,由於服務端響應不會立刻返回;
    • 單向通知:客戶端的請求發送到服務端,可是並不指望服務端作出任何響應;
  • 一對多交互
    • 發佈/訂閱方式:客戶端發佈通知消息,被零個或多個感興趣的服務訂閱;
    • 發佈/異步響應方式:客戶端發佈請求消息,而後等待從感興趣的服務發回的響應;

1.3 API的演化

  • 語義化版本控制:用於指定如何使用版本號,而且以正確的方式遞增版本。其由3部分組成:
    • MAJOR:對API進行不兼容的修改時;
    • MINOR:對API進行向後兼容的加強時;
    • PATCH:進行向後兼容的錯誤修復時;
    • 規範:MAJOR.MINOR.PATCH
  • 進行次要而且向後兼容的改變:對ADP的附加修改更換或功能加強。其包括:
    • 添加可選屬性
    • 向響應添加屬性
    • 添加新操做
  • 進行主要而且不向後兼容的版本:須要服務在一段時間內同時支持新舊版本的API時;

1.4 消息的格式

消息格式會影響進程間通訊的效率、API的可用性和可演化新。使用跨語言的消息格式尤其重要;服務器

  • 基於文本的消息格式
    • 舉例:JSON、XML;
    • 好處:可讀性高、自描述性,有良好的向後兼容性 [消息接收方只需挑選他們感興趣的值,忽略其餘];
    • 弊端:信息冗長,解析文本須要額外的性能效率開銷;
  • 二進制消息格式
    • 舉例:Tars、Protocol Buffers、Avro;
    • 好處:提供強類型定義的IDL(接口描述文件),用於定義消息;編譯器會根據這些格式生成序列化和反序列化代碼;
    • 弊端:不得不採用API優先的方法進行服務設計

2. 基於同步遠程過程調用模式的通訊

2.1 遠程過程調用RPI

指客戶端使用同步的遠程過程調用協議(如REST)來調用服務。網絡

遠程過程調用工做原理

圖解:客戶端業務邏輯調用代理接口,這個接口由遠程過程調用代理適配器類實現。遠程過程調用代理向服務器發送請求,該請求由遠程過程調用服務器適配器類處理,該類經過接口調用服務的業務邏輯。而後它將恢復發送回遠程過程調用代理,該代理將結果返回給客戶端的業務邏輯。架構

  • 代理接口:一般是封裝底層通訊協議,以下面介紹的REST與gRPC。

2.2 REST通訊協議的特色及優缺點

REST是一種(老是)使用HTTP協議的進程間通訊機制。併發

特色負載均衡

  • REST使用HTTP動詞來操做資源,使用URL引用這些資源;
  • 資源一般使用XML文檔或JSON對象的形式,也可使用其餘格式(二進制等);
  • REST的成熟模型有:有4個層次(P71);
  • REST API:最流行的REST IDL是Open API規範,它是從Swagger開源項目發展而來的;
  • REST API的挑戰
    • 在一個請求中獲取多個資源的挑戰:指如何在單個請求中檢索多個相關對象;
    • 吧操做映射爲HTTP動詞的挑戰:指一個HTTP動詞可能對應多種方法,如PUT請求更新訂單可能包括取消訂單、修改訂單等;

好處

  • 很是簡單,你們比較熟悉;
  • 可使用瀏覽器擴展(如Postman插件)或者curl之類的命令行測試HTTP API;
  • 直接支持請求/響應方式的通訊;
  • HTTP對防火牆友好;
  • 不須要中間代理,簡化系統架構;

弊端

  • 只支持請求/響應方式的通訊;
  • 可能致使可用性下降。因爲客戶端和服務直接通訊而沒有代理來緩衝消息,所以它們必須在REST API調用期間保持在線;
  • 客戶端必須知道服務實例的位置(URL)。客戶端必須使用所謂的服務發現機制來定位服務實例;
  • 在單個請求中獲取多個資源具備挑戰性;
  • 有時很難將多個更新操做映射到HTTP動詞;

2.3 gRPC通訊協議的特色及優缺點

gRPC是一個用於編寫跨語言客戶端和服務端的框架,是一種二進制協議。

特色

  • gRPC API由一個或多個服務和請求/響應消息定義組成;
  • 服務定義相似Java接口,是強類型方法的集合;
  • 使用Protocol Buffers做爲消息格式,是一種高效且緊湊的二進制格式,是一種標記格式;
    • 所以gRPC使API可以在保持向後兼容的同時進行變動;

好處

  • 設計具備複雜更新操做的API很是簡單;
  • 具備高效、緊湊的進程間通訊機制,尤爲是在交換大量消息時;
  • 支持在遠程過程調用和消息傳遞過程當中使用雙向流式消息方式;
  • 實現了客戶端和用各類語言編寫的服務端之間的互操做性;

弊端

  • 與基於REST/JSON的API機制相比,JavaScript客戶端使用基於gRPC的API須要作更多的工做;
  • 舊式防火牆可能不支持HTTP/2;

2.4 同步通訊下的局部故障風險

客戶端和服務端是獨立的進程,服務端極可能沒法在有限的時間內對客戶端的請求做出響應。

同步通訊下的局部故障風險
圖解:當Order Service無響應時,OrderServiceProxy將無限期地阻塞,等待響應。會消耗時間、浪費線程等資源。最終API Gateway將資源消耗,沒法處理請求,整個API不可用。

解決方法是

  • 必須讓遠程過程調用代理(如OrderServiceProxy)有正確處理無響應服務的能力;
  • 須要決定如何從失敗的遠程服務中恢復;

2.5 解決局部故障的思路與方法

  • 開發可靠地遠程過程調用代理:使用Netflix描述的方法,能夠包括如下機制的組合;
    • 網絡超時:在等待針對請求的響應時,不要作成無限阻塞,而是設定一個超時,用來保證不會一直在無響應的請求上浪費資源;
    • 限制客戶端向服務器發出請求的數量:把客戶端可以向特定服務發起的請求設置一個上限,若是請求達到上限,就讓請求馬上失敗;
    • 斷路器模式:監控客戶端發出請求的成功和失敗數量,若是失敗的比例超過必定的閾值,就啓動斷路器,讓後續調用當即失敗。若是大量請求都以失敗了結,說明被調服務不可用。通過必定時間後,客戶端繼續嘗試,若是調用成功,則移除斷路器;
  • 從服務失效故障中恢復
    • 能夠只是服務向其客戶端返回錯誤;
    • 返回備用值(如默認值或緩存響應);

2.6 應用層服務發現模式

服務及其客戶直接與服務註冊表交互;

應用層服務發現模式工做原理

  • 服務實例使用服務註冊表註冊其網絡位置。客戶端首先經過查詢服務註冊表獲取服務實例列表來調用服務,而後它向其中一個實例發送請求;
  • 這種服務發現是如下兩種模式的組合:
    • 自注冊模式:服務實例向服務註冊表註冊本身;
      • 能夠提供運行狀態檢查URL(「心跳」功能,服務註冊表按期調用該端點驗證服務實例是否正常且可用於處理請求);
    • 客戶端發現模式:客戶端從服務註冊表檢索可用服務實例的列表,並在它們之間進行負載均衡;
      • 爲了提升性能,客戶端可能會緩存服務實例;
  • 業界有Netflix開發的Eureka組件,一個高可用的服務註冊表;Pivotal開發的SpringCloud
    使相關組件使用很是簡單;

2.7 平臺層服務發現模式

經過部署基礎設施來處理服務發現;

平臺層服務發現模式工做原理

  • 部署平臺包括一個服務註冊表,用於跟蹤已部署服務的IP地址;
  • 部署平臺爲每一個服務提供DNS名稱、虛擬IP(VIP)地址和解析爲VIP地址的DNS名稱;
  • 這種服務發現是如下兩種模式的組合:
    • 第三方註冊模式:由第三方負責(稱爲註冊服務器)處理註冊,而不是服務自己先服務註冊表註冊本身;
    • 服務端發現模式:客戶端向DNS名稱發出請求,對該DNS名稱的請求被解析到路由器,路由器查詢服務註冊表並對請求進行負載均衡;
  • 業界有Docker與Kubernetes,都內置有服務註冊表與服務發現機制;

3. 基於異步消息模式的通訊

使用消息機制時,服務之間的通訊採用異步交換消息的方式完成。

基於消息機制的應用程序一般採用消息代理;另外一種選擇是使用無代理架構。

3.1 關於消息

消息由消息頭部和消息主體組成;

  • 消息頭部
    • 標題:名稱與值對;
    • 消息ID:消息傳遞基礎惟一ID;
    • 返回地址:指定發送回覆的消息通道;
  • 消息主體:以文本或二進制格式發送的數據;
    • 文檔:包含數據的通用消息。接受者決定如何解釋它。對命令式消息的回覆是文檔消息的一種應用場景;
    • 命令:一條等同於RPC請求的消息。它指定要調用的操做及其參數;
    • 事件:表示發送方這一端發生了重要的事件。事件一般是領域事件,表示領域對象的狀態更改;

3.2 關於消息通道

消息通道工做原理
有如下兩種類型的消息通道:

  • 點對點通道
    • 向正在從通道讀取的一個消費者傳遞消息;
    • 如:命令式消息一般經過點對點通道發送;
  • 發佈 - 訂閱通道
    • 將一條消息發送給全部訂閱的接收方;
    • 如:事件式消息一般經過發佈 - 訂閱通道發送;

3.3 使用消息機制實現交互方式

介紹下面四種交互方式的消息機制:

  • 實現單向通知
    • 客戶端將消息(一般是命令式消息)發送到服務所擁有的點對點通道;
    • 服務訂閱該通道並處理該消息,但服務不會發回回復;
  • 實現發佈/訂閱
    • 客戶端將消息發佈到由多個接收方讀取的發佈/訂閱通道;
    • 發佈領域事件的服務擁有本身的發佈/訂閱通道,通道名稱每每派生自領域類;
    • 如:Order Service將Order事件發佈到Order通道;Delivery Service將Delivery事件發佈到Delivery通道;
  • 實現發佈/異步響應
    • 一種更高級的交互方式,將發佈/訂閱與請求/響應這兩種方式的元素組合實現;
    • 客戶端發佈一條消息,在消息的頭部中指定回覆通道。這個通道同時也是一個發佈 - 訂閱通道;
    • 消費者將包含相關性ID的回覆消息寫入回覆通道;
    • 客戶端經過使用相關性ID來收集響應,以此將回復消息與請求進行匹配;
  • 實現請求/響應和異步請求/響應
    • 客戶端發送請求,服務會發回回復;
    • 客戶端必須告知服務發送回覆消息的位置,而且必須將回復消息與請求匹配;
      • 即:客戶端發送具備回覆通道頭部的命令式消息。服務器將回復消息寫入回覆通道,該回復消息包含與消息標識符具備相同的相關性ID。客戶端使用相關性ID將回復消息與請求匹配;
    • 因爲客戶端和服務端使用消息機制進行通訊,所以交互本質上是異步的;
    • 工做原理圖以下:

實現請求/響應交互方式工做原理圖

3.4 爲基於消息機制的服務API建立API規範

服務的異步API規範必須制定消息通道的名稱、經過每一個通道交換的消息類型及其格式。

服務的異步API

  • 服務的異步API包含供客戶端調用的操做和由服務對外發布的事件;
  • (記錄異步操做)可使用如下兩種不一樣交互方式之一調用服務的操做:
    • 請求/異步響應式API:包括服務端命令消息通道、服務接受的命令式消息的具體類型和格式,以及服務發送的回覆消息的類型和格式;
    • 單向通知式API:包括服務的命令消息通道,以及服務接受的命令式消息的具體類型和格式;
  • (記錄事件發佈)服務還可使用發佈/訂閱的方式對外發布事件;
    • 此API風格等規範包括事件通道以及服務發佈到通道的事件式消息的類型和格式;

3.5 無代理消息的利弊

在無代理的架構中,服務能夠直接交換信息。

好處

  • 容許更輕的網絡流量和更低的延遲,由於沒有中間代理過程;
  • 消除了消息代理可能成爲性能瓶頸或單點故障的可能性;
  • 具備較低的操做複雜性,由於不須要設置和維護消息代理;

弊端

  • 服務須要了解彼此位置,所以必須使用服務發現機制;
  • 下降可用性,由於在交換消息時,信息的接收方和發送方必須同時在線;
  • 在實現例如確保消息可以成功投遞這些複雜功能時的挑戰性更大;

舉例

  • ZeroMQ:一種流行的無代理消息技術;

無代理與基於代理的架構

3.6 基於代理消息的利弊

消息代理是全部消息的中介節點;發送方將消息寫入消息代理,消息代理將消息發送給接收方。

好處

  • 鬆耦合;
  • 消息緩存:消息代理能夠在消息被處理以前一直緩存消息;
  • 靈活的通訊:消息代理支持前面提到的全部交互方式;
  • 明確的進程間通訊

弊端

  • 潛在的性能瓶頸:解決方法 - 橫向擴展;
  • 潛在的單點故障:解決辦法 - 大多數現代消息代理是高可用的;
  • 額外的操做複雜性:消息系統必須是一個獨立安裝、配置和運維的系統組件;

舉例

  • 流行的開源消息代理:Apache ActiveMQ(JMS)、RabbitMQ(AMQP)、Apache Kafka;
  • 基於雲的消息服務:AWS Kinesis、AWS SQS;
  • 上述除了AWS SQS外都支持點對點和發佈 - 訂閱通道;AWS SQS只支持點對點通道;

每一個消息代理都有本身的特點

3.7 選擇消息代理須要考慮的因素

  • 支持的編程語言
  • 支持的信息標準
  • 消息排序:消息代理是否可以保留消息的排序;
  • 投遞保證:消息代理提供怎樣的消息投遞保證;
  • 持久性
  • 耐久性:若是接收方從新鏈接到消息代理,它是否會收到斷開鏈接時發送的消息;
  • 可擴展性
  • 延遲
  • 競爭性(併發)接收方:消息代理是否支持競爭性接收方;

3.8 處理併發和消息順序

問題描述:在橫向擴展多個消息接收方的實例的狀況下,消息的順序可能會錯位。

解決方法:使用分片消息通道擴展接收方;

使用分片消息通道擴展接收方工做原理圖
圖解

  • 分片通道由兩個或多個分片組成,每一個分片的行爲相似於一個通道;
  • 發送方在消息頭部指定分片鍵,一般是任意字符串或字節序列。消息代理使用分片鍵將消息分配給特定的分片;
    • 如:經過計算分片鍵的散列來選擇分片;
  • 消息代理將接收方的多個實例組合在一塊兒,並將他們視爲相同的邏輯接收方;
    • 如:Apache Kafka使用術語消費者組;消息代理將每一個分片分配給單個接收器;它在接收方啓動和關閉時從新分配分片;

3.9 處理重複消息

問題描述:客戶端、網絡或消息代理的故障可能致使消息被屢次傳遞。

有如下兩種解決辦法:

  • 編寫冪等消息處理器
    • 冪等操做特色:任意屢次執行所產生的影響均與一次執行的影響相同;
  • 跟蹤消息並丟棄重複消息
    • 將消息處理程序註冊進應用程序表(NoSQL)【第七章介紹】;
    • 使用message id跟蹤消息並丟棄重複消息,以下圖:

使用message id跟蹤消息並丟棄重複消息

3.10 事務性消息

  • 使用數據庫表做爲消息隊列
    • 事務性發件箱:經過將事件或消息保存在數據庫OUTBOX表中,將其做爲數據庫事務是一部分發布;
      使用數據庫表做爲消息隊列
  • 經過輪詢模式發佈事件
    • 輪詢發佈數據:經過輪詢數據庫中的發件箱發佈消息;
    • 小規模下運行良好,弊端在於常常輪詢數據庫會形成較大開銷;
  • 使用事務日誌拖尾模式發佈事件
    • 事務日誌拖尾:經過拖尾數據日誌發佈對數據庫所作的修改;
    • 一些行業案例:Debezium、Linkedln Databus、DynamoDB streams、Eventuate Tram;
    • 下圖解:每次應用程序提交到數據庫的更新都對應着數據庫事務日誌中的一個條目;事務日誌挖掘器能夠讀取事務日誌,把每條跟消息有關的記錄發送給消息代理;

事務日誌拖尾模式


3.11 消息相關的類庫和框架

服務須要使用庫來發送和接收消息。

有兩種方法:

  • 使用消息代理的客戶端庫,問題有:
    • 客戶端庫將發佈消息的業務邏輯耦合到消息代理API;
    • 客戶端庫一般只提供發送和接收消息的基本機制,不支持更高級別的交互方式;
    • 消息代理的客戶端庫一般很是底層,須要多行代碼才能發送/接收消息;
  • 使用更高級別的庫或框架來隱藏底層細節,並直接支持更高級別的交互方式
  • 如Eventuate Tram框架;

4. 使用異步消息提升可用性

採用同步通訊機制處理請求,會對系統的可用性帶來影響。所以,應儘量選擇異步通訊機制來處理服務之間的調用。

4.1 同步消息會下降可用性

同步交互方式提交訂單流程圖

4.2 消除同步交互的方法

  • 使用異步交互模式

    • 下圖解:客戶的經過Order Service發送一個請求消息交換消息的方式建立訂單;這個服務隨即採用異步交換消息的方式跟其餘服務通訊完成訂單的建立;
    • 缺點:不少狀況下都要採用REST等同步通訊協議API,不能替換爲異步;
      異步交互方式提交訂單流程圖
  • 複製數據

    • 下圖解:Consumer Service和Restaurant Service在它們的數據發生變化時對外發布事件;Order Service訂閱這些事件,並據此更新本身的數據副本;
    • 缺點:當數據量巨大時效率低下;

複製數據提交訂單流程圖

  • 先返回響應,再完成處理
    • 下圖解:Order Service建立一個未檢驗(Pending)狀態的訂單,而後經過異步交互方式直接跟其餘服務通訊來完成驗證;
    • 缺點:使客戶端更復雜。

先返回響應,再完成處理訂單流程


5. 本章小結

  • 微服務架構是一種分佈式架構,所以進程間通訊起着關鍵做用;
  • 仔細管理服務API的演化相當重要。向後兼容的更改是最容易進行的,由於它們不會影響客戶端。若是對服務的API進行重大更改,一般須要同時支持舊版本和新版本,直到客戶端升級爲止;
  • 有許多進程間通訊技術,每種技術都有不一樣的利弊。一個關鍵的設計決策是選擇同步遠程過程調用模式或異步消息模式。基於同步遠程過程調用的協議(如REST)是最容易使用的。可是,理想狀況下,服務應使用異步消息進行通訊,以提升可用性;
  • 爲了防止故障經過系統層層蔓延,使用同步協議服務的客戶端必須設計成可以處理局部故障,這些故障是在被調用的服務停機或表現出高延遲時發生的。特別是,它必須在發出請求時使用超時,限制未完成請求的數量,並使用斷路器模式來避免調用失敗的服務;
  • 使用同步協議的架構必須包含服務發現機制,以便客戶端肯定服務實例的網絡位置。最簡單的方法是使用部署平臺實現的服務發現機制:服務器端發現和第三方註冊模式。但另外一種方法是在應用程序級別實現服務發現:客戶的發現和自注冊模式。它須要的工做量更大,但它確實能夠處理服務在多個部署平臺上運行的場景;
  • 設計基於消息的架構的一種好方法是使用消息和通道模型,它抽象底層消息系統的細節。而後,你能夠將該設計映射到特定的消息基礎結構,該基礎結構一般基於消息代理;
  • 使用消息機制的一個關鍵挑戰是以原子化的方式同時完成數據庫更新和發佈消息。一個好的解決方案是使用事務性發件箱模式,並首先將消息做爲數據庫事務的一部分寫入數據庫。而後,一個單獨的進程使用輪詢發佈者模式或事務日誌拖尾模式從數據庫中檢索信息,並將其發佈給消息代理。


最後

新人制做,若有錯誤,歡迎指出,感激涕零!
歡迎關注公衆號,會分享一些更平常的東西!
如需轉載,請標註出處!
相關文章
相關標籤/搜索