一RSocket 的主要特性
首先,RSocket 是高效一个二进制的网络通讯协议,能够满足很多场景下使用。其次,RSocket 是一个激进的响应式捍卫者,激进到连 API 都跟响应式无缝集成。
1四种通讯模式
即发即忘 FireAndForget
立即发送一个请求,无需为这个请求发送响应报文。适用于监控埋点,日志上报等,这种场景下无需回执,丢失几个请求无伤大雅。

请求响应 RequestResponse
请求方发送一条请求消息,响应方收到请求后并返回一条响应消息。传统的 HTTP 是典型的 RequestResponse。

流 RequestStream
请求方发送一个请求报文,响应方发回 N 个响应报文。传统的 MQ 是典型的 RequestStream。

通道 RequestChannel
创建一个通道上下文,双方可以互相发送消息。IM 是个典型的 RequestChannel 通讯场景。

2双向通讯 Bi-Directional
RSocket 的 Client 连接到 Server,这个过程称为 Setup,在连接成功后,会约定收发消息的方向逻辑:
- 当 Client 请求 Server 时,发送的请求 ID 永远为奇数
- 当 Server 请求 Client 时,发送的请求 ID 永远为偶数

正是因为这个奇偶性确定方向的特性,不同于传统的如 HTTP 请求,RSocket 可以做到双向请求。
3其他
- 二进制协议,紧凑高效
- 多路复用
- 基于帧(Frame)的背压,与 ReactiveStreams 语义契合
- 灵活的传输层切换: TCP/UDP/WebSocket 等
- 支持 Cancel、断点续传、租约等高级特性
综上与 HTTP 做一些比较,RSocket 的效率更高,支持的通讯场景更丰富,也没有队头阻塞的问题。与 SocketIO 这种基于纯事件的框架相比,RSocket 的请求具有很清晰的上下文,API 精炼易用。

二RSocket 的内部实现
1帧的设计
帧(Frame)是 RSocket 协议报文的最小单位。
- 一个帧由 6 bytes 的 Header 和剩余的 Body 构成,其中 Header 的 4 bytes 表示 StreamID,6 bits 表示 Frame Type, 10 bits 作为 Flags。Body 根据不同的帧类型,结构也不同,常用的带 Payload 的帧一般会包括 Metadata 和 Data 两个部分。
- 传输层如果本身不支持分帧特性的(如 TCP),那么 RSocket 会用 3 bytes 的 uint24 表示帧长度,所以最大的帧大小是 16MB。
- 如果帧超出 16MB,RSocket 支持帧分裂重组,也就是拆成更小的帧,接收端再自动重组。

2数据载体——Payload
基于帧之上,一般开发者接触到的是 Payload, 它类似一个 HTTP 报文,可以是一个 Request,也可以是一个 Response。由两个二进制部分组成:
- Metadata——元数据,类似 HTTP 的 header
- Data——数据,类似 HTTP 的 body

3架构
这里基于笔者在实现 Golang 版 SDK 的基础上整理的架构图,Java 版基本也类似。

- Transport 层将网络二进制流编解码为 Frames。
- RSocket 支持自定义最大 Frame Size,默认 16MB,当某个 Frame 超出时,会被拆解为 N 个小 Frame,收到时再重组,在介绍帧的时候也提到了,这个特性称为 Fragmentation。
- DuplexConnection 转换 Frames 为 Payload,抽象为一个个 Request/Response 上下文,并负责读写。
- RSocket 组装 Connection 为 RSocket Interface,其中 Resumable 支持断点续传,连接断开重连也能自愈,个人觉得这个特性有点鸡肋,在弱网环境有些优势,但是因为期间会缓存住未处理完毕的帧,所以会耗费大量的系统资源。
- RSocket 使用 Reactor 核心库暴露为 4 种通讯模式,抽象为高级 API。
4玩法
RSocket 有很多玩法,传统的 RPC 自然不在话下,用来做 IM 也未尝不可,某些特性也可以用来做代理或者网络穿透。
IoT 的场景,比如小明的家里有个智能空调,小明想在外面通过手机 APP 来控制空调开关,如何优雅地描述这个控制问题?最精炼的解决方案就是"小明调用空调上开关的 API"。

另外最经典的玩法就是 Broker 了,Broker 类似一种“软路由”的方案,可以让服务的发布访问变得简单。发布服务只要连接到 Broker,调用方通过反向请求的方式来让 Broker 透明转发即可,摒弃了传统的注册中心,端口管理等常见的服务治理手段。

5关于 RSocket Broker
Broker 有很多优势,发布服务不需要监听端口,无需 Sidecar,服务注册变得简单,无需 zk、etcd 之类,LoadBalance 变得简单,也更安全,没监听端口后很难攻击。也有很多劣势,网络上多了一跳,性能是有一定损耗的,Broker 是中心化设计,类似我们平时全局的 Nginx 一样,但是 Broker 的优雅启停显然更加复杂,受限于整个 Broker 集群的瓶颈等等。上帝为你关闭了一扇门,就一定会为你打开一扇窗。
目前高德落地的 FaaS 中大量使用了基于 RSocket 架构的集团 Broker,支撑了今年的五一长假,峰值 QPS 超 20 万,平稳零故障。

这里笔者也准备了一个教学用的 Mini Broker,演示了两个浏览器之间相互上下文调用彼此服务的场景,有兴趣的同学可以查看。

三响应式编程
响应式编程是个老话题了,它早已无处不在,甚至你在 Excel 里 SUM 求和,本质上也是种响应式的思维。响应式本质上就是响应变化的数据流。RSocket 这个协议本身就是以响应式之名,将其扩展到网络层面。
1响应式编程大概长这样

而在我们平时工作中,必然会引入各种操作和变换:

2Reactive Streams
JDK 推出了响应式标准 API,撇开 Processor 之外,其核心接口就 Publisher/Subscriber/Subscription,非常精炼。
- Publisher:发布者,负责生产数据。唯一的方法 subscribe,接收一个 Subscriber 开始一次新的订阅。
- Subscriber:订阅者,负责订阅消费数据。
- Subscription:订阅,某次订阅的上下文控制,如取消、通知获取下 N 条数据。
Spring 的 Reactor 是一个标准的实现,其一次完整的执行过程如下图:

- 创建 subscriber,开始订阅 Publisher。
- 生成上下文 subscription。
- Publisher 就绪,调用 onSubscribe。
- Publisher 开始生产数据。
- 每条成功生产的数据回调 onNext。
- 当生产失败时,回调 onError 并结束当前订阅。
- 当所有数据生产完毕时,回调 onComplete 并结束当前订阅。
- 中途可以调用 subscription 随时 cancel 取消订阅,或者通过 request(n)通知生产下 N 个元素,这个过程即背压。
由于 Java 天生的语言优势,很适合使用 RxJava 或 Reactor 之类的框架,代码逻辑清晰可读性会非常高。笔者在实现 Go 版的 Reactor 时,深深地体会到了没有泛型支持的 API 表现力是多么匮乏,也期待 Go2 的泛型能够有所改善。
四总结
RSocket 是个很有趣的网络协议,它可能不会普及流行,但贵在它解决问题的思路和设计很令人耳目一新。如果大家有兴趣,可以去它的官网了解下。