服务注册与发现

编者按:『应用架构一团糟?如何将单体应用改造为微服务』一文中,介绍了如何逐步地将单体应用改造为一系列的微服务。本文是系列文章的第四篇,将为大家讲述如何在微服务架构实现服务发现。作者首先介绍了服务发现的两种实现模式,接下来介绍了服务发现中最重要的服务注册如何实现。

作者简介:Chris Richardson 是 Cloudfoundry.com 的创始人,现在为提供开发和部署应用的咨询服务。

以下为译文:

为什么使用服务发现?

想象一下,如果你在写代码调用一个有 REST API 或 Thrift API 的服务,你的代码需要知道一个服务实例的网络地址(IP 地址和端口)。运行在物理硬件上的传统应用中,服务实例的网络地址是相对静态的,你的代码可以从一个很少更新的配置文件中读取网络地址。

在一个现代的,基于云的微服务应用中,这个问题就变得复杂多了,如下图所示:

/assets/404.png

服务实例的网络地址是动态分配的。而且,由于自动扩展,失败和更新,服务实例的配置也经常变化。这样一来,你的客户端代码需要一套更精细的服务发现机制。

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

客户端服务发现模式

当使用客户端服务发现的时候,客户端负责决定可用的服务实例的网络地址,以及围绕他们的负载均衡。客户端向服务注册表(service registry)发送一个请求,服务注册表是一个可用服务实例的数据库。客户端使用一个负载均衡算法,去选择一个可用的服务实例,来响应这个请求,下图展示了这种模式的架构:

/assets/404.png

一个服务实例被启动时,它的网络地址会被写到注册表上;当服务实例终止时,再从注册表中删除。这个服务实例的注册表通过心跳机制动态刷新。

Netflix OSS 提供了一个客户端服务发现的好例子。Netflix Eureka 是一个服务注册表,提供了 REST API 用来管理服务实例的注册和查询可用的实例。Netflix Ribbon 是一个 IPC 客户端,和 Eureka 一起处理可用服务实例的负载均衡。下面会深入讨论 Eureka。

客户端的服务发现模式有优势也有缺点。这种模式相对直接,但是除了服务注册表,没有其它动态的部分了。而且,由于客户端知道可用的服务实例,可以做到智能的,应用明确的负载均衡决策,比如一直用 hash 算法。这种模式的一个重大缺陷在于,客户端和服务注册表是一一对应的,必须为服务客户端用到的每一种编程语言和框架实现客户端服务发现逻辑。

服务器端服务发现模式

下图展示了这种模式的架构

/assets/404.png

客户端通过负载均衡器向一个服务发送请求,这个负载均衡器会查询服务注册表,并将请求路由到可用的服务实例上。通过客户端的服务发现,服务实例在服务注册表上被注册和注销。

AWS 的 ELB(Elastic Load Blancer)就是一个服务器端服务发现路由器。一个 ELB 通常被用来均衡来自互联网的外部流量,也可以用 ELB 去均衡流向 VPC(Virtual Private Cloud)的流量。一个客户端通过 ELB 发送请求(HTTP 或 TCP)时,使用的是 DNS,ELB 会均衡这些注册的 EC2 实例或 ECS(EC2 Container Service)容器的流量。没有另外的服务注册表,EC2 实例和 ECS 容器也只会在 ELB 上注册。

HTTP 服务器和类似 Nginx、Nginx Plus 的负载均衡器也可以被用做服务器端服务发现负载均衡器。例如,Consul Template 可以用来动态配置 Nginx 的反向代理。

Consul Template 定期从存储在 Consul 服务注册表的数据中,生成任意的配置文件。每当文件变化时,会运行一个 shell 命令。比如,Consul Template 可以生成一个配置反向代理的 nginx.conf 文件,然后运行一个命令告诉 Nginx 去重新加载配置。还有一个更复杂的实现,通过 HTTP API 或 DNS 去动态地重新配置 Nginx Plus。

有些部署环境,比如 Kubernetes 和 Marathon 会在集群中的每个 host 上运行一个代理。这个代理承担了服务器端服务发现负载均衡器的角色。为了向一个服务发送一个请求,一个客户端使用 host 的 IP 地址和服务分配的端口,通过代理路由这个请求。这个代理会直接将请求发送到集群上可用的服务实例。

服务器端服务发现模式也是优势和缺陷并存。最大的好处在于服务发现的细节被从客户端中抽象出来,客户端只需要向负载均衡器发送请求,不需要为服务客户端使用的每一种语言和框架,实现服务发现逻辑;另外,这种模式也有一些问题,除非这个负载均衡器是由部署环境提供的,又是另一个高需要启动和管理的可用的系统组件。

服务注册表(Service Registry)

服务注册表是服务发现的关键部分,是一个包含了服务实例的网络地址的数据库,必须是高可用和最新的。客户端可以缓存从服务注册表处获得的网络地址。但是,这些信息最终会失效,客户端会找不到服务实例。所以,服务注册表由一个服务器集群组成,通过应用协议来保持一致性。

正如上面提到的,Netflix Eureka 是一个服务注册表的好例子。它提供了一个 REST API 用来注册和查询服务实例。一个服务实例通过 POST 请求来注册自己的网络位置,每隔 30 秒要通过一个 PUT 请求重新注册。注册表中的一个条目会因为一个 HTTP DELETE 请求或实例注册超时而被删除,客户端通过一个 HTTP GET 请求来检索注册的服务实例。

