本文出自Nginx官网,是微服务介绍系列文章的第四篇。原文地址:https://www.nginx.com/blog/service-discovery-in-a-microservices-architecture/
1.介绍
在第一篇文章中比较了微服务架构的应用和单体应用的差异,讨论了微服务架构的优点与缺点;第二篇文章和第三篇文章中讨论了进程间通信相关的问题;在本篇文章中我们讨论服务发现相关的问题。
2.为什么使用服务发现
为了访问REST接口或者Thrift接口的服务,你需要知道服务实例的IP地址和端口号。对部署在物理机上的传统应用而言,应用的IP地址相对固定;发起请求时查询配置文件获取服务地址,在服务地址变化时更新配置文件。
在基于云的微服务应用中,事情变复杂了:服务实例的地址是动态分配的;甚至,由于自动伸缩、服务宕机、服务更新等原因导致服务的实例集合也是变化的,如下图所示:
客户端需要使用更复杂的服务发现机制来解决问题。主要有两种服务发现模式:客户端发现和服务端发现。
3.客户端服务发现模式
使用客户端服务发现时,客户端负责决定可用服务实例的网络地址,并在它们中间进行负载均衡。客户端查询包含所有可用服务实例的服务注册表,使用负载均衡算法选择一个可用的服务,发出服务请求。过程如下图所示:
服务实例启动后,向服务注册表注册网络地址;服务终止时,服务注册表会删除对应记录;服务注册表使用心跳机制定期更新。
Netflix OSS是客户端服务发现的例子。Netflix Eureka是服务注册表,它使用REST风格的接口,提供服务注册管理和可用服务查询功能。Netflix Ribbon在Eureka的配合下实现负载均衡。
客户端服务发现模式有很多优点和缺点:这种模式相对简单,除了服务注册表之外,其他模块相对稳定;由于知道哪些服务可用,客户端就可能做一些更智能的、应用特有的负载均衡策略,比如一致哈希等;一个显著缺点是将客户端和服务注册表耦合在一起,必须根据客户端使用的开发语言和框架实现服务发现逻辑。
4.服务端服务发现模式
在服务端服务发现模式下,客户端向服务端的负载均衡器发送请求,负载均衡器查询服务注册表,将请求路由到对应的服务实例。像客户端服务发现模式一样,服务实例实现向服务注册表注册和注销的功能,示意图如下:
AWSElastic Load Balancer (ELB)是服务端服务发现的例子。ELB一般用于来自互联网的外部负载均衡,实际上,它也可以用于私有云内部的负载均衡。客户端使用域名访问ELB,ELB在一组注册的云主机或者云容器之间做负载均衡;ELB中没有专门的服务注册表,云主机直接向ELB注册。
NGINXPlus或NGINX作为HTTP服务器和负载均衡器,也可以作为服务端服务发现的负载均衡器,比如,可以使用Consul Template动态配置NGINX作为反向代理。Consul Template能根据Consul service registry存储的配置数据定期生成配置文件;在文章(https://www.airpair.com/scalable-architecture-with-docker-consul-and-nginx)提到的例子中,Consul Template能动态生成反向代理的配置文件nginx.conf,并执行命令告诉Nginx重新加载配置。使用HTTP接口或者DNS能实现更复杂的Nginx的动态配置。
像Kubernetes和Marathon等部署环境,集群中的每个服务实例都需要运行一个代理作为服务端服务发现的负载均衡器,客户端请求发送到代理,代理将请求透明转发到集群下的某个可用实例。
服务端服务发现模式也有一些优点和缺点,最大的好处就是服务发现的细节从客户端剥离了,客户端不需要实现服务发现的代码,只需要向负载均衡器发服务请求;况且,有些部署环境已经免费实现了负载均衡的功能。服务端服务发现模式的缺点是,如果部署环境不提供负载均衡功能,你需要实现和管理一个高可用的负载均衡系统,这不是件简单的事。
5. 服务注册表
服务注册表是服务发现机制的关键部分,它是保存服务实例网络地址的数据库。服务注册表必须是高可用并保证数据是最新的。客户端能缓存从服务注册表取得的服务实例信息,但是这些信息最终会过时,因此包含集群服务的服务注册表需要使用复制协议来保证缓存信息的一致性。
就像之前提到的,Netflix Eureka是服务注册表的一个示例,它为服务注册和服务查询提供REST风格的API。服务实例使用POST请求注册网络地址,每隔30秒使用PUT请求刷新状态。服务注册表项可以使用DELETE请求主动移除,也会因为注册超时被动移除;客户端使用GET请求获取可用服务实例。
Netflix通过在每个Amazon EC2可用区域中运行一个或多个Eureka服务器来实现高可用性。Eureka服务器运行在具有弹性IP的EC2实例上,使用DNS TEXT保存Eureka集群的配置,实现从可用区域到一组Eureka实例网络地址的映射;Eureka服务启动后,它会查询DNS获取集群配置,定位到所属集群,获取一个可用的弹性IP。
Eureka 客户端查询DNS获取服务端地址,一般情况下,客户端倾向与在一个可用区域内使用同一个服务端;如果本区域内的服务端都不可用,才会使用另外一个可用区域的服务端。
其他服务注册表包括:
etcd:一致的、分布式、高可用、使用键值存储的工具,可用于配置共享和服务发现,在Kubernetes和Cloud Foundry中使用。
Consul:另外一个服务配置和服务发现工具,支持服务健康检查。
Apache Zookeeper:一个广泛应用的、高效的,应用于分布式系统的服务协调工具,之前是Hadoop的一个子项目,现在是顶级项目。
Kubernetes、Marathon和AWS没有单独的服务注册表,服务注册表是它们架构的内置功能。
6.服务注册选项
有一系列不同的方法实现服务注册表中服务的注册和注销:一种方法是让服务自己注册,叫做自注册模式;另外一种方式是使用第三方组件管理服务注册,叫做第三方注册模式。
自注册模式
当使用自注册模式时,服务自己负责向服务注册表注册和注销;如果需要,服务实例还需要定期发送心跳以避免超时;下图展示了这种结构:
Netflix OSS Eureka是自注册模式的一个例子,Eureka客户端处理所有服务注册和服务注销的问题。在Spring Cloud项目中,使用Eureka很容易实现服务自动注册,方式是在Java配置类中使用@EnableEurekaClient声明。
自注册模式有明显的优点和缺点:优点是实现简单,服务注册时不需要查询其他系统模块;缺点是将服务端和服务注册表耦合在一起,需要根据服务端不同的编程语言和框架实现相应的注册和注销逻辑。
第三方注册模式
使用第三方注册模式,服务端不用考虑服务注册和注销,工作由第三方服务注册器完成。服务注册器通过轮询部署环境或者订阅事件的方式跟踪运行服务的变化,当发现新的服务,将其注册到注册表中;当服务终止在注册表中注销。下图展示了该模式的结构:
开源的Registrator是服务注册器的一个例子,它能自动注册和注销Docker容器中服务实例,支持多种服务注册表(包括etcd和Consul)。
另外一个服务注册器的例子是NetflixOSS Prana,主要用于非JVM语言编写的服务,它是一个与服务实例并行运行的sidecar应用程序,使用Netflix Eureka注册和注销服务实例。
一些部署环境内置了服务注册器功能:通过可伸缩组创建的Amazon EC2实例可以自动注册(使用ELB),Kubernetes的服务也能够实现自动注册。
使用第三方服务注册的最大好处是服务和服务注册表解耦,你不需要根据服务端使用开发语言和框架实现服务注册逻辑,服务注册由专门的服务集中负责;缺点是如果部署环境没有实现服务注册功能,需要单独开发和管理一个高可用的服务注册系统。
7.总结
在微服务架构下,服务实例是动态变化的,服务实例的IP地址是动态分配的,客户端要请求服务,必须使用服务发现机制。
服务注册表是服务发现机制的关键组成部分,它是所有可用服务实例的数据库,它提供管理接口和查询接口;服务实例使用管理接口实现服务的注册和注销,系统的其他组成部分使用查询接口获取可用服务。
有两种主要的服务发现模式:客户端服务发现和服务端服务发现。在使用客户端服务发现的系统中,客户端查询服务注册表,获取可用服务,发出服务请求;在使用服务端服务发现的系统中,客户端向服务路由器发请求,服务路由器查询服务注册表,将服务请求转发给服务实例。
服务注册也有两种主要的方式:一种是自注册模式,服务自己向服务注册表注册;另一种是第三方注册模式,第三方组件代表服务实例进行服务注册和注销。
在某些部署环境下,需要使用Netflix Eureka、etcd或者Apache Zookeeper等服务注册器构建自己的服务发现机制;另外一些部署环境可能内置了服务发现功能,比如,使用Kubernetes和Marathon能实现服务的注册和注销,可它们也需要在每一个集群主机上运行一个代理,充当服务端服务发现的路由器。
Nginx作为HTTP反向代理服务器和负载均衡器,也能实现服务端服务发现和负载均衡。可以使用Consul Template将服务注册表的更新推送给Nginx,并调用配置更新指令更新配置;Nginx Plus支持其他动态配置机制:使用DNS从服务注册表中获取服务实例信息,提供远程配置接口进行远程配置。