跳转到主要内容

热门内容

今日:


总体:


最近浏览:


Chinese, Simplified

大型分布式系统通常由不同的团队使用各种技术和编程语言以模块化的方式实现。这些片段需要可靠地通信,支持快速、独立的进化。在分布式系统中,模块间有效且可扩展的通信是一个关键问题。它会显著影响用户体验的延迟以及构建和运行系统所需的资源量。

在反应性声明中记录并在库中实现的体系结构模式(如反应性流和反应性扩展)有利于异步消息传递,并支持请求/响应以外的通信模式。这个“RSocket”协议是一个正式的通信协议,包含了“反应”原则。

以下是我们定义新协议的动机:

消息驱动

网络通信是异步的。RSocket协议包含这一点,并将所有通信建模为单个网络连接上的多路消息流,并且在等待响应时从不同步阻塞。

反应性宣言指出:

反应式系统依赖于异步消息传递来建立组件之间的边界,以确保松散耦合、隔离、位置透明,并提供将错误作为消息委托的方法。通过使用显式消息传递,可以对系统中的消息队列进行整形和监视,并在必要时应用背压,从而实现负载管理、弹性和流控制。…非阻塞通信只允许收件人在活动时消耗资源,从而减少系统开销。

此外, HTTP/2 FAQ 很好地解释了采用面向消息的协议的动机,这种协议的形式是通过持久连接进行多路复用:

HTTP/1.x有一个称为“行首阻塞”的问题,在这个问题上,一次只能有一个请求在连接上处于未完成状态。

HTTP/1.1试图用管道技术来解决这个问题,但它并没有完全解决这个问题(大的或慢的响应仍然会阻止后面的其他响应)。此外,由于许多中介和服务器不能正确处理管道,因此很难部署管道。

这迫使客户机使用许多试探法(通常是猜测)来确定何时将哪些请求放在与源连接的连接上;由于页面加载的可用连接数通常是可用连接数的10倍(或更多),这会严重影响性能,通常会导致阻塞请求的“瀑布式”增长。

  • 多路复用通过允许多个请求和响应消息同时运行来解决这些问题;甚至可以将一个消息的部分与另一个消息的部分混合在一起。

这反过来又允许客户机只使用每个源站的一个连接来加载页面。

常见问题解答将继续,讨论持久连接:

使用HTTP/1,浏览器在每个源站4到8个连接之间打开。由于许多网站使用多个来源,这可能意味着一个页面加载打开超过30个连接。

一个应用程序同时打开如此多的连接,打破了许多TCP构建的假设;因为每个连接都会在响应中引发大量数据,所以存在一个实际的风险,即中间网络中的缓冲区将溢出,从而导致拥塞事件和重传。

此外,使用如此多的连接不公平地垄断了网络资源,从其他性能更好的应用程序(如VoIP)中“窃取”它们。

 

交互模型

不适当的协议会增加开发系统的成本。它可以是一个不匹配的抽象,迫使系统的设计进入它允许的模型中。这迫使开发人员花费额外的时间来解决它的缺点,以处理错误并获得可接受的性能。在多语言环境中,由于不同的语言将使用不同的方法来解决这个问题,这需要团队之间的额外协调,所以这个问题被放大了。到目前为止,事实上的标准是HTTP,一切都是请求/响应。在某些情况下,这可能不是给定特性的理想通信模型。

推送通知就是这样一个例子。使用请求/响应强制应用程序进行轮询,在这种情况下,客户端一致地发送请求以检查服务器的数据。人们不需要花太多的时间去寻找应用程序的例子,这些应用程序每秒处理大量的请求,只是为了进行轮询,并且被告知它们没有任何用处。这对客户机、服务器和网络来说是浪费;而且会花费金钱;还会增加基础架构的规模、操作的复杂性,从而增加可用性。它通常也会增加用户接收通知的延迟,因为为了降低成本,轮询会缩减到更长的间隔。

