微服务架构中的进程间通信

介绍

在单体应用程序中,组件通过语言级的方法或函数调用进行彼此的调用。相比之下,基于微服务的应用程序是在多台机器上运行的分布式系统。每个服务实例通常是一个进程。因此,如下图所示,服务必须使用进程间通信(IPC)机制进行交互。

稍后我们将看一下特定的IPC技术,但首先要探讨各种设计问题。

交互风格

当为服务选择IPC机制时,首先要考虑服务如何交互。有各种客户端服务交互方式。它们可以分为两个维度。第一个维度是互动是一对一还是一对多:

一对一 - 每个客户端请求仅由一个服务实例处理。

一对多 - 每个请求由多个服务实例处理。

第二个维度是交互是同步还是异步:

同步 - 客户端期望及时响应服务,甚至可能在等待时阻塞。

异步 - 客户端在等待响应时不会阻塞,并且响应(如果有)不一定立即发送。

下表显示了各种交互方式。

一对一

一对多

同步

请求/响应

异步

通知

发布/订阅

请求/异步响应

发布/异步响应

以下是一对一的交互:

请求/响应 - 客户端向服务器发出请求并等待响应。客户期望响应及时到达。在基于线程的应用程序中,请求的线程甚至可能在等待时阻塞。

通知(a.k.a.单向请求) - 客户端向服务器发送请求,但不预期或发送回复。

请求/异步响应 - 客户端向服务发送请求,服务异步回复。客户端在等待时不阻塞,并被设计为假设响应可能不会在一段时间内到达。

以下是一对多的互动:

发布/订阅 - 客户端发布通知消息,由零个或多个感兴趣的服务消费。

发布/异步响应 - 客户端发布请求消息,然后等待一定时间的来自感兴趣的服务的响应。

每个服务通常使用这些交互样式的组合。对于一些服务,单一的IPC机制就足够了。其他服务可能需要使用IPC机制的组合。下图显示了当用户请求旅行时,出租车应用程序中的服务可能会相互作用。

服务使用通知,请求/响应和发布/订阅的组合。例如,乘客的智能手机向旅行管理服务器发送通知请求提款。旅行管理服务通过使用请求/响应来调用乘客服务来验证乘客的帐户是否活动。旅行管理服务然后创建旅程,并使用发布/订阅通知其他服务,包括调度程序,它定位可用的司机。

现在我们来看看交互风格,我们来看看如何定义API。

定义API

服务的API是服务和客户之间的合同。无论您选择IPC机制,重要的是使用某种接口定义语言(IDL)精确定义服务的API。使用AP优先的方法来定义服务还是有很好的参考。您通过编写界面定义并与客户端开发人员进行审阅,开始开发服务。它仅仅发生在您实现该服务的API定义的迭代之后。这样的设计可以提高您构建符合其客户需求的服务的机会。

如本文后面将会看到的,API定义的性质取决于您使用的是哪种IPC机制。如果您正在使用消息传递,则API由消息通道和消息类型组成。如果您使用HTTP,则API由URL和请求和响应格式组成。稍后我们将更详细地描述一些IDL。

API的演化

服务的API总是随着时间而变化。在一个单体应用程序中,通常可以直接更改API并更新所有的调用者。在基于微服务的应用程序中,这将会更加困难,即使您的API的所有消费者都是同一应用程序中的其他服务。您通常无法强制所有客户端与锁定的服务同时升级。此外,您可能会逐步部署新版本的服务,以便新旧版本的服务同时运行。制定处理这些问题的策略很重要。

处理API更改的方式取决于更改的大小。一些更改是次要的和向后兼容的。例如,您可能会向请求或响应添加属性。设计客户端和服务是有意义的,以便遵守鲁棒性原则。使用旧API的客户端应继续使用新版本的服务。该服务为缺少的请求属性提供默认值,客户端忽略任何额外的响应属性。使用IPC机制和消息传递格式,使您能够轻松地发展您的API很重要。

但是有时候,您必须对API进行主要的、不兼容的更改。由于您无法强制客户端立即升级,服务必须支持较旧版本的API一段时间。如果您使用基于HTTP的机制(如REST),则一种方法是将版本号嵌入到URL中。每个服务实例可能同时处理多个版本。或者,您可以部署每个处理特定版本的不同实例。

处理部分失效

如前面关于API网关的文章所述,在分布式系统中存在部分故障的风险。由于客户端和服务是单独的进程,服务可能无法及时响应客户端的请求。由于故障或维护,服务可能会关闭。或者服务可能过载,响应速度要求非常慢。

例如,请考虑该文章的“产品详细信息”方案。我们假设推荐服务没有反应。客户端的幼稚实现可能会无限期地阻塞并等待响应。不仅会导致用户体验不佳,而且在许多应用中,它会消耗一些宝贵的资源,例如线程。最终,运行时间将用完线程并变得无响应,如下图所示。

