使用RSocket進行服務通訊的反應性服務簡介 - Rafał Kowalski

banq發表於2019-07-13

在本文中,我們將討論微服務架構中的通訊問題,以及如何使用RSocket解決這些問題。我們介紹了它的API和支援簡單的“hello world”示例和基本背壓機制實現的互動模型。

RSocket是一種新的,訊息驅動的二進位制協議,它標準化了雲中的通訊方法。它有助於以一致的方式解決常見的應用程式問題,並且它支援多種語言(例如java,js,python)和傳輸層(TCP,WebSocket,Aeron)。

RSocket中的互動被分解為幀。每個幀由一個幀頭組成,該幀頭包含流id,幀型別定義和特定於幀型別的其他資料。幀頭之後是後設資料和有效載荷 - 這些部分攜帶使用者指定的資料。

存在多種型別的幀,表示互動模型的不同動作和可用方法。我們不打算涵蓋所有這些內容,因為它們在官方文件(http://rsocket.io/docs/Protocol)中有詳細描述

應該注意,在連線建立階段之後,RSocket不區分客戶端和伺服器。每一方都可以開始將資料傳送到另一方 - 它使協議幾乎完全對稱。

效能

幀作為位元組流傳送。它使得RSocket方式比典型的基於文字的協議更有效。從開發人員的角度來看,當JSON在網路中來回飛行時,除錯系統會更容易,但是會對效能的影響。RSocket協議沒有強加任何指定的序列化/反序列化機制,它將幀視為可以轉換為任何東西的一個位元包。這使得我們可以使用JSON序列化或更高效的解決方案,如Protobuf或AVRO。

對RSocket效能產生巨大影響的第二個因素是多路複用。該協議在單個物理連線的頂部建立邏輯流(通道)。每個流都有其唯一的ID,在某種程度上,它可以被解釋為訊息傳遞系統的佇列。此外,RSocket本身支援傳輸大型有效負載。在這種情況下,有效載荷幀被分成幾個幀,其中有一個額外的標誌 - 給定片段的序數。

反應性和流量控制

RSocket協議完全符合Reactive Manifesto中所述的原則。它在資源方面的非同步特性和節儉有助於減少終端使用者所經歷的延遲和基礎架構的成本。由於流式傳輸,我們不需要將資料從一個服務提取到另一個服務,而是在資料可用時推送資料。它是一種非常強大的機制,但也可能存在風險。

讓我們考慮一個簡單的場景:在我們的系統中,我們將事件從服務A流式傳輸到服務B.在接收器端執行的操作非常重要,需要一些計算時間。如果服務A推送事件的速度快於B能夠處理它們的速度,最終B將耗盡資源,傳送方將殺死了接收方,由於RSocket使用Reactor,它內建了對流量控制的支援,這有助於避免這種情況。

我們可以輕鬆提供背壓機制實施,根據我們的需求進行調整。接收方可以指定它想要消耗多少資料,並且在通知傳送方已準備好處理更多資料之前不會獲得更多資料。另一方面,為了限制來自請求者的傳入幀的數量,RSocket實現了一種租用機制。響應者可以指定請求者在定義的時間範圍內可以傳送多少請求。

API

如前一節所述,RSocket使用Reactor,因此在API級別上我們主要操作Mono和Flux物件。它也完全支援無功訊號 - 我們可以輕鬆地對不同事件實現“反應” - onNext,onError,onClose等。

以下段落將介紹API和RSocket中可用的每個互動選項。討論將以程式碼片段和所有示例的描述為後盾。在我們進入互動模型之前,值得描述API基礎知識,因為它將出現在多個程式碼示例中。

使用RSocketFactory建立連線

在對等體之間建立RSocket連線相當容易。API提供了(RSocketFactory)工廠和工廠方法receive和connect ,用來分別在客戶端和伺服器端建立RSocket和CloseableChannel例項。

通訊雙方(請求者和響應者)中存在的第二個共同屬性是transport。RSocket可以使用多個解決方案作為transport層(TCP,WebSocket,Aeron)。無論您選擇哪種API,都會提供工廠方法,允許您調整和調整連線。

RSocket socket = RSocketFactory.connect()
        .transport(TcpClientTransport.create(HOST, PORT))
        .start()
        .block();

RSocketFactory.receive()
    .acceptor(new HelloWorldSocketAcceptor())
    .transport(TcpServerTransport.create(HOST, PORT))
    .start()
    .subscribe();

在響應接收端者,我們必須建立一個套接字接受器例項。SocketAcceptor是一個提供對等體之間契約的介面。它有一個accept接受RSocket傳送請求的方法,並返回一個RSocket例項,用於處理來自對等體的請求。除了提供契約外,SocketAcceptor還允許我們訪問設定框架內容。在API級別,它與ConnectionSetupPayload物件有關。

public interface SocketAcceptor {
   Mono<RSocket> accept(ConnectionSetupPayload setup, RSocket sendingSocket);
}

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

互動模型​​​​​​​

設定連線後,我們可以繼續進行互動模型。RSocket支援以下操作:

  1. fire-forget
  2. request-resonse 傳統請求響應模型
  3. request-stream
  4. channel

​​​​​​​

在fire-forget模型中,還包括後設資料推送,傳送方都不關心操作的結果 , 它在返回型別(Mono)。在發生fire-forget時,完全成熟的幀被髮送到接收器,而對於後設資料推送動作,幀則不包含有效載荷 - 它僅包括頭部和後設資料,這種輕量級訊息可用於向IoT裝置的移動或對等通訊傳送通知。

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

雲應用程式中更有趣的是請求流request-stream和請求通道channel互動,它們對資料流進行操作,通常是無限的。在請求流操作的情況下,請求者將單個幀傳送給響應者並獲回資料流。這種互動方法使服務能夠將策略切換pull data到push data,而不是向響應者傳送定期請求查詢請求者,而是訂閱流並對傳入資料做出反應 - 將在可用時自動到達時發生反應。

由於多路複用和雙向資料傳輸支援,我們可以使用請求通道方法更進一步。RSocket能夠將資料從請求者流式傳輸到響應者,反之則使用單個物理連線。當請求者更新訂閱時,這種互動可能很有用 - 例如,更改訂閱標準。如果沒有雙向通道,客戶端將不得不取消流並使用新引數重新請求它。

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

public interface RSocket extends Availability, Closeable {

    Mono<Void> fireAndForget(Payload payload);

    Mono<Payload> requestResponse(Payload payload);

    Flux<Payload> requestStream(Payload payload);

    Flux<Payload> requestChannel(Publisher<Payload> payloads);

    Mono<Void> metadataPush(Payload payload);
}

在這個例子中,我們正在請求資料流,但是為了確保傳入的幀不會殺死請求者,我們將背壓機制放在適當的位置。為了實現這種機制,我們使用request_n框架.在訂閱[ onSubscribe(Subscription s)] 的開始時,我們正在請求5個物件,然後我們在onNext(Payload payload)中計算收到的專案。當所有預期的幀到達請求者時,我們正在請求接下來的5個物件 - 再次使用subscription.request(n)方法。

本節介紹的背壓機制的實施是非常基礎的。在生產中,我們應該基於更準確的度量提供更復雜的解決方案,例如預測/平均計算時間。畢竟,背壓機制不會使過度生產響應者的問題消失。它將問題轉移到響應方,在那裡可以更好地處理。關於背壓的進一步閱讀GitHub上

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

相關文章