我们知道,常见的 RESTful Web 服务采用的都是基于 HTTP 协议实现的请求 - 响应式交互方式。这种交互方案很简单,但是因为只支持单一的交互方式,也就无法应对所有日常开发需求场景,例如服务端主动向客户端推送数据。
请求 - 响应模式
那么,能不能在网络协议层上具备更加丰富的交互方式呢?答案是肯定的,这就是我们今天要讨论的 RSocket 协议。RSocket 协议提供了多种客户端和服务端之间的交互方式,它并不是对 HTTP 协议的补充,而是一个基于响应式编程技术的全新的、高性能网络通讯协议。
在引入 RSocket 协议之前,你必须要了解为什么需要这样一个协议,让我们从传统的请求 - 响应模式所存在的问题开始说起。
RSocket 协议解决了什么问题?
请求 - 响应模式的问题
请求 - 响应模式的一个问题是,高并发场景下,性能和响应性上存在瓶颈。因为整个处理过程是同步阻塞的,如果某个请求的响应时间过长,会导致其他请求无法及时响应。这一点我之前在之前的响应式编程一课中有详细讲解。
更重要的是,对很多应用场景来说,HTTP 协议提供的请求 - 响应模式是不合适的。典型的例子是消息推送,如果客户端需要获取最新的推送消息,就必须使用轮询,客户端不停的发送请求到服务器来检查更新,无疑这会造成了大量资源浪费。
客户端轮询模式
你可能会说,我们可以使用服务器发送事件(Server-Sent Events,SSE)技术实现从服务端向客户端推送消息。不过,SSE 也是一个构建在 HTTP 协议上的处理机制,一般只用来传送文本,提供的功能非常有限。
服务器发送事件
幸运的是,业界认识到了异步、多向交互通信的必要性。在 2015 年,RSocket 协议就在这样的背景下诞生了。
RSocket 协议的解决方案
RSocket 是一种语言无关的二进制网络协议,用来解决现有网络传输协议存在的单一请求 - 响应模式以及性能问题。那么,它是怎么解决这个问题的呢?
RSocket 以异步消息的方式提供 4 种交互模式,除了请求 - 响应(request/response)模式之外,还包括请求 - 响应流(request/stream)、即发即弃(fire-and-forget)和通道(channel)这三种新的交互模式。
RSocket 协议的四种交互模式
我们来看这四种交互模式的特点:
请求 - 响应模式:这是最典型也最常见的模式。发送方在发送消息给接收方之后,等待与之对应的响应消息
请求 - 响应流模式:发送方的每个请求消息,都对应接收方的一个消息流作为响应
即发即忘模式:发送方的请求消息没有与之对应的响应
通道模式:在发送方和接收方之间建立一个双向传输的通道
我们可以从请求与响应的数量对应关系来对上述四种交互模式做一个总结。
可以看到,当我们选择具体的交互模式时,请求 - 响应、请求 - 响应流和即发即忘这三种交互模式的请求数量都是 1,并能够获取不同数量的响应结果。我们就可以根据这一特性来重构现有的请求处理过程。而通道模式则比较特殊,它的请求数量和响应数量都是 N,决定了我们可以选择它来应对双向的数据流处理场景。
RSocket 协议专门设计用来和响应式编程技术风格的应用程序配合使用,在使用 RSocket 协议时,响应式编程体系中的数据流机制仍然有效。
为了更好的理解 RSocket 协议,我们对比它和 HTTP 协议。在交互模式上,与 HTTP 的请求 - 响应这种单向交互模式不同,RSocket 倡导的是对等通信。这种对等通信不是传统单向交互模式的改进,而是在客户端和服务端之间可以自由的相互发送和处理请求。
RSocket 协议的交互方式
另一方面,从性能上讲,我们知道 HTTP 协议为了兼容各种应用方式,本身有一定的复杂性和冗余性,性能一般。而 RSocket 采用的是自定义二进制协议,本身的定位就是高性能通讯协议,性能上比 HTTP 高出一个数量级。
如何正确使用 RSocket 协议?
到这里,我们就明白了 RSocket 协议是要解决什么问题,以及是如何解决的了。接下来,我们具体看看如何正确使用 RSocket 协议。
RSocket 接口
我们先来看一下 RSocket 协议中最核心的接口,即 RSocket 接口的定义,如下所示。
public interface RSocket extends Availability, Closeable {
//推送元信息,数据可以自定义
Mono<Void> metadataPush(Payload payload);
//请求-响应模式,发送一 个请求并接收一个响应
Mono<Payload> requestResponse(Payload payload);
//即发-即忘模式,请求-响应的优化,在不需要响应时非常有用
Mono<Void> fireAndForget(Payload payload);
//请求-响应流模式,类似于返回集合的请求/响应,集合将以流的方式返回,而不是等到查询完成
Flux<Payload> requestStream(Payload payload);
//通道模式,允许任意交互模型的双向消息流
Flux<Payload> requestChannel(Publisher<Payload> payloads);
}
显然,RSocket 接口通过四个方法分别实现了它所提供的四种交互模式,这里的 Payload 代表的就是一种消息对象,由两部分组成,即元信息 metadata 和数据 data,类似于常见的消息通信中的消息头和消息体的概念。
同时,你注意到吗,这里出现了两个新的数据结构 Mono 和 Flux。在响应式编程中,Mono 代表只包含 0 个或 1 个元素的数据流,而对应的 Flux 则是一个包含 0 到 n 个元素的数据流。
所以 fireAndForget() 方法返回的是一个 Mono 流,符合即发 - 即弃模式的语义。而 requestStream() 作为请求 - 响应流模式的实现,与 requestResponse() 的区别在于它的返回值是一个 Flux 流,而不是一个 Mono 对象。而且,RSocket 提供的请求 - 响应模式也比 HTTP 更具优势,因为它是异步且多路复用的。
另一方面,与其他方法不同,requestChannel() 方法返回的并不是一个 Payload 消息对象,而是一个代表响应式流的 Publisher 对象,意味着这种模式下的输入输出都是响应式流,也就是说可以实现客户端和服务端之间的双向交互,这也和通道模式的定义一致。
RSocket 与框架集成
RSocket 接口过于底层,开发人员需要考虑服务器端和客户端的具体构建方式以及手工实现远程调用的细节。所以,我通常不建议直接使用 RSocket 接口进行应用程序的开发,而是倾向于借助特定的开发框架。如果你使用的是 Spring Boot 框架,就可以构建如下所示一个简单 Controller:
@Controller
public class HelloController {
@MessageMapping("hello")
public Mono<String> hello(String name) {
return Mono.just("Hello: " + name);
}
}
这里引入了一个新的注解@MessageMapping,类似 Spring MVC 中的@RequestMapping 注解,@MessageMapping 是,Spring 中提供,用来指定 RSocket 协议中消息处理的目的地。然后,我们输入了一个 String 类型的参数并返回一个 Mono 对象,符合请求 - 响应交互模式的定义。
为了访问这个 RSocket 端点,我们需要构建一个 RSocketRequester 请求对象。基于该对象,我们就可以通过它的 route() 方法路由到前面通过@MessageMapping 注解构建的"hello"端点,如下所示:
Mono<String> response = requester.route("hello")
.data("Geektime")
.retrieveMono(String.class);
我们再来看一个请求 - 响应流的示例,如下所示:
@MessageMapping("stream")
Flux<Message> stream(Request request) {
return Flux
.interval(Duration.ofSeconds(1))
.map(index -> new Message(request.getParam, index));
}
这里我们根据输入的 Request 对象,返回一个 Flux 流,每一秒发送一个新的 Message 对象。
如果你想在其他框架中使用 RSocket 协议,也有很多选择。Dubbo 在 3.0.0-SNAPSHOT 版本里基于 RSocket 对响应式编程提供了支持,开发人员可以非常方便的使用 RSocket 的 API。而随着 Spring 框架的持续升级,5.2 版本中也把 RSocket 作为缺省的网络通信协议。
目前,RSocket 协议的应用已经越来越广泛。相信随着这项技术的不断成熟,日常开发过程中也会出现更多的应用场景和解决方案。
总结
今天我们系统讨论了 RSocket 这款新的高性能网络通信协议。与 HTTP 协议相比,RSocket 提供了四种不同的交互模式来实现多样化的网络通信。同时,RSocket 也无缝集成了响应式编程技术,我们可以通过 RSocket 协议来实现异步、非阻塞式的网络通信。