服务发现
这是第四章,这本书是关于用微服务构建应用。 第一章介绍了微服务架构模式和讨论了采用微服务的优缺点 。第二章和第三章描述了微服务之间通讯的各个方面。 本章,我们探索服务发现相关的问题。
为什么要用服务发现?
让我们想象一下你正在写一些代码通过REST API或Thrift API 调用一个服务。 为了发起一个请求,你的代码需要知道这个服务实例的网络地址(IP地址和端口号)。 运行在物理机上的传统应用,服务实例的网络地址相对静态。比如,你的代码从偶尔更新的配置文件读取网络服务实例的网络地址。
然而,现代的,基于云的微服务应用,这变得更加困难,如图4-1.
服务实例的地址是动态分配的。 更甚者,一组服务实例,因为自动伸缩,失败和更新等网络地址动态改变。 因此,你的客户端代码需要用一个更精心设计的服务发现机制。
有两种服务发现的模式:客户端发现和服务器端发现。 让我们先来看看客户端发现。
客户端发现模式
当采用客户端发现模式时,客户端负责确定可用服务实例的网络地址和负载均衡他们的请求。 客户端查询服务注册表,服务注册表是可用服务实例的数据库。 然后客户端 采用负载均衡算法选择可用的服务实例发起请求。
图4-2 展示了这种模式的结构
服务实例的网络地址是当服务启动时注册到服务注册表的。当服务实例终结的时候,它被移出服务注册表。 服务实例的注册信息通常是采用心跳(heartbeat)机制周期性刷新。
Netflix OSS 提供了一个非常棒的客户端服务发现模式的例子。Netflix Eureka是一个服务注册表。 它提供了RESTAPI用于管理服务实例注册和查询可用实例。Netflix Ribbon 是一个IPC客户端,它和Eureka协同工作,负载均衡对可用实例的请求。 我们将会在本章的后面深入讨论Eureka。
客户端发现模式有很多优点和缺点。 这个模式相对直观,并且除了服务注册表,没有其他的动态部分。 因为客户端知道可用的服务实例,它可以智能地,针对特定应用做出负载均衡判断比如采用哈希方式负载均衡。 这种模式一个比较大的缺点是它把客户端和服务注册表耦合在一起。 对客户端使用每种编程语言和框架,你必须实现客户端服务发现逻辑。
看完了客户端侧的发现机制,接下来让我们看看服务器端的服务发现机制。
服务器端的发现模式
另外一种服务发现的方式是服务器端的服务发现模式图4-3 展示了这种模式的结构。
客户端通过负载均衡器发起对服务的请求。 负载均衡器查询服务注册表并且路由每个请求到可用的服务实例。 正如客户端发现机制一样, 服务实例在服务注册表里注册和注销。
AWS Elastic Load Balance是个服务器端发现路由器的例子。 ELB是通常被用于负载均衡来自互联网的外部请求。 然而,你也可以在VPC(Virtual Private Cloud)里用ELB负载均衡流量。客户端通过ELB,用DNS名称发起请求(HTTP或TCP)。 ELB在一组注册过的EC2(Elastic Compute Cloud)实例或EC2容器服务(EC2Container Service)容器之间负载均衡请求。 没有独立的可见的服务注册表。而是,EC2实例和ECS容器用ELB注册自己。
HTTP服务器和负载均衡比如NGINX Plus和NGINX也可以被用作服务端发现负载均衡器。 比如,这个博客描述了使用Consul 模板动态地重新配置NGINX反向代理。 Consul Template是一个工具,它可以周期地重新生成任意配置文件,配置的信息来自于Consul 服务注册表(Consul Service Registry)。 无论何时文件更改,它都会执行一个任意的shell命令。 在博客描述的例子中,Consul模板生成nginx.conf 文件,它配置反向代理,并且然后运行一个命令告知NGINX重载配置。 一个更复杂的实现可以动态重新配置NGINX Plus(通过它的HTTP API或DNS)。
一些部署环境,比如Kubernetes 和 Marathon 在集群的每个主机上运行代理。 这个代理扮演着服务器端发现负载均衡器。 为了请求一个服务,一个客户端通过代理用主机的地址和服务分配的端口路由请求。 代理然后透明地转交请求(Request)给集群中的可用的服务实例。
服务器端发现模式有优点也有缺点。 这种模式的优点是服务发现的细节部分从客户端抽象掉了。 客户端仅仅需要发起请求给负载均衡器。 这就消除了对你的服务客户端每种编程语言和框架实现发现逻辑的要求。同样,正如上面提到的,一些部署环境免费提供了这种功能。这种模式当然也有些缺点,除非部署环境提供了负载均衡器,否则它也是需要配置和管理高可用(Highly Available)的系统组件。
服务注册表
服务注册表是服务发现的关键部分。 它是包含服务实例的网络地址数据库。 服务注册表需要高可用(HA)并且保持时刻最新。 客户端可以缓存从注册表获取到的网络地址。然而,这个信息最终会变得过时导致客户端不能发现服务实例。因此,一个服务注册表包含一个服务器集群,它们用复制协议(replication protocol)维持一致性。
正如前面提到的,Netflix Eureka是个服务注册很好的例子。 它提供了REST API用于注册和查询服务实例。 一个服务实例用POST请求注册它的网络地址。每隔30秒,它必须用PUT请求刷新它的注册信息。 服务注册可以通过HTTP Delete请求移除,或是实例注册超时(30秒超时)自动移除。 正如你或许期待的,一个客户端可以通过HTTP GET请求获取注册服务实例。
Netflix 是通过在每个区运行一个或多个Eureka服务器保证高可用的(HA)。每个Eureka运行在一个EC2实例的服务器拥有一个灵活的IP地址(Elastic IP address)。DNS TEXT记录被用来存储Eureka集群配置,它是一个从可用区域到Eureka服务器地址的映射地图。当一个Eureka服务器启动时,它查询DNS以获取Eureka集群配置信息,定位它的同伴,并且分配它自己一个未使用的IP地址。
其他服务注册的例子,包括:
etcd - 一个高可用的,分布式的,一致性的,键值存储,它用于分享配置和服务发现。有个两个很著名的项目用etcd,他们是Kubernetes 和 CloudFoundry。
Consul-一个用于发现和配置的工具。 它提供了一个API,允许客户端注册和发现服务。Consul能执行健康检查和确定服务的可用性。
Apache ZooKeeper- 一个广泛使用的,高效协同的服务用于分布式应用。Apache ZooKeeper 原是Hadoop的子项目,但是现在已经是一个分离的,顶级项目。
正如前面提到的,一些系统比如Kubernetes,Marathon和AWS没有明确的服务注册表。相反,服务注册表仅仅是基础架构的内置部分。
服务注册选项
如前面提到的,服务实例必须从服务注册表注册和注销。 有几个不同的选项处理注册和注销。一个选项是服务实例注册他们自己,自我注册模式。 其他的选项是,用其他的系统管理服务实例的注册,第三方注册模式。 首先让我们看看自我注册模式。
自我注册模式
当我们采用自我注册模式的时,一个服务实例在服务注册表中负责注册和注销自己。 如果是需要的,一个服务实例发送心跳请求以防止注册过期。图4-4 显示了这种模式的结构。
Netflix OSS Eureka客户端是这种方法的很好的例子。 Eureka客户端处理服务实例的注册和注销的方方面面。 Spring Cloud Project实现了许多模式包括服务发现,使自动注册一个服务实例到Eureka变得简单。 你只需要简单地用@EnableEurekaClient标注Java
Configuration 类 就可以。
自我注册模式有缺点和优点。 一个优点是它相对简单,并且不需要其他的系统组件。 然而,一个主要的缺点是它耦合了服务实例和服务注册表。 你必须要在你用的服务中为每种编程语言和框架中实现注册逻辑。
一个替代方法,它从服务注册表解耦出来,是第三方注册模式。
第三方注册模式
当你使用第三方注册模式时,服务实例不负责在服务注册表中注册他们自己。 相反,另外的系统组件又称为服务注册员(registrar,or服务登记员)处理服务注册。 服务注册员通过轮询部署环境或订阅事件的方式,跟踪运行实例的变化。当它注意到有新的可用的服务实例时,它在服务注册表中,注册这个新的可用的服务。 服务注册员也负责注销终止的服务实例。
图4-5 显示了这种模式的结构:
一个服务员登记员(Registrar)的例子是开源项目 Registrator它自动地注册和注销服务实例,这些实例被部署在Dokcer容器中。 Registrator支持几种注册表,包括etcd 和 Consul。
另外一个服务登记员的例子是NetflixOSS Prana。 它主要倾向于用非JVM语言写的服务,它是一个辅助应用(sidecar Application)和服务实例并排运行。 Prana在Netflx
Eureka中注册和注销服务实例。
服务登记员在一些部署环境中是一个内置的组件。Autonscaling Group 创建的EC2实例能用ELB自动注册。 Kubernetes 服务是自动注册并对服务发现可用。
当然了,第三方注册模式也有优点和缺点。 一个主要的优点是服务从服务注册表中解耦出来。 你不需要去为每种你用的语言和框架去实现服务注册逻辑(写代码)。相反,服务实例的注册是在一个专用服务中集中处理。
总结
在一个微服务应用中,运行实例动态更改。 实例已经分配了网络地址。因此,为了能使客户端发起到一个服务的请求,它必须采用服务发现机制。
服务发现的关键部件是服务注册表(service registry)。 服务注册表是一个可用实例的数据库。 服务注册提供了管理API和一个查询API。 服务实例用管理API在注册表中注册和注销服务。 查询API被系统组件用于发现可用的服务实例。
有两个主要的服务发现模式:客户端发现(client-side service discovery)和服务端发现(service-side discovery)。 在使用客户端发现的系统中,客户端查询服务注册表,选择可用的服务实例,然后发起请求。 在使用服务端发现的系统中,客户端通过路由发起请求,它查询服务注册表,然后转交请求到可用的实例。
有两种主要从注册表注册和注销服务的方法。一种方法是服务实例在注册表中自己注册自己,也就是自我注册模式(self-registration pattern)。 另外一种方式是用其他系统组件处理注册和注销服务,也就是第三方注册模式(third-party registration pattern)。
在一些部署环境中,你需要设置你自己的服务发现基础架构,可以采用服务注册如Netflix Eureka ,etcd ,或是 Apache ZooKeeper 。 在一些其他的部署环境中,服务注册是内置的,比如,Kubernetes 和Marathon,它自己处理服务的注册和注销 。 他们也在每个集群主机上运行代理,它的作用是服务端发现路由器。
HTTP反向代理和负载均衡器如NGINX也能被用作服务端的发现的负载均衡器。 服务注册表能推送路由信息到NGINX并且调用一个优雅的配置更新。 比如你可以用Consul Template。 NGINX Plus支持附加的动态重配置机制(additional dynamic reconfiguration mechanism)。 它能用DNS从注册表拉关于服务实例信息,并且它提供了一个API用于远程配置。
翻译自“Microservice -from design to deployment" by Chris Richardson with Floyd Smith.