与RSocket进行服务通信的反应式服务—简介

最初于2019年7月11日发布在https://blog.grapeup.com。


与RSocket进行服务通信的反应式服务—简介

本文是该微型系列文章的第一篇,它将帮助您熟悉RSocket —一种新的二进制协议,它可能会彻底改变机器对机器的通信。 在以下各段中,我们讨论分布式系统的问题,并说明如何使用RSocket解决这些问题。 我们专注于微服务之间的通信和RSocket的交互模型。

请注意,本文中提供的代码示例可在GitHub上找到→

分布式系统中的通信问题

微服务无处不在,几乎无处不在。我们经历了漫长的旅程,从糟糕的部署和维护单块应用程序到完全分布式的微型可扩展微服务。这样的架构设计有很多好处。但是,它也有缺点,值得一提。首先,为了向最终客户交付价值,服务必须交换大量数据。在整体应用程序中这不是问题,因为整个通信都在单个JVM中进行。在微服务架构中,服务部署在单独的容器中并通过内部或外部网络进行通信,因此网络是一等公民。如果您决定在云中运行应用程序,事情将变得更加复杂,在这种情况下,网络问题和延迟增加是您无法完全避免的事情。与其尝试解决网络问题,不如让您的体系结构具有弹性并且即使在动荡的时期中也能完全正常运行,这更好。

让我们更深入地研究微服务,数据,通信和云的概念。 作为示例,我们将讨论可通过网站和移动应用访问的企业级系统,以及与小型外部设备(例如家用加热器控制器)进行通信的系统。 该系统由多个微服务组成,大多数以Java编写,并且具有一些Python和node.js组件。 显然,所有这些文件都跨多个可用性区域进行复制,以确保整个系统高度可用。

为了与IaaS提供者无关,并改善开发人员体验,这些应用程序正在PaaS之上运行。 我们在这里有各种各样的可能性:Cloud Foundry,Kubernetes或结合在Cloudboostr中的两者都是合适的。 在服务之间的通信方面,设计很简单。 每个组件都公开普通的REST API-如下图所示。

与RSocket进行服务通信的反应式服务—简介

乍一看,这样的体系结构看起来还不错。 组件被分离并在云中运行-可能出什么问题? 实际上,存在两个主要问题-它们都与沟通有关。

第一个问题是HTTP的请求/响应交互模型。 尽管有很多用例,但它并不是为机器对机器的通信而设计的。 微服务在不关心操作结果的情况下发送一些数据到另一个组件是很常见的(触发并忘记),或者在数据可用时自动流传输数据(数据流)。 使用请求/响应交互模型很难以优雅,有效的方式实现这些通信模式。 即使执行简单的即发即弃操作也有副作用-服务器必须将响应发送回客户端,即使客户端对处理它不感兴趣。

第二个问题是性能。 假设我们的系统被客户广泛使用,流量增加,并且我们注意到我们正在努力处理每秒数百个请求。 借助容器和云,我们能够轻松扩展我们的服务。 但是,如果我们进一步跟踪资源消耗,则会注意到在内存不足的情况下,VM的CPU几乎处于空闲状态。 问题出在通常与HTTP 1.x一起使用的每个请求模型的线程中,其中每个单个请求都有自己的堆栈内存。 在这种情况下,我们可以利用反应性编程模型和非阻塞IO。 它将大大减少内存使用量,但是不会减少延迟。 HTTP 1.x是基于文本的协议,因此需要传输的数据大小比二进制协议要大得多。

在机器对机器的通信中,我们不应将自己局限于HTTP(尤其是1.x),其请求/响应交互模型以及性能低下。 那里(市场上)有更多更合适,更强大的解决方案。 基于RabbitMQ,gRPC甚至HTTP 2并支持多路复用和二进制化有效负载的消息传递,在性能和效率方面比纯HTTP 1.x更好。

与RSocket进行服务通信的反应式服务—简介

使用多种协议可以使我们在给定场景中以最有效和最合适的方式链接微服务。 但是,采用多种协议迫使我们一次又一次地重新发明轮子。 我们必须使用与安全性有关的额外信息来丰富我们的数据,并创建多个适配器来处理协议之间的转换。 在某些情况下,运输需要外部资源(经纪人,服务等),这些资源必须高度可用。 即使我们所需要的只是基于消息的简单"即发即弃"操作,额外的资源也会带来额外的成本。 此外,多种不同的协议可能会引入与应用程序管理相关的严重问题,尤其是如果我们的系统包含数百个微服务时。

