大型分布式系统通常由不同团队使用各种技术和编程语言以模块化方式实现。这些部件需要可靠地通信并支持快速,独立的演进。模块之间有效且可扩展的通信是分布式系统中的关键问题。它会显着影响用户体验的延迟时间以及构建和运行系统所需的资源量。
反应式宣言中记录并在Reactive Streams和Reactive Extensions等库中实现的架构模式支持异步消息传递,并包含超出请求/响应的通信模式。这种“RSocket”协议是一种包含“反应”原则的正式通信协议。
以下是我们定义新协议的动机:
消息驱动
网络通信是异步的。RSocket协议包含了这一点,并通过单个网络连接将所有通信建模为多路复用的消息流,并且在等待响应时不会同步阻塞。
“ 反应式宣言”指出:
Reactive Systems依靠异步消息传递在组件之间建立边界,以确保松散耦合,隔离,位置透明性,并提供将错误委派为消息的方法。使用显式消息传递可以通过整形和监视系统中的消息队列并在必要时应用反压来实现负载管理,弹性和流量控制。…非阻塞通信允许收件人在活动时仅消耗资源,从而减少系统开销。
此外,HTTP / 2 FAQ很好地解释了以持久连接的多路复用形式采用面向消息的协议的动机:
HTTP / 1.x有一个称为“行头阻塞”的问题,实际上一次只有一个请求在连接上是未完成的。
HTTP / 1.1试图通过流水线修复此问题,但它没有完全解决问题(大的或慢的响应仍然可以阻止其他人)。此外,发现流水线很难部署,因为许多中介和服务器不能正确处理它。
这迫使客户端使用一些启发式(通常是猜测)来确定在什么时候连接到源的请求; 因为页面加载10次(或更多)可用连接的数量很常见,这会严重影响性能,通常会导致被阻止请求的“瀑布”。
多路复用通过允许多个请求和响应消息同时在飞行中来解决这些问题; 它甚至可以将一条消息的一部分与另一条消息混合在一起。
反过来,这允许客户端每个源只使用一个连接来加载页面。
该FAQ也讨论了持久连接:
使用HTTP / 1,浏览器可以在每个源的4到8个连接之间打开。由于许多站点使用多个源,这可能意味着单个页面加载打开超过30个连接。
一个应用程序打开如此多的连接同时打破了TCP构建的许多假设; 由于每个连接都会在响应中启动大量数据,因此存在中间网络中的缓冲区溢出,导致拥塞事件和重新传输的真实风险。
此外,使用如此多的连接不公平地垄断网络资源,将其从其他更好的应用程序(例如,VoIP)“窃取”。
互动模型
不合适的协议会增加开发系统的成本。它可能是一种不匹配的抽象,迫使系统的设计进入它允许的模具。这迫使开发人员花费额外的时间来解决其缺点,以处理错误并实现可接受的性能。在多语言环境中,这个问题被放大了,因为不同的语言将使用不同的方法来解决这个问题,这需要团队之间的额外协调。迄今为止,HTTP是事实上的标准,一切都是请求/响应。在某些情况下,这可能不是给定功能的理想通信模型。
一个这样的例子是推送通知。使用请求/响应强制应用程序执行轮询,其中客户端始终发送检查服务器以获取数据的请求。人们不需要远远地查找应用程序执行大量请求/秒的示例只是为了进行轮询并被告知没有任何内容可供他们使用。这对客户端,服务器和网络来说都是浪费; 花钱; 并增加基础设施规模,运营复杂性和可用性。它还通常在接收通知时增加用户体验的等待时间,因为为了降低成本,轮询被缩减到更长的间隔。
由于这个和其他原因,RSocket不仅限于一个交互模型。下面描述的各种支持的交互模型为系统设计开辟了强大的新可能性:
即发即忘
“即发即忘”是对请求/响应的优化,在不需要响应时非常有用。它允许显着的性能优化,不仅仅是通过跳过响应来节省网络使用,而且还允许在客户端和服务器处理时间内,因为不需要簿记来等待并关联响应或取消请求。
此交互模型对于支持损失的用例非常有用,例如非关键事件日志记录。
其用法可以这样想:
1
Future<Void> completionSignalOfSend = socketClient.fireAndForget(message);
请求/响应(单响应)
RSocket仍然支持标准的请求/响应语义,并且仍然期望通过RSocket连接表示大多数请求。这些请求/响应交互可以被认为是优化的“仅1个响应的流”,并且是通过单个连接复用的异步消息。
消费者“等待”响应消息,因此它看起来像一个典型的请求/响应,但它下面永远不会同步阻塞。
其用法可以这样想:
1
Future<Payload> response = socketClient.requestResponse(requestPayload);
请求/流(多响应,有限)
从请求/响应扩展是请求/流,其允许流回多个消息。可以将其视为“集合”或“列表”响应,但不是将所有数据作为单个响应返回,而是按顺序流回每个元素。
用例可能包括以下内容:
- 获取视频列表
- 在目录中获取产品
- 逐行检索文件
其用法可以这样想:
1
Publisher<Payload> response = socketClient.requestStream(requestPayload);
通道
通道是双向的,在两个方向上都有一串消息。受益于此交互模型的示例用例是:
- 客户端请求最初突发当前世界视图的数据流
- 当发生更改时,将从服务器发送到deltas / diffs
- 客户端随时间更新订阅以添加/删除条件/主题/等。
如果没有双向通道,客户端将不得不取消初始请求,重新请求并从头开始接收所有数据,而不仅仅是更新订阅并有效地获得差异。
其用法可以这样想:
1
Publisher<Payload> output = socketClient.requestChannel(Publisher<Payload> input);
行为
除了上面的交互模型之外,还有其他行为可以使应用程序和系统效率受益。
单响应与多响应
单响应和多响应之间的一个关键区别是RSocket堆栈如何向应用程序传递数据:单个响应可能跨多个帧传输,并且是多路复用的多个消息流传输的更大RSocket连接的一部分。但单响应意味着应用程序仅在收到整个响应时才获取其数据。虽然多重响应可以实现零碎。这可以允许用户在设计其服务时考虑到多响应,然后客户端可以在收到第一个块后立即开始处理数据。
双向
RSocket支持双向请求,其中客户端和服务器都可以充当请求者或响应者。这允许客户端(例如用户设备)充当来自服务器的请求的响应者。
例如,服务器可以向客户端查询跟踪调试信息、状态等。这可以通过允许服务器端在需要时进行查询而不是让数百万/数十亿的客户端不断提交可能偶尔提交的数据来减少基础架构扩展需求。被需要。这也打开了目前客户端和服务器之间未设想的未来交互模型。
取消
所有流(包括请求/响应)都支持取消,以便有效清理服务器(响应者)资源。这意味着当客户取消或离开时,服务器有机会提前终止工作。这对于诸如流和订阅之类的交互模型是必不可少的,但是甚至对请求/响应有用,以允许有效采用诸如“备份请求”之类的方法来驯服尾部延迟(更多信息,可以参考:这里,这里,这里和这里)。
可恢复
对于长期流,特别是那些为移动客户端提供订阅的流,如果必须重新建立所有订阅,网络断开连接会显着影响成本和性能。当网络立即重新连接或在Wifi和蜂窝网络之间切换时,这尤其令人震惊。
RSocket支持会话恢复,允许简单的握手通过新的传输连接恢复客户端/服务器会话。
应用流程控制
RSocket支持两种形式的应用程序级流控制,以帮助保护客户端和服务器资源免受不堪重负:
Reactive Streams request(n) 异步拉取和租用。
此协议旨在用于数据中心,服务器到服务器用例以及Internet上的服务器到设备用例,例如移动设备或浏览器。
Reactive Streams request(n) Async Pull
第一种形式的流控制适用于服务器到服务器和服务器到设备的用例。它的灵感来自Reactive Streams Subscription.request(n)行为。RxJava,Reactor和Akka Streams是使用这种形式的“异步拉 - 推”流控制的实现示例。
RSocket允许request(n)信号通过网络边界组成,从请求者到响应者(通常是客户端到服务器)。这可以在应用程序级别使用Reactive Streams语义控制响应者到请求者的发射流,并允许使用有界缓冲区,因此流量调整适应应用程序消耗,并且不仅仅依赖于传输和网络缓冲。
这种相同的数据类型和方法已经在被采用到Java 9 的java.util.concurrent.Flow套件的类型。
租约
第二种形式的流控制主要关注数据中心中的服务器到服务器用例。启用后,响应者(通常是服务器)可以根据其对容量的了解向请求者发出租约,以便控制请求率。在请求方,这使应用程序级负载平衡能够仅向具有信号容量的响应者(服务器)发送消息。从服务器到客户端的此信号允许在具有机器群集的数据中心中实现更智能的路由和负载平衡算法。
多语言支持
上述许多动机可以通过利用现有协议,库和技术来实现。但是,这通常最终会与特定的实现紧密结合,这些实现必须在语言,平台和技术堆栈之间达成一致。
相反,将交互模型和流控制行为形式化为协议提供了不同语言的实现之间的契约。这反过来改善了比普遍存在的HTTP / 1.1请求/响应更广泛的行为集中的多语言交互,同时还支持跨语言的Reactive Streams应用程序级流控制(而不仅仅是Java,例如,最初定义了Reactive Streams) )。
传输层灵活性
正如HTTP请求/响应不是应用程序可以或应该通信的唯一方式一样,TCP不是唯一可用的传输层,并不是所有用例的最佳选择。因此,RSocket允许您根据环境,设备功能和性能需求交换底层传输层。RSocket(应用程序协议)以WebSockets,TCP和Aeron为目标,预计可用于任何具有TCP类特征的传输层,例如QUIC。
也许更重要的是,它使TCP,WebSockets和Aeron无需付出巨大努力即可使用。例如,使用WebSockets通常很有吸引力,但它所暴露的只是框架语义,因此使用它需要定义应用程序协议。这通常是压倒性的,需要付出很多努力。TCP甚至不提供框架。因此,大多数应用程序最终使用HTTP / 1.1并坚持请求/响应并错过了同步请求/响应之外的交互模型的好处。
出于这个原因,RSocket在这些网络传输上定义了应用层语义,以便在适当时选择它们。本文后面的内容是与在尝试利用WebSockets和Aeron之前探索的其他协议进行简要比较,然后才确定需要新的应用程序协议。
效率和性能
无效地使用网络资源的协议(重复握手和连接建立以及拆除开销,膨胀的消息格式等)可以极大地增加系统的感知等待时间。此外,如果没有流控制语义,当依赖服务减速时,单个写得不好的模块可能会超出系统的其余部分,从而可能导致重试风暴,从而给系统带来进一步的压力。Hystrix是一个尝试解决同步请求/响应问题的示例解决方案。然而,它的开销和复杂性都会带来成本。
此外,选择不当的通信协议会浪费服务器资源(CPU,内存和网络带宽)。虽然这对于较小的部署来说可能是可接受的,但是具有数百或数千个节点的大型系统将小的低效率倍增到显着的过量。由于服务器资源相对便宜但不是无限的,因此占用空间很大,扩展空间较小。即使使用好的工具,管理巨型集群也要昂贵得多,灵活性也差。而一个经常被遗忘的观点是,群集越大,操作越复杂,这成为可用性问题。
RSocket寻求:
- 通过支持来自任何语言的多个传输的流控制,支持非阻塞,双工,异步应用程序通信,从而减少感知延迟并提高系统效率。
- 通过以下方式减少硬件占用空间(以及成本和操作复杂性):
- 通过使用二进制编码提高CPU和内存效率
- 通过允许持久连接来避免冗余工作
- 通过以下方式减少感知用户延迟
- 避免握手和相关的往返网络开销
- 通过使用二进制编码减少计算时间
- 分配更少的内存并降低垃圾收集成本
比较
以下是我们在决定创建RSocket之前审查的一些协议的简要回顾。它并不试图详尽无遗或详尽。它也不寻求批评各种协议,因为它们都擅长于它们所构建的内容。本节仅用于表示现有协议未充分满足激励创建RSocket的要求。
对于上下文:
- RSocket是OSI第5/6层或TCP / IP“应用层”协议。
- 它旨在用于在行为上类似TCP的双工,二进制传输协议(在此进一步描述)。
TCP&QUIC
- 没有框架或应用程序语义
- 必须提供应用程序协议
WebSockets
- 没有应用程序语义,只是框架
- 必须提供应用程序协议
HTTP / 1.1和HTTP / 2
HTTP为构建应用程序协议提供了勉强足够的原始功能,但仍需要在其上定义应用程序协议。它不足以定义应用程序语义。(来自Google的gRPC是在HTTP / 2之上构建的协议的示例,用于添加这些类型的语义)。
这些有限的应用程序语义通常需要应用程序协议来定义诸如以下内容:
- 使用GET,POST或PUT进行请求
- 使用Normal,Chunked或SSE进行响应
- MimeType的有效载荷
- 错误消息传递以及标准状态代码
- 客户端应如何处理状态代码
- 使用SSE作为从服务器到客户端的持久通道,以允许服务器向客户端发出请求
从响应者(通常是服务器)到请求者(通常是客户端)的流控制没有定义的机制。HTTP / 2在字节级别进行流量控制,而不是应用程序级别。用于传递请求者(通常是服务器)可用性(例如,使请求失败)的机制是低效且痛苦的。它不支持诸如“即发即忘”之类的交互模型,并且流模型效率低下(分块编码或SSE,它是基于ASCII的)。
尽管它无处不在,但仅仅REST不足以定义应用程序语义。
那么HTTP / 2怎么样?它不解决HTTP / 1问题并解决RSocket的动机吗?
很不幸的是,不行。HTTP / 2 对于浏览器和请求/响应文档传输要好得多,但不会暴露应用程序的所需行为和交互模型,如本文档前面所述。
以下是HTTP / 2 规范和常见问题解答中的一些引用,这些引用对于提供HTTP / 2定位的上下文非常有用:
“HTTP的现有语义保持不变。”
“……从应用程序的角度来看,协议的功能基本没有改变……”
“这项工作被特许用于修改有线协议 - 即,如何将HTTP标头,方法等放到线路上,而不是改变HTTP的语义。”
此外,“推送承诺”专注于为标准Web浏览行为填充浏览器缓存:
“推送的响应始终与客户的明确请求相关联。”
这意味着我们仍然需要SSE或WebSockets(并且SSE是文本协议,因此需要Base64编码为UTF-8)才能进行推送。
HTTP / 2意味着更好的HTTP / 1.1,主要用于网站浏览器中的文档检索。对于应用程序,我们可以比HTTP / 2做得更好。
原文链接
http://rsocket.io/docs/Motivations
如果对大铁憨的文章感兴趣,也欢迎关注大铁憨的微信公众号,微信搜索”大铁憨”,或者微信扫描下面的二维码。
