一、简介
RSocket 是一种二进制字节流传输协议,位于 OSI 模型中的5~6层,底层可以依赖 TCP、WebSocket、Aeron 协议。最初由 Netflix 开发,支持 Reactive Streams。其开发背后的动机是用开销更少的协议取代超文本传输协议(HTTP),HTTP 协议对于许多任务(如微服务通信)来说效率低下。
二、设计目标
- 支持对象传输,包括 Fire-and-Forget、Request/Response、Request/Stream、Channel
- 支持应用层流量控制
- 支持单连接双向、多次复用
- 支持连接修复
- 更好的使用WebSocket和Aeron协议
三、消息驱动
网络通信是异步的,RSocket 协议包含这一点,并将所有通信建模为在单个网络连接(TCP)上的、多路复用的消息流,在等待响应时从不同步阻塞。
响应式宣言指出:
反应式系统依赖异步的消息传递,从而确保了松耦合、隔离、位置透明的组件之间有着明确边界。这一边界还提供了将失败作为消息委托出去的手段。使用显式的消息传递,可以通过在系统中塑造并监视消息流队列, 并在必要时应用回压, 从而实现负载管理、 弹性以及流量控制。使用位置透明的消息传递作为通信的手段, 使得跨集群或者在单个主机中使用相同的结构成分和语义来管理失败成为了可能。非阻塞的通信使得接收者可以只在活动时才消耗资源, 从而减少系统开销。
此外,HTTP/2 FAQ 很好地解释了在持久连接上采用多路复用的面向消息的协议的动机:
- HTTP/1.x 有一个叫做 “head-of-line blocking(队头阻塞)” 的问题,在这种情况下,即在一个连接上一次只能有一个未完成请求。
- HTTP/1.1 试图通过流水线(Pipelining)来解决这个问题,但它并没有完全解决这个问题(大的或慢的响应仍然会阻塞后面的其他响应)。此外,人们发现流水线很难部署,因为许多代理和服务器不能正确地处理它。
在HTTP/1中使用并发连接和域名分片来缓解HOL问题
- 并发连接,浏览器针对每个源(域名)可以打开4-8个TCP连接,提升并发度。
- 域名分片,浏览器和HTTP/1限制了并发连接的数量,那么就把多个域名指向一台服务器,从而提升连接数量。
这迫使客户端使用一些启发式方法(通常是猜测)来确定什么请求在什么时候放在与源站的哪个连接上;由于加载一个页面的次数通常是可用连接数量的10倍或者更多。这会导致被阻塞的请求“瀑布式”的增长,从而严重的影响性能。
多路复用通过允许多个请求和响应消息在一个连接上同时传输来解决这些问题;甚至可以将一条消息的部分与另一条消息的部分混合在一起。
使用HTTP/1,浏览器为每个源打开4-8个连接,由于许多站点使用多个源,这可能意味着打开单个页面要加载30多个TCP连接。
一个应用程序同时打开如此多的连接,打破了TCP所建立的许多假设;由于每个连接都会在响应中传输大量的数据,因此TCP缓冲区很大可能会溢出,从而导致拥塞事件和超时重传。
一个应用程序同时打开如此多的连接,此外,使用如此多的连接不公平地垄断了网络资源,“窃取”了其他性能更好的应用程序(如VoIP)的资源。
四、Interaction Models(交互模型)
不合适的协议会增加系统开发的成本。它可能是一个不匹配的抽象,但是我们必须将系统设计强加到他允许的交互模型中。这迫使开发人员花费额外的时间来解决它的缺点,以处理错误并获得可接受的性能。在多语言环境中,这个问题被放大了,因为不同的语言将使用不同的方法来解决这个问题,这需要团队之间的额外协调。到目前为止,通信协议事实上的标准是HTTP,它只支持请求/响应的交互模式。在某些情况下,这可能不是最理想通信模型。
一个例子是推送通知。使用request/respones交互模型,客户端必须使用轮训不断检查服务端的状态。应用程序每秒执行大量的请求,只是为了轮询,然后被告知没有适合它们的东西。这对于客户端、服务器、网络来说是巨大的浪费。花费金钱;并增加了基础设施的规模、运营的复杂性,从而提高了可用性。它还通常会增加用户接收通知时的延迟,因为轮询会缩减到更长的间隔以试图降低成本。
出于这个和其他原因,RSocket不仅仅局限于一个交互模型。下面描述的各种支持的交互模型为系统设计提供了强大的新可能性:
4.1 Fire-and-Forget(即发即弃)
即发即弃是请求/响应的优化,在不需要响应时很有用。它允许显着的性能优化,不仅仅是通过跳过响应来节省网络使用,而且还可以减少客户端和服务器的处理时间,因为客户端不需要记录和等待请求关联的响应和取消请求。
此交互模型对于支持有损的用例非常有用,例如非关键事件日志记录。
可以这样使用:
Future<Void> completionSignalOfSend = socketClient.fireAndForget(message);
4.2 Request/Response (single-response)(请求/响应(单响应))
仍然支持标准请求/响应语义,并且仍有望代表 RSocket 连接上的大多数请求。这些请求/响应交互可以被认为是优化的“只有 1 个响应的流”,并且是在单个连接上多路复用的异步消息。
消费者“等待”响应消息,所以它看起来像一个典型的请求/响应,但它从不同步阻塞。
可以这样使用:
Future<Payload> response = socketClient.requestResponse(requestPayload);
4.3 Request/Stream (multi-response, finite)(请求/流(多响应,有限))
从request/response延伸出来的是request/stream,它允许多条消息流回。将此视为“集合”或“列表”响应,但不是将所有数据作为单个响应返回,而是按顺序流回每个元素。
用例可能包括以下内容:
- 获取视频列表
- 在目录中获取产品
- 逐行检索文件
可以这样使用:
Publisher<Payload> response = socketClient.requestStream(requestPayload);
4.4 Channel(通道)
通道是双向的,在两个方向上都有消息流。
受益于此交互模型的示例用例是:
- 客户端请求一个数据流,该数据流最初会破坏当前的世界视图
- 当发生变化时,增量/差异从服务器发送到客户端
- 客户端随时间更新订阅以添加/删除标准/主题/等。
如果没有双向通道,客户端将不得不取消初始请求,重新请求并从头开始接收所有数据,而不是仅仅更新订阅并有效地获取差异。
可以这样使用:
Publisher<Payload> output = socketClient.requestChannel(Publisher<Payload> input);
五、协议形式
- 连接上传输的数据是流(Stream)
- 流(Stream)由帧(Frame)组成
- 帧(Frame)包含了元数据(MetaData)与业务数据(Data)
基于 RSocket 协议,我们的业务数据会被打包成帧,并以帧流的形式在客户端与服务端互相传输。所以 RSocket 的所有特性都是基于这个帧流实现的。协议详情可以参考:https://rsocket.io/about/protocol
RSocket是一个二进制协议,也就是说在一个RSocket连接上传输的消息体对数据格式没有任何要求,应用程序可以为所欲为的压缩数据量的大小。
这样的二进制协议通常来说能给性能带来极大的提升,但是产生的代价是,网络中间件也会因为无法解读消息体中的数据,丧失了在对具体应用流量进行监控,日志和路由的能力。RSocket通过把每个消息体分成data和metadata的方式,在保证高效传输的前提下,提供了暴露少量元数据给网络中间件的能力。
对于每个data和metadata,应用可以采用不同的序列化方法。
- data一般作为应用本身需要传递的业务数据,采取自定义的高效序列化方式,且对网络基础设施不可见。
- metadata可以采用网络基础设施一致默认的格式。在分布式传输的过程中,这些中间件可以按需求对metadata进行读写,然后监控应用健康状况或者调整路由。
六、RSocket与其它协议有什么区别?
6.1 对比Http1.x
Http1.x只支持request/response,但是现实应用中并不是所有请求都需要有回应(Fire And Forget)、有的需求需要一个请求返回一个数据流(request/stream)、有的还需要双向数据传输(channel)。
6.2 对比Http2.x
http2.x不支持应用层流量控制、伪双向传输,即服务端push数据本质上还是对客户端请求的响应,而不是直接推送。RSocket做到了真正的双向传输,使得服务端可以调用客户端服务,使得服务端和客户端在角色上完全对等,即两边同时是Requester和Responder。
6.3 对比grpc
- grpc需要依赖protobuf,本质上还是http2.x。RSocket不限制编解码,可以是xml、json、protobuf等。
- 性能上grpc要差一些:详见压测对比,https://dzone.com/articles/rsocket-vs-grpc-benchmark
6.4 对比TCP
一个应用层的协议、一个传输层的协议,其实两者不在一个层面,为啥要作比较呢,因为netty让tcp层的编程也很容易,但是需要自定义传输协议,比如定义header、body长度等等,用起来还是很麻烦的。
6.5 对比WebSockets
websocket不支持应用层流量控制,本质上也是一端请求另一端响应,不支持连接修复。
七、RSocket适用于哪些场景?
- 移动设备与服务器的连接
- 数据双向传输,且支持流量控制。支持背压,背压的意思:如果客户端请求服务端过快,那么服务端会堆积请求,最终耗光资源。有了背压机制,服务端可以根据自己的资源来控制客户端的请求速度,即调用客户端告诉它别发那么快。
- 支持连接修复,比如手机进地铁之后,网络断开一段时间,其他协议需要重新建立连接,RSocket则可以修复连接继续传输帧数据。
- 微服务场景
- spring cloud目前支持的http协议,不能fire and forget、不能请求流数据、不能单连接双向调用;替换成RSocket之后可以满足以上需求的同时提高性能。且针对服务治理、负载均衡等RSocket都在慢慢完善。
- 由于微服务和移动设备的普及,RSocket火起来应该就是这几年的事儿,让我们拭目以待吧。
欢迎大家关注我的公众号【老周聊架构】,Java后端主流技术栈的原理、源码分析、架构以及各种互联网高并发、高性能、高可用的解决方案。