为了防止这个问题,您必须设计您的服务能够处理部分故障。

Netflix描述了一个可遵循的好办法。处理部分失效的策略包括:

网络超时 - 不要无限期地阻止,等待响应时总是使用超时。使用超时确保资源永远无法被束缚。

限制未完成请求的数量 - 对客户端可以设置使用特定服务的未完成请求数量的上限。如果达到极限,则可能无法处理额外的请求,这些请求的尝试需要立即失败。

断路器模式 - 跟踪成功和失败请求的数量。如果错误率超过配置的阈值,请断开断路器,以便让进一步的尝试立即失败。如果大量请求失败,则表明服务不可用,并且发送请求是无意义的。超时时间后,客户端应重新尝试,如果成功,请关闭断路器。

提供备用 - 在请求失败时执行后备逻辑。例如,返回缓存数据或默认值,例如空的一组建议。

Netflix Hystrix是一个实现这些和其他模式的开源库。如果您使用的是JVM,那么您一定要考虑使用Hystrix。而且,如果您在非JVM环境中运行,则应使用等效的库。

IPC技术

有很多不同的IPC技术可供选择。服务可以使用基于HTTP的REST或Thrift的基于同步请求/响应的通信机制。或者,他们可以使用异步的、基于消息的通信机制,如AMQP或STOMP。还有各种不同的消息格式。服务可以使用人类可读的、基于文本的格式,如JSON或XML。或者,它们可以使用诸如Avro或协议缓冲器的二进制格式(更有效)。稍后我们将看一下同步的IPC机制,但是首先来讨论异步IPC机制。

基于消息的异步通信

当使用消息传递时,进程通过异步交换消息进行通信。客户端通过发送消息向服务发出请求。如果服务达到预期响应,则通过向客户端发送单独的消息来实现。由于通信是异步的,客户端不会阻塞来等待回复。相反,客户端是假定响应不会被立即收到。

消息由标题(诸如发件人之类的元数据)和消息体组成。消息通过信道进行交换。任何数量的生产者都可以向通道发送消息。类似地,任何数量的消费者都可以从频道接收消息。有两种渠道,点对点和发布订阅。一个点对点的频道向正在读取频道的消费者提供一个消息。服务使用点对点通道,用于前面描述的一对一交互风格。发布订阅频道将每条消息传递给所有附加的消费者。服务使用发布订阅渠道进行上述的一对多的交互风格。

下图显示了出租车应用程序如何使用发布订阅频道。

旅行管理服务通过向发布订阅频道写入旅行创建的消息来通知有关新旅程的调度员等有兴趣的服务。调度员找到可用的司机并通过向发布订阅通道写入司机提出的消息来通知其他服务。

有很多信息系统可供选择。你应该选择一个支持各种编程语言的。一些消息系统支持标准协议,如AMQP和STOMP。其他消息系统具有专有但已记录的协议。有大量的开源消息系统可供选择,包括RabbitMQ,Apache Kafka,Apache ActiveMQ和NSQ。在高层次上,他们都支持某种形式的信息和渠道。他们都努力做到可靠、高性能、可扩展。然而,每个Broker的消息传递模式的细节存在显着的差异。

使用消息传递有很多优点:

将客户端与服务分离 - 客户端只需通过向相应的渠道发送消息即可提出请求。客户端完全不了解服务实例。它不需要使用发现机制来确定服务实例的位置。

消息缓冲 - 使用诸如HTTP的同步请求/响应协议,客户端和服务在交换期间必须可用。相比之下,消息代理器将写入通道的消息排队,直到消费者处理它们。这意味着,例如,即使订单执行系统缓慢或不可用,在线商店也可以接受客户的订单。订单消息进行简单地排队。

灵活的客户端 - 服务交互 - 消息传递支持前面描述的所有交互方式。

显式进程间通信 - 基于RPC的机制尝试使调用远程服务看起来与调用本地服务相同。然而,由于物理学规律和部分失败的可能性,实际上是完全不同的。消息传递使得这些差异非常明确,因此开发人员不会被视为虚假的安全感。

然而,使用消息传递有一些缺点:

额外的操作复杂性 - 消息传递系统是必须安装、配置和操作的另一个系统组件。消息代理程序必须高度可用,否则系统可靠性受到影响。

实现基于请求/响应的交互的复杂性 - 请求/响应风格的交互需要一些实现的工作。每个请求消息必须包含响应通道和想着的标识符。该服务将包含相关ID的响应消息写入响应通道。客户端使用相关ID将响应与请求相匹配。通常更容易使用直接支持请求/响应的IPC机制。

现在我们已经看过使用基于消息传递的IPC,我们来看看基于请求/响应的IPC。

基于请求/响应的同步IPC

当使用基于同步的、基于请求/响应的IPC机制时,客户端向服务器发送请求。该服务处理该请求并发回一个响应。在许多客户端中,使请求的线程在等待响应时阻塞。其他客户端可能会使用异步,事件驱动的客户端代码,这可能是由Futures或Rx Observables封装的。然而,与使用消息传递不同,客户端假定响应将及时到达。有许多协议可供选择。两种流行协议是REST和Thrift。我们先来看一下REST。

REST

今天开发REST风格的API是时尚的。 REST是一种(几乎总是使用HTTP)的IPC机制。 REST中的一个关键概念是资源,通常表示业务对象(如客户或产品)或业务对象的集合。 REST使用HTTP动词来操纵资源,这是使用URL引用的。例如,GET请求返回资源的表示形式,可能是XML文档或JSON对象的形式。 POST请求创建一个新资源,PUT请求更新一个资源。引用REST的创建者Roy Fielding:

“REST提供了一组架构约束,当整体应用时,强调组件交互的可扩展性,接口的通用性,组件的独立部署以及中间组件,以减少交互延迟,实施安全性和封装传统系统。"

下图显示了出租车应用程序可能使用REST的方式之一。

乘客的智能手机通过向旅行管理服务的/ tripps资源发出POST请求来请求旅行。该服务通过向乘客管理服务发送关于乘客的信息的GET请求来处理该请求。在验证乘客被授权创建旅行后,旅行管理服务将创建旅程,并向智能手机返回201响应。

许多开发人员声称其基于HTTP的API是RESTful的。但是,并不是所有的都是这样。 Leonard Richardson(无关系)为REST定义了一个非常有用的成熟度模型,包括以下级别。

级别0 - 0级API的客户端通过向其唯一的URL端点发送HTTP POST请求来调用该服务。每个请求指定要执行的操作,操作的目标(例如业务对象)以及任何参数。

1级 - 1级API支持资源的想法。要对资源执行操作,客户端会创建一个POST请求,指定要执行的操作和任何参数。

级别2 - 级别2 API使用HTTP动词来执行动作:GET检索,POST创建和PUT进行更新。请求查询参数和主体(如果有)指定操作的参数。这使服务能够利用Web基础设施,如缓存GET请求。

3级 - 3级API的设计是基于非常有名的HATEOAS(超文本作为应用状态引擎)原理。基本思想是GET请求返回的资源的表示包含用于执行该资源上允许的操作的链接。例如,客户端可以使用响应于发送的GET请求返回的订单表示中的链接来取消订单以检索订单。 HATEOAS的优点不再需要将网址硬编码到客户端代码中。另一个好处是,由于资源的表示包含可允许操作的链接,所以客户端不必猜测可以对当前状态的资源执行什么操作。

使用基于HTTP的协议有很多好处:

HTTP简单而熟悉。

您可以使用扩展名(如Postman)从浏览器中测试HTTP API,或使用curl从命令行测试HTTP API(假设使用了JSON或其他一些文本格式)。

它直接支持请求/响应式通信。

HTTP当然是防火墙友好的。

它不需要中间代理,这简化了系统的体系结构。

使用HTTP有一些缺点:

它只直接支持交互的请求/响应风格。您可以使用HTTP进行通知,但服务器必须始终发送HTTP响应。

因为客户端和服务直接通信(没有中介缓冲区消息),所以在交换期间都必须运行它们。 客户端必须知道每个服务实例的位置(即URL)。如前面关于API网关的文章所述,这在现代应用程序中是一个非常重要的问题。

客户端必须使用服务发现机制来定位服务实例。

开发者社区最近重新发现了RESTful API的接口定义语言的价值。有几个选项,包括RAML和Swagger。一些IDL(如Swagger)允许您定义请求和响应消息的格式。其他如RAML要求您使用单独的规范,如JSON模式。除了描述API之外,IDL通常还具有从接口定义生成客户端存根和服务器骨架的工具。

Thrift

Apache Thrift是REST的有意思的替代方案。它是编写跨语言RPC客户端和服务器的框架。 Thrift提供了一个C风格的IDL来定义您的API。您可以使用Thrift编译器来生成客户端存根和服务器端骨架。编译器生成各种语言的代码,包括C ++,Java,Python,PHP,Ruby,Erlang和Node.js.

Thrift接口由一个或多个服务组成。服务定义类似于Java接口。它是强类型方法的集合。 Thrift方法可以返回(可能为空)值,也可以定义为单向。返回值的方法实现了交互的请求/响应风格。客户端等待响应,并可能会抛出异常。单向方法对应于交互的通知风格。服务器不发送响应。