上面提到的问题是发明RSocket的根本原因,也是它可能彻底改变云通信的根本原因。 通过其反应性和内置的强大交互模型,RSocket可以应用于各种业务场景,并最终统一我们在分布式系统中使用的通信模式。

RSocket解救

RSocket是一种新的,消息驱动的二进制协议,它标准化了云中的通信方法。 它有助于以一致的方式解决常见的应用程序问题,并支持多种语言(例如java,js,python)和传输层(TCP,WebSocket,Aeron)。 在以下各节中,我们将更深入地研究协议内部结构并讨论交互模型。

框架化和消息驱动

RSocket中的交互分为框架。 每个帧都包含一个帧头,其中包含流ID,帧类型定义和特定于该帧类型的其他数据。 帧头后跟元数据和有效负载,这些部分承载用户指定的数据。

与RSocket进行服务通信的反应式服务—简介

有多种类型的框架表示不同的动作和交互模型的可用方法。 我们不会涵盖所有这些内容,因为官方文档(
http://rsocket.io/docs/Protocol)中对此进行了广泛的描述。 但是,很少有值得注意的东西。 其中之一是客户端在通信开始时将其发送到服务器的设置框架。 可以自定义此框架,以便您可以添加自己的安全规则或连接初始化期间所需的其他信息。 应该注意的是,在建立连接阶段之后,RSocket不会区分客户端和服务器。 每一侧都可以开始将数据发送到另一侧-这使该协议几乎完全对称。

性能

帧作为字节流发送。 它使RSocket方式比典型的基于文本的协议更有效。 从开发人员的角度来看,当JSON在网络中来回飞行时,调试系统更容易,但是对性能的影响使这种便利性成为问题。 该协议没有强加任何特定的序列化/反序列化机制,它认为帧是一包可以转换为任何东西的位。 这样就可以使用JSON序列化或更有效的解决方案,例如Protobuf或AVRO。

影响RSocket性能的第二个因素是多路复用。 该协议在单个物理连接的顶部创建逻辑流(通道)。 每个流都有其唯一的ID,在某种程度上可以将其解释为我们从消息传递系统知道的队列。 这种设计解决了HTTP 1.x中已知的主要问题-每个请求模型的连接和"流水线"的性能较弱。 此外,RSocket本机支持大型有效负载的传输。 在这种情况下,有效载荷帧会被分割成带有额外标志(给定片段的序数)的几个帧。

反应性和流量控制

RSocket协议完全包含"反应式宣言"中所述的原则。 它在资源方面的异步特性和节俭功能有助于减少最终用户所经历的延迟以及基础架构的成本。 多亏了流式传输,我们不需要将数据从一项服务拉到另一项服务,而是在数据可用时将其推送。 这是一个非常强大的机制,但它也可能具有风险。 让我们考虑一个简单的场景:在我们的系统中,我们将事件从服务A传输到服务B。在接收方执行的操作很简单,需要一定的计算时间。 如果服务A推送事件的速度快于B处理事件的速度,则B最终将耗尽资源-发送方将终止接收方。 由于RSocket使用反应堆,因此它内置了对流控制的支持,这有助于避免这种情况。

我们可以轻松提供根据我们的需求调整的反压机制实施方案。 接收者可以指定要消耗多少数据,而不会收到更多数据,除非它通知发送者准备处理更多数据。 另一方面,为了限制从请求者传入的帧数,RSocket实现了租用机制。 响应者可以指定在定义的时间范围内请求者可以发送多少个请求。

API

如上一节所述,RSocket使用Reactor,因此在API级别上,我们主要在Mono和Flux对象上进行操作。 它也完全支持反应性信号-我们可以轻松地在不同事件上实现"反应"-onNext,onError,onClose等。

以下各段将介绍API和RSocket中可用的每个交互选项。 讨论将以代码片段和所有示例的描述为后盾。 在进入交互模型之前,值得介绍一下API基础,因为它将在多个代码示例中提出。

用RSocketFactory设置连接

在同级之间建立RSocket连接非常容易。 该API为工厂(RSocketFactory)提供了工厂方法,它们可以接收并连接以分别在客户端和服务器端创建RSocket和CloseableChannel实例。 在通信双方(请求者和响应者)中存在的第二共同财产是运输工具。 RSocket可以使用多种解决方案作为传输层(TCP,WebSocket,Aeron)。 无论选择哪种API,API都将提供工厂方法,使您可以调整和调整连接。

而且,对于响应者,我们必须创建一个套接字接受器实例。 SocketAcceptor是提供对等方之间合同的接口。 它具有单个方法accept,该方法接受RSocket发送请求并返回RSocket实例,该实例将用于处理来自对等方的请求。 除了提供合同外,SocketAcceptor还使我们能够访问设置框架的内容。 在API级别,它由ConnectionSetupPayload对象反映。

如上所示,在同级之间建立连接是相对容易的,特别是对于那些以前使用过WebSockets的人来说-就API而言,两种解决方案都非常相似。

互动模式

建立连接后,我们可以继续进行交互模型。 RSocket支持以下操作:

与RSocket进行服务通信的反应式服务—简介

随手可得,以及元数据推送,旨在将数据从发送方推送到接收方。 在这两种情况下,发送方都不关心操作的结果-它在API级别上以返回类型(Mono)反映出来。 这些动作之间的区别在于框架。 万一发生火灾而忘记了,将完整的帧发送到接收器,而对于元数据推送操作,该帧不具有有效负载-它仅由标头和元数据组成。 此类轻量级消息可用于将通知发送到IoT设备的移动或对等通信。

RSocket还能够模仿HTTP行为。 它支持请求-响应语义,这可能是您将要与RSocket一起使用的主要交互类型。 在流上下文中,此类操作可以表示为由单个对象组成的流。 在这种情况下,客户端正在等待响应帧,但是它以完全非阻塞的方式进行响应。

在云应用程序中,更有趣的是对数据流进行操作的请求流和请求通道交互,通常是无限的。 在请求流操作的情况下,请求者将单个帧发送给响应者并获取数据流。 这种交互方法使服务能够从提取数据策略切换到推送数据策略。 无需向响应者发送定期请求,请求者可以订阅流并对传入数据做出反应-当数据可用时,它将自动到达。

得益于多路复用和双向数据传输的支持,我们可以使用请求通道方法更进一步。 RSocket能够使用单个物理连接将数据从请求者流式传输到响应者,以及以另一种方式。 当请求者更新订阅时(例如,更改订阅标准),此类交互可能会很有用。 如果没有双向通道,客户端将不得不取消流并使用新参数重新请求它。

在API中,交互模型的所有操作都由下面显示的RSocket接口的方法表示。

为了改善开发人员的体验并避免实现RSocket接口的每个方法的必要性,API提供了我们可以扩展的抽象AbstractRSocket。 通过将SocketAcceptor和AbstractRSocket放在一起,我们可以获得服务器端的实现,在基本情况下,该实现可能看起来像这样:

在发送方,使用交互模型非常简单,我们需要做的就是在我们使用RSocketFactory创建的RSocket实例上调用特定方法,例如

有关RSocket交互模型中可用方法的更多示例,请访问GitHub→

发送方方面更有趣的是反压机制的实现。 让我们考虑以下请求方实施示例:

在此示例中,我们正在请求数据流,但是为了确保传入的帧不会杀死请求者,我们采用了反压机制。 为了实现此机制,我们使用request_n框架,该框架在API级别上由subscription.request(n)方法反映出来。 在订阅的开始[onSubscribe(Subscription s)],我们请求5个对象,然后我们在onNext(Payload有效负载)中计数接收到的项目。 当所有预期的帧都到达请求者时,我们正在请求接下来的5个对象-再次使用subscription.request(n)方法。 下图显示了该订户的流程:

与RSocket进行服务通信的反应式服务—简介

本节介绍的背压机制的实现非常基础。 在生产中,我们应基于更准确的指标(例如, 预测/平均计算时间。 毕竟,背压机制不会使响应者生产过剩的问题消失。 它将问题转移到响应方,可以更好地解决。 有关反压的更多信息,请参见Medium和GitHub。

摘要

在本文中,我们讨论了微服务体系结构中的通信问题,以及如何使用RSocket解决这些问题。 我们以简单的" hello world"示例和基本的反压机制实现为背景,介绍了其API和交互模型。

在本系列的下一篇文章中,我们将介绍RSocket的更多高级功能,包括LoadBalancing和Resumability,还将讨论RSocket上的抽象-RPC和Spring Reactor。

请注意,此处提供了完整的工作示例→

(本文翻译自Rafał Kowalski的文章《Reactive service to service communication with RSocket — Introduction》,参考:https://medium.com/@
b3rnoulli/reactive-service-to-service-communication-with-rsocket-introduction-5d64e5b6909)


分享到:


相關文章: