NGINX微服务(三)进程间通信

作者 : 开心源码 本文共8209个字,预计阅读时间需要21分钟 发布时间: 2022-05-11 共73人阅读

https://github.com/oopsguy/microservices-from-design-to-deployment-chinese

译者:Oopsguy

本书的第三章主要是关于用微服务架构构建应使用程序。第一章详情了微服务架构模式,将其与单体架构模式进行比照,并探讨了用微服务的优点和缺点。第二章形容了应使用程序用户端通过扮演中间人角色的 API 网关与微服务器进行通信。在本章中,我们来理解一下系统中的服务是如何相互通信的。第四章将详细讨论服务发现方面的内容。

3.1、简介

在单体应使用程序中,组件可通过语言级方法或者者函数相互调使用。相比之下,基于微服务的应使用程序是一个运行在多台机器上的分布式系统。通常,每个服务实例是一个进程。

因而,如图 3-1 所示,服务必需用进程间通信(IPC)机制进行交互。

稍后我们将理解到多种 IPC 技术,但在此之前,我们先来讨论一下涉及到的各种设计问题。

NGINX微服务(三)进程间通信

用进程间通信交互的微服务

3.2、交互方式

当为服务选择一种 IPC 机制时,首先需要考虑服务如何交互。有很多种用户端-服务交互方式。它们能分为两个类。第一类是一对一交互与一对多交互:

  • 一对一 – 每个用户端请求都由一个服务实例解决。

  • 一对多 – 每个请求由多个服务实例解决。

第二类是同步交互与异步交互:

  • 同步 – 用户端要求服务及时响应,在等待过程中可可以会发生阻塞。

  • 异步 – 用户端在等待响应时不会发生阻塞,但响应(假如有)不肯定立即返回。

下表展现了各种交互方式。

一对一一对多
同步请求/响应
异步通知发布/订阅
异步请求/异步响应发布/异步响应

表 3-1、进程间通信方式

一对一交互分为以下列举的类型,包括同步(请求/响应)和异步(通知与请求/异步响应):

  • 请求/响应 – 用户端向服务发出请求并等待响应。用户端要求响应及时到达。在基于线程的应使用程序中,发出请求的线程可可以在等待时发生阻塞。

  • 通知(又名单向请求) – 用户端向服务发送请求,但不要求响应。

  • 请求/异步响应 – 用户端向服务发送请求,服务异步响应。用户端在等待时不发生阻止,适使用于假设响应可可以不会立即到达的场景。

一对多交互可分为以下列举的类型,它们都是异步的:

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

  • 发布/异步响应 – 用户端发布请求消息,而后等待肯定时间来接收消费者的响应。

通常,每个服务都组合着用这些交互方式。对于少量服务,单一的 IPC 机制就足够了,但其余服务可可以需要组合多个 IPC 机制。

图 3-2 显示了当使用户请求打车时,打车应使用中的服务可可以会发生交互。

NGINX微服务(三)进程间通信

用了多种 IPC 机制的服务交互

服务用了通知、请求/响应和发布/订阅组合。例如,乘客的智可以手机向 Trip Management 微服务发送一条通知以请求一辆车。Trip Management 服务通过用请求/响应来调使用 Passenger Management 服务以验证乘客的帐户能否可使用。之后,Trip Management 服务创立路线,并用发布/订阅通知其余服务,包括使用于定位可使用司机的 Dispatcher。

现在我们来看一下交互方式,我们先来看看如何定义 API。

3.3、定义 API

服务 API 是服务与用户端之间的契约。无论您选择何种 IPC 机制,用接口定义语言(interface definition language,IDL)严格定义服务 API 都是非常有必要的。有论据证实用 API 优先(API?first)法定义服务更加合适。在对您需要实现的服务的 API 定义进行迭代之后,您能通过编写接口定义并与用户端开发人员进行审阅来开始开发服务。这样设计能添加您构建出符合用户端需求的服务的机率。

正如您将会在后面看到,定义 API 的方式取决于您用何种 IPC 机制。假如您正在用消息传递,则 API 由消息通道和消息类型组成。假如您用的是 HTTP,则 API 由 URL、请求和响应格式组成。稍后我们将详细地详情关于 IDL 方面的内容。

3.4、演化 API

服务 API 总是随着时间而变化。在单体应使用程序中,更改 API 和升级所有调使用者通常是一件直截了当的事。但在基于微服务的应使用程序中,即便您的 API 的所有消费者都是同一应使用程序中的其余服务,要想完成这些工作也是非常困难的。通常,您无法强制所有用户端与服务更新的节奏一致。此外,您可可以需要逐渐部署新版本服务,以便新旧版本的服务同时运行。制定这些问题的解决策略还是很重要的。