出于这个和其他原因,RSocket不仅仅限于一个交互模型。下面描述的各种支持的交互模型为系统设计提供了强大的新可能性:

开火与忘记(Fire-and-Forget)

Fire and forget是请求/响应的优化,在不需要响应时非常有用。它允许显著的性能优化,不仅是通过跳过响应来节省网络使用,而且还允许在客户端和服务器处理时间内进行优化,因为不需要等待响应或取消请求并将其关联起来。

此交互模型对于支持丢失的用例(如非关键事件日志记录)非常有用。

用法可以这样认为:

Future<Void> completionSignalOfSend = socketClient.fireAndForget(message);

请求/响应(单个响应)

标准的请求/响应语义仍然受支持,并且仍然需要表示RSocket连接上的大多数请求。这些请求/响应交互可以被认为是优化的“只有一个响应的流”,并且是在单个连接上多路复用的异步消息。

使用者“等待”响应消息,因此它看起来像一个典型的请求/响应,但在它下面永远不会同步阻塞。

用法可以这样认为:

Future<Payload> response = socketClient.requestResponse(requestPayload);

请求/流(多响应,有限)

从请求/响应扩展为请求/流,它允许多条消息被流回。可以将其视为“collection”或“list”响应,但不是将所有数据作为单个响应返回,而是将每个元素按顺序返回。

用例可以包括如下内容:

  • 获取视频列表
  • 获取目录中的产品
  • 逐行检索文件

用法可以这样认为:

Publisher<Payload> response = socketClient.requestStream(requestPayload);

渠道

通道是双向的,在两个方向上都有消息流。

从这个交互模型中受益的一个用例示例是:

  • 客户端请求一个数据流,该数据流最初会破坏当前的世界视图
  • 增量/差异在发生更改时从服务器发送到客户端
  • 客户端会随着时间的推移更新订阅以添加/删除条件/主题/etc。

如果没有双向通道,客户机将不得不取消初始请求、重新请求并从头接收所有数据,而不仅仅是更新订阅并有效地获得差异。

用法可以这样认为:

Publisher<Payload> output = socketClient.requestChannel(Publisher<Payload> input);

 

行为

除了上面的交互模型之外,还有其他一些行为可以使应用程序和系统效率受益。

单响应与多响应

单响应和多响应之间的一个关键区别是RSocket堆栈如何向应用程序传递数据:一个响应可以跨多个帧进行,并且是一个更大的RSocket连接的一部分,该连接将多个消息多路传输。但是单个响应意味着应用程序只有在收到整个响应时才能获取其数据。而多响应则是零碎的。这可以让用户在设计服务时考虑到多个响应,然后客户机在收到第一个数据块时就可以开始处理数据。

双向的

RSocket支持双向请求,其中客户端和服务器都可以充当请求者或响应者。这允许客户端(如用户设备)充当服务器请求的响应器。

例如,服务器可以查询客户机以获取跟踪调试信息、状态等。这可以用于减少基础架构扩展需求,方法是允许服务器端在需要时进行查询,而不是让数百万/数十亿客户机不断提交可能只是偶尔需要的数据。这也为客户机和服务器之间的未来交互模型打开了大门。

取消

所有流(包括请求/响应)都支持取消以允许高效清理服务器(响应程序)资源。这意味着当客户机取消或离开时,服务器有机会提前终止工作。这对于交互模型(如流和订阅)是必不可少的,但对于请求/响应(request/response)来说,这甚至非常有用,可以有效地采用诸如“备份请求”之类的方法来驯服尾部延迟(这里、这里、这里和这里有更多信息)。

可恢复性

对于长寿命流,特别是那些服务于移动客户端订阅的流,如果必须重新建立所有订阅,则网络断开可能会显著影响成本和性能。当网络立即重新连接,或者在Wifi和蜂窝网络之间切换时,这种情况尤其严重。

RSocket支持会话恢复,允许简单的握手通过新的传输连接恢复客户机/服务器会话。

