YMNNALFT: 使用 RSocket 实现轻松 RPC

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

欢迎来到另一期《你可能不需要另一个库》(You May Not Need Another Library For That) (YMNNALFT)! 自2016年以来,我在我的 Spring 技巧视频中花费了大量时间来阐明(或者至少尝试阐明!)Spring 生态系统中一些更巨大的机会。然而今天,我带着不同的精神来到这里,想要关注那些有时隐藏起来的小瑰宝,它们能做得很出色,并且可以为你省去额外的第三方依赖及其隐含的复杂性。

集成由共同的、可能不稳定且拥挤的网络隔开的两个服务是计算机科学中最具挑战性的问题之一。

快速插话:计算机科学中最具挑战性的问题当然是 CSS 中的垂直布局。

关于集成不同系统和服务的各种方法,你可以写一整本书。但是,Gregor HohpeBobby Woolf 已经用他们的著作《企业集成模式》做到了这一点,所以我将使用他们列表中的一项。

消息传递是指生产者将消息(带有信封和载荷)发送到可靠的中介代理。该代理充当生产者和消费者之间消息的传递服务。

RPC,或者说远程代理调用(Remote Proxy Calls)...... 不对,不是这个。危险过程调用(Risky Procedure Calls)?不对...... 相对无痛灾难(Relatively Painless Calamities)?也不对...... 远程过程调用(Remote Procedure Calls)!就是这个。RPC 是指消费者(通过某种网络协议,如 SOAP-RPC、Hessian、Burlap、Spring 自带的 HTTP Invoker、XML RPC、EJBs、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.boot : spring-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://localhost: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.boot : spring-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 社区中所有即将举行的活动。

查看全部