Thrift支持各种消息格式:JSON,二进制和紧凑的二进制。二进制比JSON更有效率,因为解码速度更快。而且,顾名思义,紧凑的二进制是一种节省空间的格式。当然,JSON是人性化和浏览器友好的。 Thrift还为您提供了包括原始TCP和HTTP在内的传输协议选择。原始TCP可能比HTTP更有效率。然而,HTTP是防火墙,浏览器和人性化的。

消息格式

现在我们来看HTTP和Thrift,我们来看看消息格式的问题。如果您使用的是消息系统或REST,则可以选择您的消息格式。其他IPC机制,如Thrift可能只支持少量的消息格式,也许只有一个。在这两种情况下,使用跨语言消息格式很重要。即使您现在以单一语言编写您的微型服务,您将来也可能会使用其他语言。

有两种主要的消息格式:文本和二进制。基于文本格式的示例包括JSON和XML。这些格式的优点在于,它们不仅具有人类可读性,而且是自我描述的。在JSON中,对象的属性由名称 - 值对的集合表示。类似地,在XML中,属性由命名元素和值表示。这使得消息的消费者能够挑选其感兴趣的值并忽略其余的值。因此,消息格式的微小变化可以轻松地向后兼容。

XML文档的结构由XML模式指定。随着时间的推移,开发者社区已经意识到JSON还需要一个类似的机制。一个选择是使用JSON Schema,独立或作为IDL的一部分,如Swagger。

使用基于文本的消息格式的缺点是消息往往是冗长的,特别是XML。因为消息是自描述的,每个消息除了它们的值之外还包含属性的名称。另一个缺点是解析文本的开销。因此,您可能需要考虑使用二进制格式。

有几种二进制格式可供选择。如果您使用Thrift RPC,您可以使用二进制Thrift。如果您选择消息格式,流行的选项包括协议缓冲区和Apache Avro。这两种格式都提供了一种用于定义消息结构的类型IDL。然而,一个区别是协议缓冲区使用标记字段,而Avro消费者需要知道模式才能解释消息。因此,协议缓冲区的API进化比使用Avro更容易。

总结

微服务器必须使用进程间通信机制进行通信。在设计您的服务如何通信时,您需要考虑各种问题:服务如何交互,如何为每个服务指定API,如何发展API以及如何处理部分故障。微服务器可以使用两种IPC机制,异步消息传递和同步请求/响应。

原文发布于微信公众号 - IT技术精选文摘(ITHK01)

原文发表时间:2017-09-07

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏子勰随笔

Android中关于Nativa编译(NDK、JNI)的一些问题

1748
来自专栏BeJavaGod

阿里巴巴Druid数据源,史上最强的数据源,没有之一

目前常用的数据源主要有c3p0、dbcp、proxool、druid,先来说说他们 Spring 推荐使用dbcp; Hibernate 推荐使用c3p0和pr...

3819
来自专栏mini188

学习笔记:Maven构造版本号的方法解决浏览器缓存问题

需要解决的问题   在做WEB系统开发时,为了提高性能会利用浏览器的缓存功能,其实即使不显式的申明缓存,现代的浏览器都会对静态文件(js、css、图片之类)缓存...

2359
来自专栏企鹅号快讯

系统方案合集-系统备份方案

导读 | 精选 一、方案特点 此方案是基于批处理脚本和任务计划技术,针对系统特有文件结构和数据库结构的特点,而形成的系统备份方案。该方案特点: 1.易用性好,通...

3357
来自专栏翻译

在Ubuntu 14.04/14.10上搭建Minecraft Spigot服务器

本文介绍了如何在Ubuntu 14.04 / 14.10上搭建自己的Minecraft服务器,搭建一个让我们可以与朋友远程开黑的私服,甚至是搭建一个几百人的公共...

1912
来自专栏从流域到海域

在微服务之间进行通信

原文地址:https://dzone.com/articles/communicating-between-microservices

4065
来自专栏腾讯云serverless的专栏

使用 SCF 无服务器云函数定时拨测站点并邮件告警

利用无服务器架构中提供的定时触发能力,在运维监控场景有很多种用处,例如定时备份、定时拨测、定时统计等。

9.5K2
来自专栏安恒信息

揭密HTML 5带来的攻击手法

HTML5 是下一代的HTML,HTML5赋予网页更好的意义和结构。更加丰富的标签将随着对RDFa的,微数据与微格式等方面的支持,构建对程序、对用户...

3595
来自专栏Android开发实战

Android P新增检测项 应用热修复受重大影响

最近关于热修复崩溃在Android P 版本的内容持续增高,也许这个commit可以帮到你.

4953
来自专栏AI星球

Java Web 从入门到"改行"(1)--基础准备

首先,说明一下,这是一篇关于 Java Web 基础入门的文章,上学期开始学习 Java Web ,看了不少有关 「XXX从入门到精通」的书籍,近期,要帮着导师...

962

扫码关注云+社区