应用程序流控制

RSocket支持两种形式的应用程序级流控制,以帮助保护客户端和服务器资源不被淹没:“反应流”请求(n)异步拉取和租用。

此协议设计用于数据中心、服务器到服务器用例以及internet上的服务器到设备用例(如移动设备或浏览器)。

“反应流”请求(n)异步拉取

这种流控制的第一种形式适用于服务器到服务器和服务器到设备的用例。它的灵感来自于反应流订阅请求(n) 行为。RxJava、Reactor和Akka流是使用这种形式的“异步拉-推”流控制实现的示例。

RSocket允许在从请求者到响应者(通常是从客户端到服务器)的网络边界上组合请求(n)信号。这将使用应用程序级别的反应流语义控制从响应者到请求者的发射流,并启用有界缓冲区的使用,以便根据应用程序的消耗调整流速率,而不是仅依赖传输和网络缓冲。

在并发java.util.concurrent。类型的流套件。

租赁

第二种形式的流控制主要关注数据中心中的服务器到服务器用例。启用后,响应程序(通常是服务器)可以根据其容量知识向请求程序发出租约,以控制请求速率。在请求方方面,这将启用应用程序级负载平衡,以便仅向具有信号容量的响应方(服务器)发送消息。从服务器到客户机的这一信号允许在具有机群的数据中心中使用更智能的路由和负载平衡算法。

 

多语言支持

以上许多动机都可以通过利用现有的协议、库和技术来实现。然而,这往往与特定的实现紧密耦合,而这些实现必须在语言、平台和技术堆栈之间达成一致。

相反,将交互模型和流控制行为形式化为协议提供了不同语言实现之间的契约。这反过来又在更广泛的行为集中改进了多线程交互,而不是无处不在的HTTP/1.1请求/响应,同时还支持跨语言的反应流应用程序级流控制(而不仅仅是在Java中,例如,反应流最初是在Java中定义的)。

传输层灵活性

正如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进行响应
  • 有效载荷类型
  • 错误消息和标准状态代码
  • 客户机应如何处理状态代码
  • 使用SSE作为从服务器到客户端的持久通道,以允许服务器向客户端发出请求

从响应程序(通常是服务器)到请求程序(通常是客户端)的流控制没有定义的机制。HTTP/2在字节级而不是应用程序级进行流控制。通信请求者(通常是服务器)可用性(例如请求失败)的机制效率低下且痛苦。它不支持诸如fire和forget之类的交互模型,流模型效率低下(基于ASCII的分块编码或SSE)。

尽管REST无处不在,但它本身并不足以定义应用程序语义,也不适合定义应用程序语义。

但是HTTP/2呢?它是否解决了HTTP/1问题并解决了RSocket的动机?

不幸的是,no.HTTP/2对于浏览器和请求/响应文档传输来说要好得多,但是并没有像本文前面描述的那样为应用程序公开所需的行为和交互模型。

以下是HTTP/2规范和常见问题解答中的一些引述,它们有助于提供HTTP/2的目标上下文:

“HTTP的现有语义保持不变。”

“…从应用程序的角度来看,协议的特性基本上没有改变…”

“这项工作是为了修改wire协议 - ,即如何将HTTP头、方法等‘放到wire上’,而不是更改HTTP的语义。”

此外,“推送承诺”侧重于为标准的web浏览行为填充浏览器缓存:

“推送响应始终与客户端的显式请求相关联。”

这意味着我们仍然需要SSE或WebSockets(SSE是一种文本协议,因此需要将Base64编码转换为UTF-8)来进行推送。

HTTP/2是一个更好的HTTP/1.1,主要用于网站浏览器中的文档检索。对于应用程序,我们可以比HTTP/2做得更好。

 

原文:http://rsocket.io/docs/Motivations

本文:

讨论:请加入知识星球或者微信圈子【首席架构师圈】

最后修改
星期四, 一月 5, 2023 - 21:56
Tags
 
Article