YMNNALFT:轻松使用 RSocket 实现 RPC

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

欢迎来到 You May Not Need Another Library For That (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.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://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.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 社区中所有即将举行的活动。

查看全部