YMNNALFT:使用 RSocket 輕鬆實現 RPC

工程 | Josh Long | 2021 年 1 月 18 日 | ...

歡迎來到新一期的你可能不需要另一個程式庫 (YMNNALFT)!自 2016 年以來,我花了很多時間在 我的 Spring Tips 影片中闡明(或嘗試闡明!)Spring 生態系統中一些更巨大的機會。 然而,今天,我以不同的精神來到這裡,想關注那些做著奇妙事情,並且可以讓你免於額外的第三方依賴及其隱含複雜性的小而有時隱藏的寶石。

整合兩個被常見、可能不穩定的且不堪重負的網路分隔的服務,是最具挑戰性的電腦科學問題之一。

快速題外話:電腦科學中最具挑戰性的問題當然是 CSS 中的垂直佈局。

你可以寫一整本書關於整合不同系統和服務的不同方式。但是,Gregor HohpeBobby Woolf 已經用他們的 企業整合模式一書做到了,所以我將使用他們的列表之一。

訊息傳遞是生產者將訊息(包含封包和有效負載)發送到可靠的中介代理。該代理充當生產者和消費者之間訊息的傳遞服務。

RPC,或遠端代理呼叫...,不對,不是那個。 有風險的程序呼叫? 不是...相對無痛的災難? 不是...遠端程序呼叫! 就是那個。 RPC 是消費者調用遠端物件上的方法(通過某種網路協定,例如 SOAP-RPC、Hessian、Burlap、Spring 自己的 HTTP Invoker、XML RPC、EJB、RMI、DCOM、CORBA 等)。 體驗旨在感覺像在同一個虛擬機器中調用本地物件上的方法。

檔案傳輸是生產者將檔案傳輸到共享、約定的(網路)檔案系統,而消費者使用儲存在那裡的訊息。 這是當今許多批次處理的基礎。 如果你還沒有,你應該看看 Spring Batch。 十分之九的牙醫同意:Spring Batch 保持牙齒清潔和整合過程精簡。

共享資料庫是生產者和消費者從同一個表中讀取資料(不建議)。 實際上,這在目前是一個有點反模式的方法,尤其是在微服務的上下文中。

肯定有關於 RPC 的優點與訊息傳遞作為可靠地整合生產者和消費者的方式的討論,但不是那個討論,因為我認為我已經找到了最好的妥協方案:反應式、有效負載不可知、閃電般的快速、可觀察的 RSocket。RSocket 是一種二進位協定,最初由 Netflix 的工程師開發,他們離開後繼續在 Facebook 上工作。 該協定是為規模速度而建構的,並規避了 HTTP 1-2 和 gRPC 的許多限制。 由於許多原因,它是一種令人無比興奮的協定

  • 它支援適當的雙向通訊
  • 它支援許多不同的訊息交換模式,而不僅僅是請求/回應
  • 它支援元資料以傳播帶外資訊,例如令牌
  • 它在網路協定層面具體化了 Reactive Streams 規範概念(背壓!在線上!萬歲!)
  • 它有一個很酷的.io 網域,大家都知道這對於註定要進入雲端的技術的成功至關重要

它是一種以訊息封包為中心的協定,但它使用起來很簡單,如果你想過 RPC 生活,它甚至更簡單。

有許多適用於各種語言的客戶端,包括 Java。 Java 客戶端建構在 Project Reactor 之上。 即使 Spring 本身沒有原生支援,將 RSocket 整合到 Spring 應用程式中也是微不足道的-微不足道! 我說-。 但 Spring 本身確實有原生支援,而且非常棒。 該整合使用與 Spring Framework 4 中的原始 WebSocket 支援相同的組件模型。

讓我們看一個簡單的範例服務。

你需要以下依賴項。

  • Spring Initializr 上的 RSocket - org.springframework.bootspring-boot-starter-rsocket

這是程式碼

package bootiful.rpc.server;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.messaging.handler.annotation.DestinationVariable;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Controller;

