與RSocket進行服務通信的反應式服務—簡介

最初於2019年7月11日發佈在https://blog.grapeup.com。


與RSocket進行服務通信的反應式服務—簡介

本文是該微型系列文章的第一篇,它將幫助您熟悉RSocket —一種新的二進制協議,它可能會徹底改變機器對機器的通信。 在以下各段中,我們討論分佈式系統的問題,並說明如何使用RSocket解決這些問題。 我們專注於微服務之間的通信和RSocket的交互模型。

請注意,本文中提供的代碼示例可在GitHub上找到→

分佈式系統中的通信問題

微服務無處不在,幾乎無處不在。我們經歷了漫長的旅程,從糟糕的部署和維護單塊應用程序到完全分佈式的微型可擴展微服務。這樣的架構設計有很多好處。但是,它也有缺點,值得一提。首先,為了向最終客戶交付價值,服務必須交換大量數據。在整體應用程序中這不是問題,因為整個通信都在單個JVM中進行。在微服務架構中,服務部署在單獨的容器中並通過內部或外部網絡進行通信,因此網絡是一等公民。如果您決定在雲中運行應用程序,事情將變得更加複雜,在這種情況下,網絡問題和延遲增加是您無法完全避免的事情。與其嘗試解決網絡問題,不如讓您的體系結構具有彈性並且即使在動盪的時期中也能完全正常運行,這更好。

讓我們更深入地研究微服務,數據,通信和雲的概念。 作為示例,我們將討論可通過網站和移動應用訪問的企業級系統,以及與小型外部設備(例如家用加熱器控制器)進行通信的系統。 該系統由多個微服務組成,大多數以Java編寫,並且具有一些Python和node.js組件。 顯然,所有這些文件都跨多個可用性區域進行復制,以確保整個系統高度可用。

為了與IaaS提供者無關,並改善開發人員體驗,這些應用程序正在PaaS之上運行。 我們在這裡有各種各樣的可能性:Cloud Foundry,Kubernetes或結合在Cloudboostr中的兩者都是合適的。 在服務之間的通信方面,設計很簡單。 每個組件都公開普通的REST API-如下圖所示。

與RSocket進行服務通信的反應式服務—簡介

乍一看,這樣的體系結構看起來還不錯。 組件被分離並在雲中運行-可能出什麼問題? 實際上,存在兩個主要問題-它們都與溝通有關。

第一個問題是HTTP的請求/響應交互模型。 儘管有很多用例,但它並不是為機器對機器的通信而設計的。 微服務在不關心操作結果的情況下發送一些數據到另一個組件是很常見的(觸發並忘記),或者在數據可用時自動流傳輸數據(數據流)。 使用請求/響應交互模型很難以優雅,有效的方式實現這些通信模式。 即使執行簡單的即發即棄操作也有副作用-服務器必須將響應發送回客戶端,即使客戶端對處理它不感興趣。

第二個問題是性能。 假設我們的系統被客戶廣泛使用,流量增加,並且我們注意到我們正在努力處理每秒數百個請求。 藉助容器和雲,我們能夠輕鬆擴展我們的服務。 但是,如果我們進一步跟蹤資源消耗,則會注意到在內存不足的情況下,VM的CPU幾乎處於空閒狀態。 問題出在通常與HTTP 1.x一起使用的每個請求模型的線程中,其中每個單個請求都有自己的堆棧內存。 在這種情況下,我們可以利用反應性編程模型和非阻塞IO。 它將大大減少內存使用量,但是不會減少延遲。 HTTP 1.x是基於文本的協議,因此需要傳輸的數據大小比二進制協議要大得多。

在機器對機器的通信中,我們不應將自己侷限於HTTP(尤其是1.x),其請求/響應交互模型以及性能低下。 那裡(市場上)有更多更合適,更強大的解決方案。 基於RabbitMQ,gRPC甚至HTTP 2並支持多路複用和二進制化有效負載的消息傳遞,在性能和效率方面比純HTTP 1.x更好。

與RSocket進行服務通信的反應式服務—簡介

使用多種協議可以使我們在給定場景中以最有效和最合適的方式鏈接微服務。 但是,採用多種協議迫使我們一次又一次地重新發明輪子。 我們必須使用與安全性有關的額外信息來豐富我們的數據,並創建多個適配器來處理協議之間的轉換。 在某些情況下,運輸需要外部資源(經紀人,服務等),這些資源必須高度可用。 即使我們所需要的只是基於消息的簡單"即發即棄"操作,額外的資源也會帶來額外的成本。 此外,多種不同的協議可能會引入與應用程序管理相關的嚴重問題,尤其是如果我們的系統包含數百個微服務時。