解决 API 变更的方式取决于变更的程度。某些更改是次要或者需要向后兼容以前的版本。例如,您可可以会向请求或者响应增加属性。这时设计用户与服务时遵守鲁棒性准则就显得很有意义了。用较旧 API 的用户端应继续用新版本的服务。该服务为缺少的请求属性提供默认值,并且用户端忽略任何多余的响应属性。用 IPC 机制和消息格式非常重要,能让您轻松地演化 API。

但有时候,您必需对 API 作出大量不兼容的更改。因为您无法强制用户端立即更新,服务也必需支持较旧版本的 API 一段时间。假如您用了基于 HTTP 的机制(如 REST),则一种方法是将版本号嵌入 URL 中。每个服务实例可可以同时解决多个版本。或者者,您能部署多个不同的实例,每个实例使用于解决特定版本。

3.5、解决局部故障

正如第二章中关于 API 网关所述,在分布式系统中存在局部故障风险。因为用户端进程与服务进程是分开的,服务可可以无法及时响应用户端的请求。因为故障或者者维护,服务可可以需要关闭。也有可可以因服务过载,造成响应速度变得极慢。

例如,请回想第二章中的产品详细信息场景。我们假设 Recommendation Service 没有响应。用户端天真般的实现可可以会无限期地阻塞以等待响应。这不仅会导致使用户体验糟糕,而且在许多应使用程序中,它将耗费如线程之类等宝贵资源。以致最终,在运行时将线程使用完,造成无法响应,如图 3-3 所示。

NGINX微服务(三)进程间通信

因无响应服务引起的线程阻塞

为了防止这个问题出现,您必需设计您的服务以解决局部故障。以下是一个由 Netflix 给出的好方法。解决局部故障的策略包括:

  • 网络超时 – 在等待响应时,不要无限期地阻塞,始终用超时方案。用超时方案确保资源不被无限地耗费。

  • 限制未完成的请求数量 – 对用户端拥有特定服务的未完成请求的数量设置上限。假如达到了上限,发出的额外请求可可以是毫无意义的,因而这些尝试需要立即失败。

  • 断路器模式 – 追踪成功和失败请求的数量。假如错误率超过配置阈值,则断开断路器,以便后续的尝试可以立即失败。假如出现大量请求失败,则表明服务不可使用,发送请求将是无意义的。发生超时后,用户端应重新尝试,假如成功,则关闭断路器。

  • 提供回退 – 请求失败时执行回退逻辑。例如,返回缓存数据或者者默认值,如一组空白的推荐数据。

Netflix Hystrix 是一个实现上述和其余模式的开源库。假如您正在用 JVM,那么您肯定要考虑用 Hystrix。假如您在非 JVM 环境中运行,则应用相等作使用的库。

3.6、IPC 技术

有多种 IPC 技术可供选择。服务能用基于同步请求/响应的通信机制,比方基于 HTTP 的 REST 或者 Thrift。或者者,能用异步、基于消息的通信机制,如 AMQP 或者 STOMP。

还有各种不同的消息格式。服务能用人类可读的、基于文本的格式,如 JSON 或者 XML。或者者,能用如 Avro 或者 Protocol Buffers 等二进制格式(更加高效)。稍后我们将探讨同步 IPC 机制,但在此之前让我们先来探讨一下异步 IPC 机制。

3.7、异步、基于消息的通信

当用消息传递时,进程通过异步交换消息进行通信。用户端通过发送消息向服务发出请求。假如服务需要回复,则通过向用户端发送一条单独的消息来实现。因为通信是异步的,因而用户端不会阻塞等待回复。相反,用户端被假定不会立即收到回复。

一条消息由头部(如发件人之类的元数据)和消息体组成。消息通过通道进行交换。任何数量的生产者都能向通道发送消息。相似地,任何数量的消费者都能从通道接收消息。有两种通道类型,分别是点对点(point?to?point)与发布订阅(publish?subscribe):

  • 点对点通道发送一条消息给一个切确的、正在从通道读取消息的消费者。服务用点对点通道,就是上述的一对一交互方式。

  • 发布订阅通道将每条消息传递给所有订阅的消费者。服务用发布订阅通道,就是上述的一对多交互方式。

图 3-4 展现了打车应使用程序如何用发布订阅通道。

NGINX微服务(三)进程间通信

用了发布-订阅通道的打车应使用

Trip Management 服务通过向发布订阅通道写入 Trip Created 消息来通知已订阅的服务,如 Dispatcher。 Dispatcher 找到可使用的司机并通过向发布订阅通道写入 Driver Proposed 消息来通知其余服务。

有许多消息系统可供选择。您应该选择一个支持多种编程语言的。

少量消息系统支持标准协议,如 AMQP 和 STOMP。其余消息系统有专有的文档化协议。

有大量的开源消息系统可供选择,包括 RabbitMQ、Apache Kafka、Apache ActiveMQ 和 NSQ。在高层上,他们都支持某种形式的消息和通道。他们都力求做到可靠、高性可以和可扩展。然而,每个代理商的消息传递模型细节上都存在着很大差异。

用消息传递有很多优点:

  • 将用户端与服务分离 – 用户端通过向相应的通道发送一条消息来简单地发出一个请求。服务实例对用户端而言是透明的。用户端不需要用发现机制来确定服务实例的位置。

  • 消息缓冲 – 用如 HTTP 的同步请求/响应协议,用户端和服务在交换期间必需可使用。相比之下,消息代理商会将消息写入通道入队,直到消费者解决它们。这意味着,例如,即便订单执行系统出现缓慢或者不可使用的情况,在线商店还是能接受用户的订单。订单消息只要要简单地排队。

  • 灵活的用户端-服务交互 – 消息传递支持前面提到的所有交互方式。

  • 毫无隐瞒的进程间通信 – 基于 RPC 的机制试图使调使用远程服务看起来与调使用本地服务相同。然而,因为物理因素和局部故障的可可以性,他们实际上是完全不同的。消息传递使这些差异变得非常显著,所以开发人员不会被这些虚假的安全感所欺骗。

然而,消息传递也存在少量缺点:

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

  • 实施基于请求/响应的交互的复杂性 – 请求/响应式交互需要做些工作来实现。每个请求消息必需包含应答通道标识符和相关标识符。该服务将包含相关 ID 的响应消息写入应答信道。用户端用相关 ID 将响应与请求相匹配。通常用直接支持请求/响应的 IPC 机制更加容易。

现在我们已经理解了用基于消息的 IPC,让我们来看看请求/响应的 IPC。

3.8、同步的请求/响应 IPC

当用基于同步、基于请求/响应的 IPC 机制时,用户端向服务器发送请求。该服务解决该请求并返回响应。

在许多用户端中,请求的线程在等待响应时被阻塞。其余用户端可可以会用异步、事件驱动的用户端代码,这些代码可可以是由 Futures 或者 Rx Observables 封装的。然而,与用消息传递不同,用户端假定响应可以及时到达。

有许多协议可供选择。有两种流行协议分别是 REST 和 Thrift。我们先来看一下 REST。

3.8.1、REST

如今,开发 RESTful 风格的 API 是很流行的。REST 是一种用了 HTTP (几乎总是)的 IPC 机制。

资源是 REST 中的一个关键概念,它通常表示业务对象,如用户、产品或者这些业务对象的集合。REST 用 HTTP 动词(谓词)来操纵资源,这些资源通过 URL 引使用。例如,GET 请求返回一个资源的表示形式,可可以是 XML 文档或者 JSON 对象形式。POST 请求创立一个新资源,PUT 请求升级一个资源。

引使用 REST 创立者 Roy Fielding:

“REST 提供了一套架构束缚,当作为整体应使用时,其强调组件交互的可扩展性、接口的通使用性、组件的独立部署以及中间组件,以减少交互推迟、实施安全性和封装传统系统。” – Roy Fielding,《架构风格与基于网络的软件架构设计》

图 3-5 展现了打车应使用程序可可以用 REST 的方式之一。

NGINX微服务(三)进程间通信

用了 RESTful 交互的打车应使用

乘客的智可以手机通过向 Trip Management 服务的 /trips 资源发出一个 POST 请求来请求旅程。该服务通过向 Passenger Management 服务发送一个获取乘客信息的 GET 请求来解决该请求。在验证乘客被受权创立旅程后,Trip Management 服务将创立旅程,并向智可以手机返回 201 响应。

许多开发人员宣称其基于 HTTP 的 API 就是 RESTful。然而,正如 Fielding 在这篇博文中所形容的那样,并不是所有的都是这样。

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 的一个好处是不再需要将 URL 硬编码在用户端代码中。另一个好处是,因为资源的表示包含可允许操作的链接,所以用户端不必猜测能对当前状态的资源执行什么操作。

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

  • HTTP 简单易懂。

  • 您能用浏览器中的扩展(如 Postman)测试 HTTP API,或者者用 curl 命令行测试 HTTP API(假设用了 JSON 或者其余少量文本格式)。

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

  • HTTP 是防火墙友好。

  • 它不需要中间代理商,简化了系统架构。

