五分鐘了解微服務(wù)架構(gòu)通信模式
通信是微服務(wù)架構(gòu)中的關(guān)鍵要素,人們廣泛討論的焦點(diǎn)是如何選擇最有效的方法進(jìn)行服務(wù)間交互。在這篇文章中,將探討和總結(jié)微服務(wù)的最佳通信策略,深入探討如何有效利用每種通信方式。
交互方式
要有效理解微服務(wù)架構(gòu)中的服務(wù)通信方式,首先必須熟悉可用的交互方式。每種風(fēng)格都有其獨(dú)特的優(yōu)缺點(diǎn),在為服務(wù)確定最合適的通信機(jī)制的明智決定之前,全面了解這些細(xì)微差別至關(guān)重要。這些基礎(chǔ)知識(shí)可確保所選方法完全符合系統(tǒng)的具體要求和挑戰(zhàn)。
交互方式可以分為兩個(gè)維度,第一個(gè)維度是一對(duì)一還是一對(duì)多的交互:
- 一對(duì)一(One-to-one) - 每個(gè)客戶請(qǐng)求由一個(gè)服務(wù)處理。
- 一對(duì)多(One-to-many) - 每個(gè)請(qǐng)求由多個(gè)服務(wù)處理。
第二個(gè)維度是同步還是異步交互。
- 同步(Synchronous) - 客戶端希望服務(wù)及時(shí)做出響應(yīng),甚至可能在等待時(shí)阻塞。
- 異步(Asynchronous) - 客戶端不會(huì)阻塞,即使有響應(yīng),也不一定立即發(fā)送。
下表顯示了不同維度的組合:
通信維度
下面分別簡(jiǎn)要介紹一下。
一對(duì)一交互:
- 請(qǐng)求/響應(yīng) - 客戶端向服務(wù)端提出請(qǐng)求并等待響應(yīng)。客戶端希望響應(yīng)能及時(shí)到達(dá),甚至可能在等待時(shí)阻塞。這種交互方式通常會(huì)導(dǎo)致服務(wù)緊耦合。
- 異步請(qǐng)求/響應(yīng) - 客戶端向服務(wù)端發(fā)送請(qǐng)求,服務(wù)端以異步方式回復(fù)??蛻舳嗽诘却龝r(shí)不會(huì)阻塞,而服務(wù)端可能很長(zhǎng)時(shí)間都不會(huì)發(fā)送響應(yīng)。
- 單向通知 - 客戶端向服務(wù)端發(fā)送請(qǐng)求,但不期望立即獲得回復(fù)。
一對(duì)多交互:
- 發(fā)布/訂閱 - 客戶端發(fā)布一條通知消息,由零個(gè)或多個(gè)感興趣的服務(wù)消費(fèi)。
- 發(fā)布/同步響應(yīng) - 客戶端發(fā)布請(qǐng)求信息,然后等待相關(guān)服務(wù)的響應(yīng)。
記住,一種服務(wù)可以有多種通信方式!
使用同步遠(yuǎn)程過(guò)程(Remote Procedure Invocation)調(diào)用模式進(jìn)行通信
客戶端向服務(wù)端發(fā)送請(qǐng)求,服務(wù)處理請(qǐng)求并發(fā)回響應(yīng)。有些客戶端可能會(huì)阻塞等待響應(yīng),有些客戶端則可能采用反應(yīng)式非阻塞架構(gòu)。但與使用消息傳遞不同的是,客戶端假定響應(yīng)會(huì)及時(shí)到達(dá)。
下圖顯示了 RPI 的工作原理??蛻舳酥械臉I(yè)務(wù)邏輯會(huì)調(diào)用 PRI 代理適配器類(lèi)實(shí)現(xiàn)的代理接口,RPI 代理向服務(wù)發(fā)出請(qǐng)求。
請(qǐng)求由 RPI 服務(wù)端適配器類(lèi)處理,該類(lèi)通過(guò)接口調(diào)用服務(wù)的業(yè)務(wù)邏輯,然后將回復(fù)發(fā)送給 RPI 代理,后者將結(jié)果返回給客戶端的業(yè)務(wù)邏輯。
代理接口通常封裝了底層通信協(xié)議。我們將重點(diǎn)介紹最流行的 REST 和 gRPC 協(xié)議。
REST API
REST 的關(guān)鍵概念是資源,通常代表單個(gè)業(yè)務(wù)對(duì)象(如客戶或產(chǎn)品)或業(yè)務(wù)對(duì)象集合。REST 通過(guò) HTTP 動(dòng)詞來(lái)操作資源,資源使用 URL 引用。例如,GET 請(qǐng)求返回資源的表示形式,通常是 XML 文檔或 JSON 對(duì)象,也可以使用二進(jìn)制等其他格式。POST 請(qǐng)求創(chuàng)建新資源,PUT 請(qǐng)求更新資源。
1.REST API 的挑戰(zhàn):
(1) 在一次請(qǐng)求中獲取多個(gè)資源:
REST 資源通常以客戶和訂單等業(yè)務(wù)對(duì)象為重點(diǎn),這給在一次請(qǐng)求中獲取多個(gè)相關(guān)對(duì)象帶來(lái)了挑戰(zhàn)。例如,獲取訂單及其關(guān)聯(lián)的客戶通常需要多次 API 調(diào)用。常見(jiàn)的解決方法是增強(qiáng)應(yīng)用程序接口,使客戶端可以在一次調(diào)用中獲取相關(guān)資源,例如使用帶有擴(kuò)展查詢參數(shù)的 GET 請(qǐng)求來(lái)指定相關(guān)資源。雖然這種方法在很多情況下都很有效,但實(shí)施起來(lái)可能會(huì)很復(fù)雜、很耗時(shí),這也是 GraphQL 等用于更簡(jiǎn)化數(shù)據(jù)檢索的替代技術(shù)興起的原因之一。
(2) 將操作映射到 HTTP 動(dòng)詞
一個(gè)值得注意的 REST API 設(shè)計(jì)挑戰(zhàn)是如何將業(yè)務(wù)對(duì)象上的特定操作分配給正確的 HTTP 動(dòng)詞。例如,更新訂單可能涉及取消或修改訂單等各種操作,而且并非所有更新都必須是冪等的,而這正是使用 HTTP PUT 方法所必需的。一種常見(jiàn)的方法是為不同的更新操作創(chuàng)建子資源,例如使用 POST 取消訂單(POST /orders/{orderId}/cancel )或修改訂單(POST /orders/{orderId}/revise )。另一種方法是將操作作為 URL 查詢參數(shù)。不過(guò),這些方法可能并不完全符合 REST 原則。將操作映射到 HTTP 動(dòng)詞上的這種困難,促成了 gRPC 等替代技術(shù)的流行。
使用 REST 有很多好處:
- 使用簡(jiǎn)單,大部分工程師都比較熟悉。
- 可以在瀏覽器中使用 Postman 插件等工具測(cè)試 HTTP API,也可以在命令行中使用 curl 進(jìn)行測(cè)試(假設(shè)使用的是 JSON 或其他文本格式)。
- 直接支持請(qǐng)求/響應(yīng)式通信。
- HTTP 對(duì)防火墻是友好的。
- 不需要中間代理,從而簡(jiǎn)化了系統(tǒng)架構(gòu)。
使用 REST 有一些缺點(diǎn):
- 只支持請(qǐng)求/響應(yīng)式通信。
- 可用性低。由于客戶端和服務(wù)端直接通信,沒(méi)有中間組件緩沖信息,因此在交互過(guò)程中,客戶端和服務(wù)端必須同時(shí)運(yùn)行。
- 客戶端必須知道服務(wù)端實(shí)例的位置(URL)。在現(xiàn)代應(yīng)用中,這是一個(gè)非同小可的問(wèn)題??蛻舳吮仨毷褂盟^的服務(wù)發(fā)現(xiàn)機(jī)制來(lái)定位服務(wù)實(shí)例。
- 在一次請(qǐng)求中獲取多個(gè)資源具有挑戰(zhàn)性。
- 有時(shí)很難將多個(gè)更新操作映射到 HTTP 動(dòng)詞。
3.使用 gRPC
gRPC 提供了另一種選擇,它使用基于二進(jìn)制消息的協(xié)議,強(qiáng)調(diào) API 優(yōu)先的方法。gRPC 采用協(xié)議緩沖區(qū)(Protobuf),一種由谷歌開(kāi)發(fā)的語(yǔ)言中立的序列化系統(tǒng),允許開(kāi)發(fā)人員在基于Protobuf的接口定義語(yǔ)言(IDL)中定義 API。gRPC API 在 HTTP/2 上運(yùn)行,支持簡(jiǎn)單的請(qǐng)求/響應(yīng)和流式 RPC,因此服務(wù)端可以向客戶端發(fā)送消息流,反之亦然。該技術(shù)支持創(chuàng)建定義明確的服務(wù)接口和強(qiáng)類(lèi)型方法,為處理微服務(wù)架構(gòu)中各種復(fù)雜的通信模式提供了強(qiáng)大的框架。
gRPC 的優(yōu)點(diǎn)和缺點(diǎn)
gRPC 有幾個(gè)好處:
- 設(shè)計(jì)一個(gè)擁有豐富更新操作的應(yīng)用程序接口非常簡(jiǎn)單。
- 具有高效、緊湊的 IPC 機(jī)制,尤其是在交換大型信息時(shí)。
- 雙向數(shù)據(jù)流可實(shí)現(xiàn) RPI 和消息傳遞兩種通信方式。
- 可實(shí)現(xiàn)客戶端與用多種語(yǔ)言編寫(xiě)的服務(wù)之間的互操作性。
gRPC 也有若干缺點(diǎn):
- 與基于 REST/JSON 的應(yīng)用程序接口相比,JavaScript 客戶端在使用基于 gRPC 的應(yīng)用程序接口時(shí)需要花費(fèi)更多的時(shí)間。
- 老式防火墻可能不支持 HTTP/2。
- gRPC 是 REST 的一個(gè)令人信服的替代方案,但與 REST 一樣,它也是一種同步通信機(jī)制,因此也存在部分失效的問(wèn)題。
使用異步消息傳遞模式進(jìn)行通信
使用消息傳遞時(shí),服務(wù)通過(guò)異步消息進(jìn)行通信?;谙鬟f的應(yīng)用程序通常使用消息代理,作為服務(wù)之間的中介??蛻舳送ㄟ^(guò)發(fā)送消息向服務(wù)端發(fā)出請(qǐng)求,如果服務(wù)端實(shí)例需要回復(fù),就會(huì)向客戶端發(fā)送一條單獨(dú)的消息。由于通信是異步的,客戶端不會(huì)阻塞等待回復(fù)。相反,客戶端在編寫(xiě)時(shí)假定不會(huì)立即收到回復(fù)。
1.消息傳遞概述
摘自 Gregor Hohpe 和 Bobby Woolf 合著的《企業(yè)集成模式》一書(shū):
消息通過(guò)信道進(jìn)行交互。發(fā)送方(應(yīng)用程序或服務(wù))向信道寫(xiě)入消息,接收方(應(yīng)用程序或服務(wù))從信道讀取消息。我們先介紹下信息,再了解下信道。
2.關(guān)于消息
消息由消息頭(header) 和消息體(message body) 組成。
消息頭是描述發(fā)送數(shù)據(jù)的名-值對(duì)和元數(shù)據(jù)的集合。除了表示消息發(fā)送方的名-值對(duì),消息頭還包含其他名-值對(duì),如由發(fā)送方或消息基礎(chǔ)設(shè)施生成的唯一消息 ID,以及可選的返回地址,該地址指定了應(yīng)寫(xiě)入響應(yīng)的信道。消息體是以文本或二進(jìn)制格式發(fā)送的數(shù)據(jù)。
有幾種不同的消息:
- 文檔(Document) - 僅包含數(shù)據(jù)的通用消息,由接收方?jīng)Q定如何解釋。對(duì)命令的回復(fù)就是文檔消息的一個(gè)例子。
- 命令(Command) - 相當(dāng)于 RPC 請(qǐng)求的消息,指定了要調(diào)用的操作及其參數(shù)。
- 事件(Event) - 表示發(fā)件人發(fā)生了值得注意的事情的信息。事件通常是領(lǐng)域事件,表示領(lǐng)域?qū)ο螅ㄈ缬唵位蚩蛻簦┑臓顟B(tài)變化。
本文將主要介紹命令和事件。
3.關(guān)于信道
發(fā)送方的業(yè)務(wù)邏輯調(diào)用發(fā)送端接口,該接口封裝了底層通信機(jī)制。發(fā)送端由消息發(fā)送適配器類(lèi)實(shí)現(xiàn),該適配器類(lèi)通過(guò)信道向接收者發(fā)送消息。信道是消息傳送基礎(chǔ)架構(gòu)的一個(gè)抽象概念。接收器中的消息處理適配器類(lèi)被調(diào)用來(lái)處理消息,并調(diào)用由消費(fèi)者業(yè)務(wù)邏輯實(shí)現(xiàn)的接收端接口。任何數(shù)量的發(fā)送者都可以向同一個(gè)信道發(fā)送消息。同樣,任何數(shù)量的接收者都可以從同一個(gè)信道接收消息。
信道基礎(chǔ)設(shè)施
了解兩種信道非常重要:點(diǎn)對(duì)點(diǎn)(Point-To-Point) 和發(fā)布-訂閱(Publish-Subscribe)。
- 點(diǎn)對(duì)點(diǎn)信道將消息準(zhǔn)確發(fā)送給正從信道讀取消息的消費(fèi)者之一。服務(wù)使用點(diǎn)對(duì)點(diǎn)信道來(lái)實(shí)現(xiàn)前面介紹的一對(duì)一交互方式。例如,命令消息通常通過(guò)點(diǎn)對(duì)點(diǎn)信道發(fā)送。
- 發(fā)布-訂閱信道將每條消息發(fā)送給所有訂閱的消費(fèi)者。服務(wù)使用發(fā)布-訂閱信道來(lái)實(shí)現(xiàn)前面介紹的一對(duì)多交互方式。例如,事件消息通常通過(guò)發(fā)布-訂閱信道發(fā)送。
既然我們已經(jīng)清楚了解了異步通信,包括消息和信道的概念,那么接下來(lái)就應(yīng)該探索異步通信框架提供的各種通信機(jī)制的實(shí)現(xiàn)。
4.實(shí)現(xiàn)請(qǐng)求/響應(yīng)和異步請(qǐng)求/響應(yīng)
當(dāng)客戶端和服務(wù)端使用請(qǐng)求/響應(yīng)或異步請(qǐng)求/響應(yīng)進(jìn)行交互時(shí),客戶端發(fā)送請(qǐng)求,而服務(wù)端則返回響應(yīng)。這兩種交互方式的區(qū)別在于,使用請(qǐng)求/響應(yīng)時(shí),客戶端希望服務(wù)立即做出響應(yīng),而使用異步請(qǐng)求/響應(yīng)時(shí)則沒(méi)有這種期望。消息傳遞本質(zhì)上是異步的,因此只提供異步請(qǐng)求/響應(yīng)。但客戶端可以阻塞,直到收到響應(yīng)為止。
客戶端和服務(wù)端通過(guò)交換消息來(lái)實(shí)現(xiàn)異步請(qǐng)求/響應(yīng)式交互。如圖所示,客戶端向服務(wù)端對(duì)應(yīng)的點(diǎn)對(duì)點(diǎn)消息傳遞信道發(fā)送一條命令消息,其中指定了要執(zhí)行的操作及其參數(shù)。服務(wù)處理請(qǐng)求并向客戶端擁有的點(diǎn)對(duì)點(diǎn)信道發(fā)送包含結(jié)果的回復(fù)消息。
5.異步請(qǐng)求/響應(yīng)
從上圖可以看出,客戶端必須告訴服務(wù)端將響應(yīng)發(fā)送到哪兒,并且必須將響應(yīng)與請(qǐng)求匹配起來(lái)。幸運(yùn)的是,解決這兩個(gè)問(wèn)題并不難??蛻舳税l(fā)送的命令報(bào)文帶有回復(fù)信道頭reply channel header。服務(wù)端將響應(yīng)消息寫(xiě)入回復(fù)信道,響應(yīng)消息包含與命令消息標(biāo)識(shí)符具有相同值的關(guān)聯(lián) ID correlation id??蛻舳送ㄟ^(guò)correlation id將響應(yīng)消息與請(qǐng)求匹配。
由于客戶端和服務(wù)端使用消息傳遞進(jìn)行通信,因此本質(zhì)上是異步交互。理論上,客戶端可以阻塞直到收到響應(yīng),但實(shí)際上,客戶端會(huì)異步處理。此外,響應(yīng)通常由客戶端的任意實(shí)例進(jìn)行處理。
6.實(shí)現(xiàn)單向通知
使用異步消息傳遞可以直接實(shí)現(xiàn)單向通知。客戶端向服務(wù)端對(duì)應(yīng)的點(diǎn)對(duì)點(diǎn)信道發(fā)送消息,通常是命令消息。服務(wù)端訂閱該信道并處理消息,但不發(fā)送回復(fù)??梢詮?fù)用"異步請(qǐng)求/響應(yīng)"相同的圖示,但沒(méi)有回復(fù)信道。
7.實(shí)現(xiàn)發(fā)布/訂閱
客戶端向發(fā)布-訂閱信道發(fā)布消息,多個(gè)消費(fèi)者可以讀取該消息。服務(wù)通過(guò)發(fā)布/訂閱來(lái)發(fā)布域事件,這些事件代表了對(duì)域?qū)ο蟮母摹0l(fā)布域事件的服務(wù)擁有一個(gè)發(fā)布-訂閱信道,該通道的名稱(chēng)源自域類(lèi)。對(duì)特定域?qū)ο笫录信d趣的服務(wù)只需訂閱相應(yīng)的信道即可。
發(fā)布/訂閱
8.實(shí)現(xiàn)發(fā)布/同步響應(yīng)
發(fā)布/同步響應(yīng)交互方式是一種更高級(jí)別的交互方式,通過(guò)結(jié)合發(fā)布/訂閱和請(qǐng)求/響應(yīng)的元素來(lái)實(shí)現(xiàn)。客戶端向發(fā)布-訂閱信道發(fā)布一條指定了回復(fù)信道頭reply channel header的消息。消費(fèi)者向回復(fù)信道寫(xiě)入包含correlation id的回復(fù)信息??蛻舳送ㄟ^(guò)correlation id將回復(fù)消息與請(qǐng)求匹配起來(lái)。
應(yīng)用程序中具有異步 API 的每個(gè)服務(wù)都將使用其中一種或多種實(shí)現(xiàn)技術(shù)。使用異步 API 調(diào)用操作的服務(wù)將有一個(gè)用于請(qǐng)求的消息信道。同樣,發(fā)布事件的服務(wù)也會(huì)將事件發(fā)布到事件消息信道。
使用消息代理
基于消息傳遞的應(yīng)用程序通常會(huì)使用消息代理(一種基礎(chǔ)架構(gòu)服務(wù),服務(wù)通過(guò)它進(jìn)行通信)。但基于代理的架構(gòu)并不是唯一的消息傳遞架構(gòu),也可以使用無(wú)代理消息傳遞架構(gòu),在這種架構(gòu)中,服務(wù)之間可以直接通信(本文將不涉及這一主題)。
1.基于代理的消息傳遞概述
消息代理是所有消息流動(dòng)的中介。發(fā)送者將消息寫(xiě)入消息代理,然后由消息代理將消息發(fā)送給接收者。使用消息代理的主要好處是,發(fā)送者不需要知道消費(fèi)者的網(wǎng)絡(luò)位置。另一個(gè)好處是,消息代理可以緩沖消息,直到消費(fèi)者能夠處理。
有許多消息代理可供選擇。流行的開(kāi)源消息代理包括以下幾種:
- ActiveMQ
- RabbitMQ
- Apache Kafka
每個(gè)代理都會(huì)做出不同的權(quán)衡。例如,延遲極低的代理可能不保留排序,不保證傳遞消息,只將消息存儲(chǔ)在內(nèi)存中;而保證傳遞消息并可靠的將消息存儲(chǔ)在磁盤(pán)上的代理可能會(huì)有更高的延遲。
哪種消息代理最合適取決于應(yīng)用程序的需求,應(yīng)用程序的不同部分甚至可能有不同的消息傳遞需求。
2.使用消息代理實(shí)現(xiàn)消息信道
每個(gè)消息代理都以不同的方式實(shí)現(xiàn)消息信道概念。如表所示,JMS 消息代理(如 ActiveMQ)有隊(duì)列和主題?;?AMQP 的消息代理(如 RabbitMQ)有交換和隊(duì)列。Apache Kafka 有主題,AWS Kinesis 有流,AWS SQS 有隊(duì)列。 此外,一些消息代理提供比本章所述消息和信道抽象更靈活的消息傳遞機(jī)制。
消息代理列表
這里介紹的幾乎所有消息代理都支持點(diǎn)對(duì)點(diǎn)和發(fā)布-訂閱信道。AWS SQS 是個(gè)例外,它只支持點(diǎn)對(duì)點(diǎn)信道。
消息代理的問(wèn)題
1.接收競(jìng)爭(zhēng)和消息排序
挑戰(zhàn)之一保持消息有序的同時(shí)擴(kuò)展消息接收器。為了并發(fā)處理消息,通常需要多個(gè)服務(wù)實(shí)例。此外,即使是單個(gè)服務(wù)實(shí)例也可能會(huì)使用線程來(lái)并發(fā)處理多個(gè)消息。使用多線程和多服務(wù)實(shí)例并發(fā)處理消息可以提高應(yīng)用程序吞吐量,但并發(fā)處理消息的挑戰(zhàn)在于確保每條消息都能按順序處理。
例如,假設(shè)有三個(gè)服務(wù)實(shí)例從同一個(gè)點(diǎn)對(duì)點(diǎn)信道讀取消息,發(fā)送方按順序發(fā)布 "創(chuàng)建訂單"、"更新訂單"和"取消訂單"事件消息。簡(jiǎn)單的消息傳遞實(shí)現(xiàn)可以同時(shí)將每條消息傳遞給不同的接收方。由于網(wǎng)絡(luò)問(wèn)題或垃圾回收導(dǎo)致的延遲,消息的處理順序可能會(huì)被打亂,從而導(dǎo)致奇怪的行為。理論上,一個(gè)服務(wù)實(shí)例可能會(huì)在另一個(gè)服務(wù)處理"訂單創(chuàng)建"消息之前處理"訂單取消"消息!
Apache Kafka 和 AWS Kinesis 等現(xiàn)代消息代理常用的解決方案是使用分片(分區(qū))信道,下圖顯示了其工作原理。該解決方案分為三個(gè)部分:
- 分片信道由兩個(gè)或多個(gè)分片組成,每個(gè)分片的行為都和信道一樣。
- 發(fā)送者在信息頭中指定分片key,通常是任意字符串或字節(jié)序列。消息代理通過(guò)分片key將信息分配給特定分片/分區(qū)。例如,可以通過(guò)計(jì)算分片key的哈希值乘以分片數(shù)來(lái)選擇分片。
- 消息代理將接收器的多個(gè)實(shí)例分組,并將它們視為同一個(gè)邏輯接收器。例如,Apache Kafka 使用消費(fèi)者組(consumer group)一詞。消息代理將每個(gè)分片分配給一個(gè)接收器。當(dāng)接收器啟動(dòng)和關(guān)閉時(shí),會(huì)重新分配分片。
分片信道架構(gòu)
在本例中,每個(gè)訂單事件消息都以orderId作為其分片key。特定訂單的每個(gè)事件都發(fā)布到同一個(gè)分片,由單個(gè)消費(fèi)者實(shí)例讀取。因此,可以保證這些消息按順序處理。
2.處理重復(fù)消息
使用消息傳遞時(shí)必須解決的另一個(gè)難題是處理重復(fù)消息。理想情況下,消息代理應(yīng)該每條消息只傳遞一次(exactly-once),但保證準(zhǔn)確傳遞一次消息通常成本太高。相反,大多數(shù)消息代理都承諾至少傳遞一次消息(at-least-once)。
當(dāng)系統(tǒng)正常運(yùn)行時(shí),保證至少交付一次的消息代理只交付一次消息。然而,客戶端、網(wǎng)絡(luò)或消息代理的故障可能導(dǎo)致消息被傳送多次。假設(shè)一個(gè)客戶端處理了消息并更新了數(shù)據(jù)庫(kù),但在發(fā)送確認(rèn)消息之前崩潰了。消息代理將再次傳送未確認(rèn)的消息,要么在該客戶端重啟時(shí)傳送給它,要么傳送給該客戶端的另一個(gè)副本。
理想情況下,應(yīng)該使用在重新傳遞消息時(shí)保留排序的消息代理。
假設(shè)客戶端在處理"創(chuàng)建訂單"事件后,又處理了同一訂單的"取消訂單"事件,而 "創(chuàng)建訂單"事件沒(méi)有得到確認(rèn)。消息代理應(yīng)同時(shí)重新傳遞"創(chuàng)建訂單"和"取消訂單"事件。如果只重新傳遞"創(chuàng)建訂單"事件,客戶機(jī)可能會(huì)取消訂單。
有幾種不同的方法可以處理重復(fù)信息:
- 編寫(xiě)冪等消息處理程序。
- 跟蹤并丟棄重復(fù)消息。
我們來(lái)簡(jiǎn)單了解一下每種方案。
3.編寫(xiě)冪等消息處理程序
如果處理消息的應(yīng)用邏輯是冪等的,那么重復(fù)報(bào)文是無(wú)害的。如果用相同的輸入值多次調(diào)用應(yīng)用邏輯不會(huì)產(chǎn)生額外的效果,那么應(yīng)用邏輯就是冪等的。例如,取消一個(gè)已經(jīng)取消的訂單就是一個(gè)冪等操作。使用客戶提供的 ID 創(chuàng)建訂單也是如此。
只要消息代理在重新傳遞消息時(shí)保留排序,冪等消息處理程序就可以安全執(zhí)行多次。
遺憾的是,應(yīng)用邏輯往往不是等效的。或者消息代理在重新傳遞消息時(shí)不保留順序,重復(fù)或失序的消息可能會(huì)導(dǎo)致錯(cuò)誤。在這種情況下,就必須自己編寫(xiě)消息處理程序來(lái)跟蹤消息并丟棄重復(fù)消息。
4.跟蹤并丟棄重復(fù)消息
例如,考慮一個(gè)對(duì)消費(fèi)者信用卡進(jìn)行鑒權(quán)的消息處理程序,必須對(duì)每張訂單的信用卡精確鑒權(quán)一次。這個(gè)應(yīng)用程序邏輯示例每次調(diào)用都會(huì)產(chǎn)生不同的效果。如果重復(fù)的消息導(dǎo)致消息處理程序多次執(zhí)行這一邏輯,應(yīng)用程序的行為就會(huì)不正確。執(zhí)行這種應(yīng)用程序邏輯的消息處理程序必須通過(guò)檢測(cè)和丟棄重復(fù)消息來(lái)獨(dú)立實(shí)現(xiàn)。一個(gè)簡(jiǎn)單的解決方案是,消息消費(fèi)者使用消息 ID 跟蹤已處理的消息,并丟棄任何重復(fù)消息。舉例來(lái)說(shuō),可以在數(shù)據(jù)庫(kù)表中存儲(chǔ)它處理過(guò)的每條消息的ID。下圖顯示了如何使用專(zhuān)用表來(lái)實(shí)現(xiàn)這一功能。
跟蹤消息流
消費(fèi)者處理消息時(shí),會(huì)在數(shù)據(jù)庫(kù)表中記錄message id,作為創(chuàng)建和更新業(yè)務(wù)實(shí)體的事務(wù)的一部分。在本例中,消費(fèi)者向 PROCESSED_MESSAGES 表中插入一條包含message id的記錄。如果是重復(fù)消息,則 INSERT 將失敗,用戶可以丟棄該消息。
另一種方法是讓消息處理程序在應(yīng)用程序表而不是專(zhuān)用表中記錄message id。這種方法在使用 NoSQL 數(shù)據(jù)庫(kù)時(shí)特別有用,因?yàn)?NoSQL 數(shù)據(jù)庫(kù)的事務(wù)模型有限,不支持將更新兩個(gè)表作為數(shù)據(jù)庫(kù)事務(wù)的一部分。
結(jié)論
總之,微服務(wù)架構(gòu)中通信方式的選擇對(duì)于應(yīng)用程序的整體效率和可擴(kuò)展性至關(guān)重要。在本文中,我們探討了從同步調(diào)用到異步消息傳遞等各種通信機(jī)制,每種機(jī)制都有其獨(dú)特的優(yōu)勢(shì)和合適的使用場(chǎng)景。正如我們所看到的,正確的通信策略不僅能提高性能,還能確保服務(wù)交互的彈性和靈活性。
在選擇通信方式時(shí),必須考慮服務(wù)交互的性質(zhì)、對(duì)實(shí)時(shí)數(shù)據(jù)的需求以及所涉及服務(wù)的復(fù)雜性等因素。請(qǐng)記住,我們的目標(biāo)是建立一個(gè)強(qiáng)大架構(gòu),使其能夠隨著組織需求和技術(shù)進(jìn)步而發(fā)展。