Netflix 通过在每个 EC2 的可用区中,运行一个或多个 Eureka 服务器实现高可用。每个运行在 EC2 实例上的 Eureka 服务器都有一个弹性的 IP 地址。DNS TEXT records 用来存储 Eureka 集群配置,实际上是从可用区到 Eureka 服务器网络地址的列表的映射。当一个 Eureka 服务器启动时,会向 DNS 发送请求,检索 Eureka 集群的配置,定位节点,并为自己分配一个未占用的弹性 IP 地址。

Eureka 客户端(服务和服务客户端)查询 DNS 去寻找 Eureka 服务器的网络地址。客户端更想使用这个可用区内的 Eureka 服务器,如果没有可用的 Eureka 服务器,客户端会用另一个可用区内的 Eureka 服务器。

其它服务注册的例子包括:

  • Etcd:一个高可用,分布式,一致的 key-value 存储,用来共享配置和服务发现。Kubernetes 和 Cloudfoundry 都使用了 etcd;

  • Consul:一个发现和配置服务的工具。客户端可以利用它提供的 API,注册和发现服务。Consul 可以执行监控检测来实现服务的高可用;

  • Apache Zookeeper:一个常用的,为分布式应用设计的高可用协调服务,最开始 Zookeeper 是 Hadoop 的子项目,现在已经顶级项目了。

一些系统,比如 Kubernetes,Marathon 和 AWS 没有一个明确的服务注册组件,这项功能是内置在基础设置中的。

下面我们来看看服务实例如何在注册表中注册。

服务注册(Service Registration)

前面提到了,服务实例必须要从注册表中注册和注销,有很多种方式来处理注册和注销的过程。一个选择是服务实例自己注册,即 self-registration 模式。另一种选择是其它的系统组件管理服务实例的注册,即第 third-party registration 模式。

The Self-Registration Pattern

在 self-registration 模式中,服务实例负责从服务注册表中注册和注销。如果需要的话,一个服务实例发送心跳请求防止注册过期。下图展示了这种模式的架构:

/assets/404.png

Netflix OSS Eureka 客户端是这种方式的一个好例子。Eureka 客户端处理服务实例注册和注销的所有问题。Spring Cloud 实现包括服务发现在内的多种模式,简化了 Eureka 的服务实例自动注册。仅仅通过 @EnableEurekaClient 注释就可以注释 Java 的配置类

self-registration 模式同样也是优劣并存。优势之一在于简单,不需要其它组件。缺点是服务实例和服务注册表相对应,必须要为服务中用到的每种编程语言和框架实现注册代码。

The Third-Party Registration Pattern

在 third-party registration 模式中,服务实例不会自己在服务注册表中注册,由另一个系统组件 service registrar 负责。service registrar 通过轮询部署环境或订阅事件去跟踪运行中的实例的变化。当它注意到一个新的可用的服务实例时,就会到注册表中去注册。service registrar 也会将停止的服务实例注销,下图展示了这种模式的架构。

/assets/404.png

service registrar 的一个例子是开源的 Registrator 项目。它会自动注册和注销像 Docker 容器一样部署的服务。Registrator 支持 etcd 和 Consul 等服务注册。

另一个 service registrar 的例子是 NetflixOSS Prana。主要用于非 JVM 语言编写的服务,它是一个和服务实例配合的『双轮』应用。Prana 会在 Netflix Eureka 上注册和注销实例。

service registrar 是一个部署环境的内置组件,由 Autoscaling Group 创建的 EC2 实例可以被 ELB 自动注册。Kubernetes 服务也可以自动注册。

third-party registration 模式主要的优势在于解耦了服务和服务注册表。不需要为每个语言和框架都实现服务注册逻辑。服务实例注册由一个专用的服务集中实现。缺点是除了被内置到部署环境中,它本身也是一个高可用的系统组件,需要被启动和管理。

总结

在一个微服务应用中,服务实例在运行时的配置也会动态变化,包括他们的网络地址。为了满足客户端向服务发送请求的需要,必须要实现服务发现机制。

服务发现的关键部分是服务注册表。服务注册表是一个可用的服务实例的数据库。服务注册表提供了一个管理 API 和一个查询 API。服务实例的注册和注销通过管理 API 实现,查询 API 用来寻找可用的服务实例。

有两种主要的服务发现模式:客户端服务发现和服务器端服务发现。客户端服务发现系统中,客户端查询服务注册表,选择一个可用的实例,响应一个请求;在服务器端服务发现系统中,客户端通过一个路由器发送请求,这个路由器会去查询服务注册表,并将请求发送给可用的实例。

有两种形式可以实现服务实例的注册和注销,一种是 self-registration 模式,一种是 third-party registration 模式。

一些部署环境中,需要通过类似 Netflix Eureka,etcd 或 Apache Zookeeper 的组件,启动自己的服务发现基础设施。其它的部署环境中,服务发现是内置的。比如,Kubernetes 和 Marathon 处理服务实例的注册和注销,还会在每个集群 host 上运行一个代理,作为服务器端服务发现路由器的角色。

一个 HTTP 反向代理和 Nginx 也可以被用做服务器端服务发现负载均衡器。服务注册表可以推送路由信息到 Nginx,引起配置更新,比如可以用 Consul Template。Nginx Plus 支持动态的重配置机制,可以从注册表中拉取服务实例相关的信息,还提供了远程配置的 API。