使用RSocket進行服務通訊的反應性服務 - 負載平衡和可恢復性 | Rafał Kowalski

banq發表於2019-07-26

RSocket可以徹底改變分散式系統中的機器到機器通訊。在以下段落中,我們將討論雲中的負載平衡問題以及我們將介紹有助於處理網路問題的可恢復性功能,尤其是在物聯網系統中。

請注意,本文中提供的程式碼示例可在GitHub上獲得

高可用性和負載平衡是企業級系統的關鍵部分
應用程式可用性和可靠性是銀行和保險等許多業務領域的重要組成部分。在這些要求苛刻的行業中,即使在高流量,網路延遲增加或自然災害期間,服務也必須全天候運營。為確保終端使用者始終可以使用該軟體,通常會在多個可用區域中以冗餘方式部署該軟體。
在這種情況下,每個微服務的至少兩個例項部署在至少兩個可用區中。這種技術有助於我們的系統恢復彈性並增加其容量 - 微服務的多個例項能夠處理明顯更高的負載。那麼訣竅在哪裡?冗餘引入了額外的複雜性。作為工程師,我們必須確保傳入流量分佈在所有可用例項中。解決此問題有兩種主要技術:伺服器負載平衡和客戶端負載平衡。
第一種方法基於請求者不知道響應者的IP地址的假設。取而代之的是,請求者與負載均衡器進行通訊,負載均衡器負責將請求分佈在與其連線的微服務上。這種設計在雲時代相當容易採用。IaaS提供商通常擁有內建的可靠解決方案,例如Amazon Web Services中提供的Elastic Load Balancer。這種技術的主要缺點是我們必須配置和部署額外的資源,如果我們的系統由數百個微服務組成,這可能會很痛苦。此外,它可能會影響延遲 - 每個請求在負載均衡器上都有額外的“網路跳躍”。
第二種技術顛倒了這種關係。請求者不知道用於連線到響應者的中心點,而是知道給定微服務的每個例項的IP地址。擁有這些知識後,客戶端可以選擇傳送請求的響應者例項或開啟連線。此策略不需要任何額外資源,但我們必須確保請求者具有響應者的所有例項的IP地址,客戶端負載平衡模式的主要好處是它的效能 - 透過減少一個額外的“網路跳躍”,我們可以顯著減少延遲。這是RSocket實現客戶端負載平衡模式的關鍵原因之一。

RSocket中的客戶端負載平衡
在程式碼級別上,在RSocket中實現客戶端負載平衡非常簡單。該機制依賴於LoadBalancedRSocketMono作為一包可用RSocket例項的物件,由RSocket供應商提供。要訪問RSockets,我們必須訂閱LoadBalancedRSocketMono哪個onNext訊號發出完全成熟的RSocket例項。此外,它計算每個RSocket的統計資料,以便能夠估計每個例項的負載,並在此基礎上選擇在給定時間點具有最佳效能的例項。
該演算法考慮了多個引數,如延遲,維護連線數以及許多待處理請求。每個RSocket的執行狀況由可用性引數反映 - 該值從0到1取值,其中0表示給定例項無法處理任何請求,1表示分配給完全執行的套接字。下面的程式碼片段顯示了負載均衡的RSocket的基本示例,它連線到響應者的三個不同例項並執行100個請求。每次它從LoadBalancedRSocketMono物件中獲取RSocket 。

@Slf4j
public class LoadBalancedClient {

    static final int[] PORTS = new int[]{7000, 7001, 7002};

    public static void main(String[] args) {

        List<RSocketSupplier> rsocketSuppliers = Arrays.stream(PORTS)
                .mapToObj(port -> new RSocketSupplier(() -> RSocketFactory.connect()
                        .transport(TcpClientTransport.create(HOST, port))
                        .start()))
                .collect(Collectors.toList());

        LoadBalancedRSocketMono balancer = LoadBalancedRSocketMono.create((Publisher<Collection<RSocketSupplier>>) s -> {
            s.onNext(rsocketSuppliers);
            s.onComplete();
        });

        Flux.range(0, 100)
                .flatMap(i -> balancer)
                .doOnNext(rSocket -> rSocket.requestResponse(DefaultPayload.create("test-request")).block())
                .blockLast();
    }

}

如果雲中的機器到機器通訊,實時流資料並不是什麼大問題,但如果我們考慮位於無法訪問穩定、可靠的網際網路連線的區域的物聯網裝置,問題就會變得更多複雜。我們可以輕鬆確定在這樣的系統中可能遇到的兩個主要問題:網路延遲和連線穩定性。從軟體的角度來看,我們可以用第一個做很多事情,但我們可以嘗試處理後者。讓我們用RSocket來解決這個問題,從選擇合適的互動模型開始。在這種情況下最合適的是request stream方法,其中部署在雲中的微服務是請求者,溫度感測器是響應者。在選擇互動模型後,我們應用了可恢復性機制。在RSocket中,我們透過resume()呼叫的方法來實現RSocketFactory,如下例所示:

@Slf4j
public class ResumableRequester {

    private static final int CLIENT_PORT = 7001;

    public static void main(String[] args) {
        RSocket socket = RSocketFactory.connect()
                .resume()
                .resumeSessionDuration(RESUME_SESSION_DURATION)
                .transport(TcpClientTransport.create(HOST, CLIENT_PORT))
                .start()
                .block();
        socket.requestStream(DefaultPayload.create("dummy"))
                .map(payload -> {
                    log.info("Received data: [{}]", payload.getDataUtf8());
                    return payload;
                })
                .blockLast();

    }
}

@Slf4j
public class ResumableResponder {

    private static final int SERVER_PORT = 7000;
    static final String HOST = "localhost";
    static final Duration RESUME_SESSION_DURATION = Duration.ofSeconds(60);

    public static void main(String[] args) throws InterruptedException {
        RSocketFactory.receive()
                .resume()
                .resumeSessionDuration(RESUME_SESSION_DURATION)
                .acceptor((setup, sendingSocket) -> Mono.just(new AbstractRSocket() {
                    @Override
                    public Flux<Payload> requestStream(Payload payload) {
                        log.info("Received 'requestStream' request with payload: [{}]", payload.getDataUtf8());
                        return Flux.interval(Duration.ofMillis(1000))
                                .map(t -> DefaultPayload.create(t.toString()));
                    }
                }))
                .transport(TcpServerTransport.create(HOST, SERVER_PORT))
                .start()
                .subscribe();
        log.info("Server running");

        Thread.currentThread().join();
    }
}

請注意,要執行提供的示例,您需要在計算機上安裝“socat”,請參閱自述檔案以獲取更多詳細資訊

請求者和響應者端的機制類似,它基於一些元件。首先,有一個ResumableFramesStore作為幀的緩衝區。預設情況下,它將它們儲存在記憶體中,但我們可以透過實現ResumableFramesStore介面輕鬆調整它以滿足我們的需求(例如,將幀儲存在分散式快取中,如Redis)。儲存儲存在保持活動幀之間發出的資料,這些幀是定期來回傳送的,並指示對等體之間的連線是否穩定。
此外,保持活動幀包含令牌,該令牌確定請求者和響應者的最後接收位置。當對等方想要恢復連線時,它會傳送具有隱含位置的恢復幀。隱含位置是根據上次接收的位置(與我們在保持活動幀中看到的值相同)加上從該時刻收到的幀的長度計算得出的。該演算法適用於通訊雙方,在恢復幀中,它由最後接收的伺服器位置和第一個客戶端可用位置令牌反映。(恢復操作的整個流程點選標題見原文配圖)
透過採用RSocket協議中內建的可恢復性機制,我們可以相對較低的努力減少網路問題的影響。如上例所示,可恢復性在資料流應用程式中可能非常有用,尤其是在裝置進行雲通訊的情況下。

總結
在本文中,我們討論了RSocket協議的更高階功能,這些功能有助於減少網路對系統操作性的影響。我們介紹了客戶端負載均衡模式和可恢復性機制的實現。這些功能與強大的互動模型相結合構成了協議的核心。

(banq注:重試 冪等這些機制在Rsocket實現是否需要考慮?)

 

相關文章