您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center   Code  
会员   
   
 
     
   
 订阅
  捐助
微服务:从设计到部署(二)
 
来源:网络 发布于: 2017-10-10
  1837  次浏览      16
 

本文是系列文章,文章的第一篇微服务:从设计到部署(一)

3、进程间通信

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

3.1、简介

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

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

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

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

3.2、交互方式

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

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

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

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

同步 — 客户端要求服务及时响应,在等待过程中可能会发生阻塞。

异步 — 客户端在等待响应时不会发生阻塞,但响应(如果有)不一定立即返回。

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

 

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

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

请求/响应

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

通知(又称为单向请求)

客户端向服务发送请求,但不要求响应。

请求/异步响应

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

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

发布/订阅

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

发布/异步响应

客户端发布请求消息,然后等待一定时间来接收消费者的响应。

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

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

使用了多种 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 所示。

为了防止此类问题出现,您必须设计您的服务以处理局部故障。以下是一个由 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 展示了打车应用程序如何使用发布订阅通道。

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 的方式之一。

使用了 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(Hypermedia as the engine of application state,超媒体即应用程序状态引擎)。基本思想是 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 编译器生成客户端 stub 和服务器端 skeleton。编译器可以生成各种语言的代码,包括 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 的通信的工具,以及服务层中支持灵活管理的短暂服务实例。

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

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

4、服务发现

本书主要介绍如何使用微服务来构建应用程序,现在是第四小节。第一章已经介绍了微服务架构模式,并讨论了使用微服务的优点与缺点。第二章和第三章介绍了微服务间的通信,并对不同的通信机制作出对比。在本章中,我们将探讨服务发现(service discovery)相关的内容。

4.1、为何使用服务发现

我们假设您正在编写某些代码,这些代码调用了有 REST API 或 Thrift API 的服务。为了发送一个请求,您的代码需要知道服务实例的网络位置(IP 地址与端口)。在运行于物理硬件上的传统应用中,服务实例的网络位置是相对静态的。例如,您的代码可以从偶尔更新的配置文件中读取网络位置。

然而,在现代基于云的微服务应用中,这是一个更难解决的问题,如图 4-1 所示。

服务实例具有动态分配的网络位置。此外,由于自动扩缩、故障与升级,整组服务实例会动态变更。因此,您的客户端代码需要使用更精确的服务发现机制。

图 4-1、需要服务寻找帮助的客户端或 API 网关

有两种主要的服务发现模式:客户端发现(client-side discovery)与服务端发现(server-side discovery)。让我们先来看看客户端发现。

4.2、客户端发现模式

当使用客户端发现模式时,客户端负责确定可用服务实例的网络位置和请求负载均衡。客户端查询服务注册中心(service registry),它是可用服务实例的数据库。之后,客户端利用负载均衡算法选择一个可用的服务实例并发出请求。

图 4-2 展示了该模式的结构

服务实例的网络位置在服务注册中心启动时被注册。当实例终止时,它将从服务注册中心中移除。通常使用心跳机制周期性地刷新服务实例的注册信息。

Netflix OSS 提供了一个很好的客户端发现模式示例。Netflix Eureka 是一个服务注册中心,它提供了一个用于管理服务实例注册和查询可用实例的 REST API。Netflix Ribbon 是一个 IPC 客户端,可与 Eureka 一起使用,用于在可用服务实例之间使请求负载均衡。本章稍后将讨论 Eureka。

客户端发现模式存在各种优点与缺点。该模式相对比较简单,除了服务注册中心,没有其他移动部件。此外,由于客户端能发现可用的服务实例,因此可以实现智能的,特定于应用程序的负载均衡决策,比如使用一致性哈希。该模式的一个重要缺点是它将客户端与服务注册中心耦合在一起。您必须为服务客户端使用的每种编程语言和框架实现客户端服务发现逻辑。

现在我们已经了解了客户端发现,接下来让我们看看服务器端发现。