import java.util.Locale;

@SpringBootApplication
public class BootifulApplication {

	public static void main(String[] args) {
		System.setProperty("spring.profiles.active", "rpcserver");
		SpringApplication.run(BootifulApplication.class, args);
	}

}

@Controller
class GreetingsController {

	@MessageMapping("greetings.{lang}")
	String greet(@DestinationVariable("lang") Locale lang, @Payload String name) {
		System.out.println("locale: " + lang.getLanguage());
		return "Hello, " + name + "!";
	}

}

這是我放入 application.properties 中的內容

spring.rsocket.server.port=8888
spring.main.web-application-type=none

控制器是一個具有方法的物件,類似於 RPC,但嚴格來說,客戶端沒有義務等待回應。 它可以將線程置於後台或完全斷開連線。 雙贏。 在幕後,該協定比組件模型顯示的更以信封和有效負載為中心,因此我們可以獲得兩全其美的優點。

我們的服務已啟動並執行。 如果你想調用它,你可以使用 方便的 rsc CLI

rsc tcp://127.0.0.1:8888  -r greetings.en -d 'Josh' 

你應該得到這樣的輸出

Hello, Josh!

這可能就足夠了,但我們大多數人希望從我們的客戶端程式碼與我們的 RSocket 服務通訊。 有來自幾種不同程式設計語言的客戶端,包括但不限於 JavaScript、Go、.NET (C#)、Rust、C++、Ruby、Python 等。(而且,最壞的情況是,你總是可以封裝 C++ 或 Java 端口,對嗎?)

讓我們看看建構一個客戶端來與新建立的服務通訊。 我們將使用 RSocketRequester,一個可用於與 RSocket 端點通訊的客戶端。

你需要以下依賴項

  • Spring Initializr 上的 RSocket - org.springframework.bootspring-boot-starter-rsocket

這是程式碼

package bootiful.rpc.client;

import lombok.SneakyThrows;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.messaging.rsocket.RSocketRequester;

import java.util.Locale;

@SpringBootApplication
public class BootifulApplication {

	@SneakyThrows
	public static void main(String[] args) {
		System.setProperty("spring.profiles.active", "rpcclient");
		SpringApplication.run(BootifulApplication.class, args);
		Thread.sleep(5_000);
	}

	@Bean
	RSocketRequester rSocketRequester(RSocketRequester.Builder builder) {
		return builder.tcp("localhost", 8888);
	}

	@Bean
	ApplicationListener<ApplicationReadyEvent> ready(RSocketRequester rSocketRequester) {
		return event -> rSocketRequester //
				.route("greetings.{lang}", Locale.ENGLISH) //
				.data("World").retrieveMono(String.class)//
				.subscribe(greetings -> System.out.println("got: " + greetings));
	}

}

這是我放入 application.properties 中的內容

spring.main.web-application-type=none

在這裡,你可以看到 RSocket 服務的預設客戶端體驗更像是 HTTP 端點或與訊息佇列的交換。 我們將請求訊息發送到端點,這些端點更像是 URI,而不是分散式方法。 也就是說,如果你真的非常喜歡 RPC,並且不介意可選的額外依賴項。 你可能考慮實驗性的 Spring Retrosocket 專案,我們啟動該專案正是為了支援這個用例。 它提供了類似 Netflix-feign 的 RPC 體驗,但適用於 RSocket。

你喜歡這種一目了然的方法嗎? 你學到了什麼嗎? 與往常一樣,我很想聽到你的意見,所以 請在 Twitter 上發聲 (@starbuxman)! 我會帶著另一期YMNNALFT回來,所以一定要錯過。

取得 Spring 電子報

與 Spring 電子報保持聯繫

訂閱

取得領先

VMware 提供培訓和認證,以加速你的進展。

了解更多

取得支援

Tanzu Spring 在一個簡單的訂閱中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位檔案。

了解更多

即將舉行的活動

查看 Spring 社群中所有即將舉行的活動。

查看全部