一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 是个很有趣的网络协议,它可能不会普及流行,但贵在它解决问题的思路和设计很令人耳目一新。如果大家有兴趣,可以去它的官网了解下。