用 HTTP 也存在少量缺点:

  • HTTP 仅直接支持请求/响应的交互方式。您能用 HTTP 进行通知,但服务器必需始终发送 HTTP 响应。

  • 由于用户端和服务直接通信(没有一个中间者来缓冲消息),所以它们必需在交换期间都运行。

  • 用户端必需知道每个服务实例的位置(即 URL)。如第二章关于 API 网关所述,这是现代应使用程序中的一个不简单的问题。用户端必需用服务发现机制来定位服务实例。

开发人员社区最近重新发现了 RESTful API 接口定义语言的价值。有几个能选择,包括 RAML 和Swagger。少量 IDL(如 Swagger)允许您定义请求和响应消息的格式。其余如 RAML,需要您用一个单独的规范,如 JSON 模式。除了使用于形容 API 之外,IDL 通常还具备可从接口定义生成用户端 stub 和服务器 skeleton 的工具。

3.8.2、Thrift

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

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

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

3.9、消息格式

我们已经理解了 HTTP 和 Thrift,现在让我们来看看消息格式的问题。假如您用的是消息系统或者 REST,则能选择您的消息格式。其余 IPC 机制如 Thrift 可可以只支持一些的消息格式,甚至只支持一种。在任一种情况下,用跨语言消息格式就显得非常重要了。即便您现在是以单一语言编写您的微服务,您将来也可可以会用到其余语言。

有两种主要的消息格式:文本和二进制。基于文本格式的例子有 JSON 和 XML。这些格式的优点在于,它们不仅是人类可读的,而且是自形容的。在 JSON 中,对象的属性由键值对集合表示。相似地,在 XML 中,属性由命名元素和值表示。这使得消息消费者可以够筛选其感兴趣的值并忽略其他的值。因而,能轻松地向后兼容作出微小更改的消息格式。

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

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

有几种二进制格式可供选择。假如您用的是 Thrift RPC,您能用二进制 Thrift。假如您选择的消息格式,包括了流行的 Protocol Buffers 和 Apache Avro。这两种格式都提供了一种使用于定义消息结构的类型 IDL。然而,一个区别是 Protocol Buffers 用标记字段,而 Avro 消费者需要知道模式才可以解释消息。因而,Protocol Buffers 的 API 演化比 Avro 更容易用。这里有篇博文对 Thrift、Protocol Buffers 和 Avro 作出了极好的比较。

3.10、总结

微服务必需用进程间通信机制进行通信。在设计服务如何进行通信时,您需要考虑各种问题:服务如何交互、如何为每个服务指定 API、如何演变 API 以及如何解决局部故障。微服务能用两种 IPC 机制:异步消息传递和同步请求/响应。为了进行通信,一个服务必需可以够找到另一个服务。在第四章中,我们将详情微服务架构中服务发现问题。

微服务实战:NGINX 与 应使用程序架构

by Floyd Smith

NGINX 使您可以够实现各种伸缩和镜像操作,使您的应使用程序更加灵敏和高度可使用。您为伸缩和镜像所作的选择会影响到您如何进行进程间通信,这是本章的主题。

我们在 NGINX 方面建议您在实现基于微服务的应使用程序时考虑用四层架构。Forrester 在这方面有详细的报告,您能从 NGINX 上免费下载。这些层代表用户端(包括台式机或者笔记本电脑、移动、可穿戴或者 IoT 用户端)、交付、聚合(包括数据存储)和服务,其中包括应使用功可以和特定服务,而不是共享数据存储。

四层架构比以前的三层架构更加灵活,具备可扩展、响应灵敏、移动友好,并且内在支持基于微服务的应使用程序开发和交付等优点。像 Netflix 和 Uber 这样的行业引领者可以够通过用这种架构来实现使用户所需的性可以水平。

NGINX 本质上非常适合四层架构,从用户端层的媒体流,到交付层的负载均衡与缓存、聚合层的高性可以和安全的基于 API 的通信的工具,以及服务层中支持灵活管理的短暂服务实例。

同样的灵活性使得能实现强大的伸缩和镜像模式,以解决流量变化,防止安全攻击,此外还提供可使用的故障配置切换,从而实现高可使用。

在更为复杂的架构中,包括服务实例实例化和需求不断的服务发现,解耦的进程间通信往往更受青睐。异步和一对多通信方式可可以比高耦合的通信方式更加灵活,它们最终提供更高的性可以和可靠性。

本系列一律译文

https://github.com/oopsguy/microservices-from-design-to-deployment-chinese

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » NGINX微服务(三)进程间通信

发表回复