4.3、服务端发现模式

服务发现的另一种方式是服务端发现模式。图 4-3 展示了该模式的结构:

客户端通过负载均衡器向服务发出请求。负载均衡器查询服务注册中心并将每个请求路由到可用的服务实例。与客户端发现一样,服务实例由服务注册中心注册与销毁。

AWS Elastic Load Balancer(ELB)是一个服务端发现路由示例。ELB 通常用于负载均衡来自互联网的外部流量。然而,您还可以使用 ELB 来负载均衡虚拟私有云(VPC)内部的流量。客户端通过 ELB 使用其 DNS 名称来发送请求(HTTP 或 TCP)。ELB 负载均衡一组已注册的 Elastic Compute Cloud(EC2)实例或 EC2 Container Service(ECS)容器之间的流量。这里没有单独可见的服务注册中心。相反,EC2 实例 与 ECS 容器由 ELB 本身注册。

HTTP 服务器和负载均衡器(如 NGINX Plus 和 NGINX)也可以作为服务端发现负载均衡器。例如,此博文描述了使用 Consul Template 动态重新配置 NGINX 反向代理。Consul Template 是一个工具,可以从存储在 Consul 服务注册中心中的配置数据中定期重新生成任意配置文件。每当文件被更改时,它都会运行任意的 shell 命令。在列举的博文描述的示例中,Consul Template 会生成一个 nginx.conf 文件,该文件配置了反向代理,然后通过运行一个命令告知 NGINX 重新加载配置的命令。更复杂的实现可以使用其 HTTP API 或 DNS 动态重新配置 NGINX Plus。

某些部署环境(如 Kubernetes 和 Marathon)在群集中的每个主机上运行着一个代理。这些代理扮演着服务端发现负载均衡器角色。为了向服务发出请求,客户端通过代理使用主机的 IP 地址和服务的分配端口来路由请求。然后,代理将请求透明地转发到在集群中某处运行的可用服务实例。

服务端发现模式有几个优点与缺点。该模式的一个很大的优点是发现的细节从客户端抽象出来。客户端只需向负载均衡器发出请求。这消除了为服务客户端使用的每种编程语言和框架都实现发现逻辑的必要性。另外,如上所述,一些部署环境免费提供此功能。然而,这种模式存在一些缺点。除非负载均衡器由部署环境提供,否则您需要引入这个高可用系统组件,并进行设置和管理。

4.4、服务注册中心

服务注册中心(service registry)是服务发现的一个关键部分。它是一个包含了服务实例网络位置的数据库。服务注册中心必须是高可用和最新的。虽然客户端可以缓存从服务注册中心获得的网络位置,但该信息最终会过期,客户端将无法发现服务实例。因此,服务注册中心由使用了复制协议(replication protocol)来维护一致性的服务器集群组成。

如之前所述,Netflix Eureka 是一个很好的服务注册中心范例。它提供了一个用于注册和查询服务实例的 REST API。服务实例使用 POST 请求注册其网络位置。它必须每隔 30 秒使用 PUT 请求来刷新其注册信息。通过使用 HTTP DELETE 请求或实例注册超时来移除注册信息。正如您所料,客户端可以使用 HTTP GET 请求来检索已注册的服务实例。

Netflix 通过在每个 Amazon EC2 可用性区域(Availability Zone)中运行一个或多个 Eureka 服务器来实现高可用。每个 Eureka 服务器都运行在具有一个 Elastic IP 地址的 EC2 实例上。DNS TEXT 记录用于存储 Eureka 集群配置,这是一个从可用性区域到 Eureka 服务器的网络位置列表的映射。当 Eureka 服务器启动时,它将会查询 DNS 以检索 Eureka 群集配置,查找其对等体,并为其分配一个未使用的 Elastic IP 地址。

经过 Eureka 客户端 — 服务与服务客户端 — 查询 DNS 以发现 Eureka 服务器的网络位置。客户端优先使用相同可用性区域中的 Eureka 服务器,如果没有可用的,则使用另一个可用性区域的 Eureka 服务器。

以下列举了其他服务注册中心:

etcd

一个用于共享配置和服务发现的高可用、分布式和一致的键值存储。使用 etcd 的两个著名项目分别为 Kubernetes 和 Cloud Foundry。

Consul

一个发现与配置服务工具。它提供了一个 API,可用于客户端注册与发现服务。Consul 可对服务进行健康检查,以确定服务的可用性。

Apache ZooKeeper

一个被广泛应用于分布式应用程序的高性能协调服务。Apache ZooKeeper 最初是一个 Hadoop 子项目,但现在已经成为一个独立的顶级项目。

另外,如之前所述,部分系统,如 Kubernetes、Marathon 和 AWS,没有明确的服务注册中心。相反,服务注册中心只是基础设施的一个内置部分。 现在我们已经了解服务注册中心的概念,接下来让我们看看服务实例是如何被注册到服务注册中心。

4.5、服务注册方式

如之前所述,服务实例必须在服务注册中心中注册与注销。有几种不同的方式来处理注册和注销。一是服务实例自我注册,即自注册模式。另一个是使用其他系统组件来管理服务实例的注册,即第三方注册模式。我们先来了解自注册模式。

4.6、自注册模式

当使用自注册模式时,服务实例负责在服务注册中心注册和注销自己。此外,如果有必要,服务实例将通过发送心跳请求来防止其注册信息过期。

图 4-4 展示了该模式的结构。

该方式的一个很好的范例就是 Netflix OSS Eureka 客户端。Eureka 客户端负责处理服务实例注册与注销的所有方面。实现了包括服务发现在内的多种模式的 Spring Cloud 项目可以轻松地使用 Eureka 自动注册服务实例。您只需在 Java Configuration 类上应用 @EnableEurekaClient 注解即可。

自注册模式有好有坏。一个好处是它相对简单,不需要任何其他系统组件。然而,主要缺点是它将服务实例与服务注册中心耦合。您必须为服务使用的每种编程语言和框架都实现注册代码。

将服务与服务注册中心分离的替代方法是第三方注册模式。

4.7、第三方注册模式

当使用第三方注册模式时,服务实例不再负责向服务注册中心注册自己。相反,该工作将由被称为服务注册器(service registrar)的另一系统组件负责。服务注册器通过轮询部署环境或订阅事件来跟踪运行实例集的变更情况。当它检测到一个新的可用服务实例时,它会将该实例注册到服务注册中心。此外,服务注册器可以注销终止的服务实例。

图 4-5 展示了该模式的结构:

服务注册器的一个例子是开源的 Registrator 项目。它可以自动注册和注销作为 Docker 容器部署的服务实例。注册器支持多个服务注册中心,包括 etcd 和 Consul。

服务注册器的另一个例子是 NetflixOSS Prana。其主要用于非 JVM 语言编写的服务,它是一个与服务实例并行运行的侧中应用。Prana 使用了 Netflix Eureka 来注册和注销服务实例。

服务注册器在部分部署环境中是一个内置组件。Autoscaling Group 创建的 EC2 实例可以自动注册到 ELB。Kubernetes 服务将自动注册并提供发现。

第三方注册模式同样有好有坏。一个主要的好处是服务与服务注册中心之间解耦。您不需要为开发人员使用的每种编程语言和框架都实现服务注册逻辑。相反,仅需要在专用服务中以集中的方式处理服务实例注册。

该模式的一个缺点是,除非部署环境内置,否则您同样需要引入这样的一个高可用的系统组件,并进行设置和管理。

4.8、总结

在微服务应用程序中,运行的服务实例集会动态变更。实例具有动态分配的网络位置。因此,为了让客户端向服务发出请求,它必须使用服务发现机制。

服务发现的一个关键部分是服务注册中心。服务注册中心是一个可用服务实例的数据库。服务注册中心提供了管理 API 和查询 API 的功能。服务实例通过使用管理 API 从服务注册中心注册或者注销。系统组件使用查询 API 来发现可用的服务实例。

有两种主要的服务发现模式:客户端发现与服务端发现。在使用了客户端服务发现的系统中,客户端查询服务注册中心,选择一个可用实例并发出请求。在使用了服务端发现的系统中,客户端通过路由进行请求,路由将查询服务注册中心,并将请求转发到可用实例。

服务实例在服务注册中心中注册与注销有两种主要方式。一个是服务实例向服务注中心自我注册,即自注册模式。另一个是使用他系统组件代表服务完成注册与注销,即第三方注册模式。

在某些部署环境中,您需要使用如 Netflix Eureka 或 Apache ZooKeeper 等服务注册中心来设置您自己的服务发现基础设施。在其他部署环境中,服务发现是内置的,例如,Kubernetes 和 Marathon,可以处理服务实例的注册与注销。他们还在每一个扮演服务端发现路由器角色的集群主机上运行一个代理。

一个 HTTP 反向代理和负载均衡器(如 NGINX)也可以用作服务端发现负载均衡器。服务注册中心可以将路由信息推送给 NGINX,并调用一个正常的配置更新;例如,您可以使用 Consul Template。NGINX Plus 支持额外的动态重新配置机制 — 它可以使用 DNS 从注册中心中提取有关服务实例的信息,并为远程重新配置提供一个 API。

微服务实战:NGINX 的灵活性

by Floyd Smith

在微服务环境中,由于自动扩缩、故障和升级,您的后端基础设施可能会不断变化,这些包括了服务的创建,部署和扩展。如本章所述,在动态重新分配服务位置的环境中需要服务发现机制。

将 NGINX 应用于微服务的一部分好处是,您可以轻松地将其配置为自动响应后端基础设施作出的变更。NGINX 配置不仅简单灵活,而且兼容 Amazon Web Services 使用的模板,可以更轻松地管理特定的服务变更与受负载均衡的变更服务组。

NGINX Plus 具有即时重新配置 API,无需重新启动 NGINX Plus 或手动重新加载配置就能感知受负载均衡服务组的变更。在 NGINX Plus Release 8 及更高版本中,您可以将对 API 所做的更改配置为在重新启动和配置重新加载时保持不变。(重新加载不需要重新启动,不要断开连接)NGINX Plus Release 9 及更高版本支持使用 DNS SRV 记录进行服务发现,可与现有服务器发现平台(如 Consul 和 etcd)进行更紧密地集成。

我们在 NGINX 创建了一个用于管理服务发现的模型:

为几个应用程序单独运行的Docker容器,包括如 etcd 的服务发现应用程序、服务注册工具、一个或多个后端服务器以及用于负载均衡其他容器的 NGINX Plus 本身。

注册工具监控 Docker 的新容器,并使用服务发现工具注册新服务,此外,还可以删除消失的容器。

容器及其运行的服务将自动添加到负载均衡上游服务器中或从其中删除。

此 Demo 应用程序可用于多个服务发现应用程序:Consul API、来自 Consul 的 DNS SRV 记录、etcd 以及 ZooKeeper 等。

   
1837 次浏览       16
相关文章

企业架构、TOGAF与ArchiMate概览
架构师之路-如何做好业务建模?
大型网站电商网站架构案例和技术架构的示例
完整的Archimate视点指南(包括示例)
相关文档

数据中台技术架构方法论与实践
适用ArchiMate、EA 和 iSpace进行企业架构建模
Zachman企业架构框架简介
企业架构让SOA落地
相关课程

云平台与微服务架构设计
中台战略、中台建设与数字商业
亿级用户高并发、高可用系统架构
高可用分布式架构设计与实践