上面提到的問題是發明RSocket的根本原因,也是它可能徹底改變雲通信的根本原因。 通過其反應性和內置的強大交互模型,RSocket可以應用於各種業務場景,並最終統一我們在分佈式系統中使用的通信模式。

RSocket解救

RSocket是一種新的,消息驅動的二進制協議,它標準化了雲中的通信方法。 它有助於以一致的方式解決常見的應用程序問題,並支持多種語言(例如java,js,python)和傳輸層(TCP,WebSocket,Aeron)。 在以下各節中,我們將更深入地研究協議內部結構並討論交互模型。

框架化和消息驅動

RSocket中的交互分為框架。 每個幀都包含一個幀頭,其中包含流ID,幀類型定義和特定於該幀類型的其他數據。 幀頭後跟元數據和有效負載,這些部分承載用戶指定的數據。

與RSocket進行服務通信的反應式服務—簡介

有多種類型的框架表示不同的動作和交互模型的可用方法。 我們不會涵蓋所有這些內容,因為官方文檔(
http://rsocket.io/docs/Protocol)中對此進行了廣泛的描述。 但是,很少有值得注意的東西。 其中之一是客戶端在通信開始時將其發送到服務器的設置框架。 可以自定義此框架,以便您可以添加自己的安全規則或連接初始化期間所需的其他信息。 應該注意的是,在建立連接階段之後,RSocket不會區分客戶端和服務器。 每一側都可以開始將數據發送到另一側-這使該協議幾乎完全對稱。

性能

幀作為字節流發送。 它使RSocket方式比典型的基於文本的協議更有效。 從開發人員的角度來看,當JSON在網絡中來回飛行時,調試系統更容易,但是對性能的影響使這種便利性成為問題。 該協議沒有強加任何特定的序列化/反序列化機制,它認為幀是一包可以轉換為任何東西的位。 這樣就可以使用JSON序列化或更有效的解決方案,例如Protobuf或AVRO。

影響RSocket性能的第二個因素是多路複用。 該協議在單個物理連接的頂部創建邏輯流(通道)。 每個流都有其唯一的ID,在某種程度上可以將其解釋為我們從消息傳遞系統知道的隊列。 這種設計解決了HTTP 1.x中已知的主要問題-每個請求模型的連接和"流水線"的性能較弱。 此外,RSocket本機支持大型有效負載的傳輸。 在這種情況下,有效載荷幀會被分割成帶有額外標誌(給定片段的序數)的幾個幀。

反應性和流量控制

RSocket協議完全包含"反應式宣言"中所述的原則。 它在資源方面的異步特性和節儉功能有助於減少最終用戶所經歷的延遲以及基礎架構的成本。 多虧了流式傳輸,我們不需要將數據從一項服務拉到另一項服務,而是在數據可用時將其推送。 這是一個非常強大的機制,但它也可能具有風險。 讓我們考慮一個簡單的場景:在我們的系統中,我們將事件從服務A傳輸到服務B。在接收方執行的操作很簡單,需要一定的計算時間。 如果服務A推送事件的速度快於B處理事件的速度,則B最終將耗盡資源-發送方將終止接收方。 由於RSocket使用反應堆,因此它內置了對流控制的支持,這有助於避免這種情況。

我們可以輕鬆提供根據我們的需求調整的反壓機制實施方案。 接收者可以指定要消耗多少數據,而不會收到更多數據,除非它通知發送者準備處理更多數據。 另一方面,為了限制從請求者傳入的幀數,RSocket實現了租用機制。 響應者可以指定在定義的時間範圍內請求者可以發送多少個請求。

API

如上一節所述,RSocket使用Reactor,因此在API級別上,我們主要在Mono和Flux對象上進行操作。 它也完全支持反應性信號-我們可以輕鬆地在不同事件上實現"反應"-onNext,onError,onClose等。

以下各段將介紹API和RSocket中可用的每個交互選項。 討論將以代碼片段和所有示例的描述為後盾。 在進入交互模型之前,值得介紹一下API基礎,因為它將在多個代碼示例中提出。

用RSocketFactory設置連接

在同級之間建立RSocket連接非常容易。 該API為工廠(RSocketFactory)提供了工廠方法,它們可以接收並連接以分別在客戶端和服務器端創建RSocket和CloseableChannel實例。 在通信雙方(請求者和響應者)中存在的第二共同財產是運輸工具。 RSocket可以使用多種解決方案作為傳輸層(TCP,WebSocket,Aeron)。 無論選擇哪種API,API都將提供工廠方法,使您可以調整和調整連接。

而且,對於響應者,我們必須創建一個套接字接受器實例。 SocketAcceptor是提供對等方之間合同的接口。 它具有單個方法accept,該方法接受RSocket發送請求並返回RSocket實例,該實例將用於處理來自對等方的請求。 除了提供合同外,SocketAcceptor還使我們能夠訪問設置框架的內容。 在API級別,它由ConnectionSetupPayload對象反映。

如上所示,在同級之間建立連接是相對容易的,特別是對於那些以前使用過WebSockets的人來說-就API而言,兩種解決方案都非常相似。

互動模式

建立連接後,我們可以繼續進行交互模型。 RSocket支持以下操作:

與RSocket進行服務通信的反應式服務—簡介

隨手可得,以及元數據推送,旨在將數據從發送方推送到接收方。 在這兩種情況下,發送方都不關心操作的結果-它在API級別上以返回類型(Mono)反映出來。 這些動作之間的區別在於框架。 萬一發生火災而忘記了,將完整的幀發送到接收器,而對於元數據推送操作,該幀不具有有效負載-它僅由標頭和元數據組成。 此類輕量級消息可用於將通知發送到IoT設備的移動或對等通信。

RSocket還能夠模仿HTTP行為。 它支持請求-響應語義,這可能是您將要與RSocket一起使用的主要交互類型。 在流上下文中,此類操作可以表示為由單個對象組成的流。 在這種情況下,客戶端正在等待響應幀,但是它以完全非阻塞的方式進行響應。

在雲應用程序中,更有趣的是對數據流進行操作的請求流和請求通道交互,通常是無限的。 在請求流操作的情況下,請求者將單個幀發送給響應者並獲取數據流。 這種交互方法使服務能夠從提取數據策略切換到推送數據策略。 無需向響應者發送定期請求,請求者可以訂閱流並對傳入數據做出反應-當數據可用時,它將自動到達。

得益於多路複用和雙向數據傳輸的支持,我們可以使用請求通道方法更進一步。 RSocket能夠使用單個物理連接將數據從請求者流式傳輸到響應者,以及以另一種方式。 當請求者更新訂閱時(例如,更改訂閱標準),此類交互可能會很有用。 如果沒有雙向通道,客戶端將不得不取消流並使用新參數重新請求它。

在API中,交互模型的所有操作都由下面顯示的RSocket接口的方法表示。

為了改善開發人員的體驗並避免實現RSocket接口的每個方法的必要性,API提供了我們可以擴展的抽象AbstractRSocket。 通過將SocketAcceptor和AbstractRSocket放在一起,我們可以獲得服務器端的實現,在基本情況下,該實現可能看起來像這樣:

在發送方,使用交互模型非常簡單,我們需要做的就是在我們使用RSocketFactory創建的RSocket實例上調用特定方法,例如

有關RSocket交互模型中可用方法的更多示例,請訪問GitHub→

發送方方面更有趣的是反壓機制的實現。 讓我們考慮以下請求方實施示例:

在此示例中,我們正在請求數據流,但是為了確保傳入的幀不會殺死請求者,我們採用了反壓機制。 為了實現此機制,我們使用request_n框架,該框架在API級別上由subscription.request(n)方法反映出來。 在訂閱的開始[onSubscribe(Subscription s)],我們請求5個對象,然後我們在onNext(Payload有效負載)中計數接收到的項目。 當所有預期的幀都到達請求者時,我們正在請求接下來的5個對象-再次使用subscription.request(n)方法。 下圖顯示了該訂戶的流程:

與RSocket進行服務通信的反應式服務—簡介

本節介紹的背壓機制的實現非常基礎。 在生產中,我們應基於更準確的指標(例如, 預測/平均計算時間。 畢竟,背壓機制不會使響應者生產過剩的問題消失。 它將問題轉移到響應方,可以更好地解決。 有關反壓的更多信息,請參見Medium和GitHub。

摘要

在本文中,我們討論了微服務體系結構中的通信問題,以及如何使用RSocket解決這些問題。 我們以簡單的" hello world"示例和基本的反壓機制實現為背景,介紹了其API和交互模型。

在本系列的下一篇文章中,我們將介紹RSocket的更多高級功能,包括LoadBalancing和Resumability,還將討論RSocket上的抽象-RPC和Spring Reactor。

請注意,此處提供了完整的工作示例→

(本文翻譯自Rafał Kowalski的文章《Reactive service to service communication with RSocket — Introduction》,參考:https://medium.com/@
b3rnoulli/reactive-service-to-service-communication-with-rsocket-introduction-5d64e5b6909)


分享到:


相關文章: