微服务架构中常用的解决方案,总结了传统服务发现方案

正常情况下当我们要访问服务时需要知道服务实例地址和端口,如果服务实例地址和端口都是固定的我们可以直接将其配置在文件中使用,但大多数线上生产环境尤其容器部署情况下服务实例地址都是动态分配的,只有当服务实例实际部署之后才能获得地址,服务调用者根本无法提取获取服务实例地址和端口,只能在运行时通过服务发现组件解析服务名来获取服务实例地址和端口。

服务发现简单来讲就是通过服务名找到提供服务的实例地址和端口,主要用于解决如何获取服务实例地址问题。近年来随着容器技术的兴起,大量服务分散在系统各处,服务彼此之间调用都需要通过服务发现来实现。服务发现是分布式系统中不可或缺的关键组件,常用于构建服务发现解决方案的开源框架如Zookeeper、 Etcd、Consul。本文主要介绍如何基于Zookeeper、 Etcd、Consul构建服务发现方案并对其可能出现的问题进行讨论。

一个标准的服务发现架构主要有三部分组成分别是服务注册中心、服务调用者、服务提供者,架构图如下所示:

微服务架构中常用的解决方案,总结了传统服务发现方案

服务注册中心是服务发现的核心组件,其本质上是一个服务名和服务实例地址映射集合,除了提供基本的服务名解析功能外,还需要具备如下能力:

容错(Fault Tolerance):服务注册中心保存了分布式系统中所有服务名与服务实例地址映射,一旦故障必将导致整个系统不可用,是整个分布式系统核心,必须具备高可用性; 服务健康检查(Service Health Check):服务注册中心必须要能及时发现故障实例并将其注销以防止被错误访问; 监视器(Watcher):服务注册中心必须具备及时通知服务调用者服务实例注册或注销的能力,以便服务调用者及时采取措施。

注册或注销服务实例一般有两种选择:

服务实例自己注册即Self-Registration模式,在服务实例启动成功后主动将自己注册到服务注册中心,这种方法好处是架构简单但需要为服务用到的每种编程语言实现注册代码; 通过其他组件来注册服务实例即Thrid-party Registration模式,例如使用一个独立Agent通过轮询或监听事件去跟踪运行的服务实例变化进行注册或注销,好处是服务实例与服务注册中心解耦但引入第三方组件增加了架构复杂性。

服务发现方案

DNS

DNS(Domain Name System)是一种通过解析域名获取IP和端口的机制。将SRV记录注册到DNS服务器上,通过DNS解析流程进行解析。但是DNS存在两个问题:一是当服务实例启动之后将SRV记录注册到DNS服务器上比较难,需要手动维护;二是DNS严重依赖缓存,服务使用方无法及时知道一个服务实例是否已经停止。

mDNS

mDNS(multicast DNS即组播DNS)是一种零配置的服务发现机制,在内部网络中经常使用,每个服务都有一个内置的mDNS响应程序,从而不需要单独的服务注册中心。mDNS最大的问题就是要求网络基础设施支持IP多播(IP multicast),对于云环境来说显然是无法满足的,而且mDNS也无法解决DNS缓存问题。

DNS与mDNS都具备良好的容错能力,但缺乏服务健康检查和变化通知机制。

Zookeeper

Zookeeper提供分布式协调服务,在分布式系统中常被用于配置管理、名字服务、分布式锁及组管理,通常运行在一组节点上实现容错(当运行在n个节点上时能容忍n/2个节点同时故障)。

如何通过Zookeeper来实现服务发现?Zookeeper使用临时节点(ephemeral node)来实现服务注册和基本的健康检查功能。每当服务实例启动就会在Zookeeper中注册一个临时节点,而当服务实例故障或下线该临时节点会被Zookeeper自动删除,如果有其他服务依赖这个服务可以设置监听该服务实例对应的临时节点,当临时节点被删除时,依赖该服务的其他服务会获得通知。依赖Zookeeper自身的高可用及临时节点提供的健康检查和监听机制来实现具备容错能力的服务发现机制。

实际开发过程中建议使用Apache Curator来替代Zookeeper原生客户端库,Apache Curator通过封装Zookeeper原生API,提供更高抽象层次API让Zookeeper使用起来更加容易和可靠,而且提供专用于实现服务发现的API。

微服务架构中常用的解决方案,总结了传统服务发现方案

Etcd

Etcd是一个基于Raft共识算法具备线性强一致性(linearizable)的Key-Value存储系统,可以为每个Key设置TTL(time to live),当TTL过后相应Key会自动过期失效。基于Etcd构建服务发现解决方案将Etcd作为服务注册中心,服务实例注册就是在Etcd中构建一个Key-Value记录,由服务实例自身或代理负责设置并定期更新其关联Key的TTL,如果服务实例故障其对应Key就会在TTL之后过期失效,相当于将该故障服务实例注销,通过定时心跳以达到监控健康状态的效果。而且Etcd提供监听机制,允许为Key设置监听器当该Key发生变化时,监听器能及时获取通知。Etcd自身的高可用特性,基于TTL提供基本的服务健康检查,基于监听机制及时感知服务实例变化,使Etcd成为微服务架构中常用服务发现解决方案。

微服务架构中常用的解决方案,总结了传统服务发现方案

Consul

Consul是一个成熟的服务发现解决方案。其核心是一个基于Raft共识算法具备线性强一致性的Key-Value存储系统作为服务注册中心,并提供代理(Agent)机制一方面用于协调服务注册,一方面提供服务健康检查。代理(Agent)会在每个运行服务的节点上启动,获取节点地址并将该服务实例注册到服务注册中心。架构上Consul包括两类组件:Server、Agent,服务注册信息保存在Server上,通过Raft共识算法保证多个Server间数据线性强一致,保证服务注册中心高可用;将所有Agent作为集群节点,使用Gossip协议进行组关系管理和故障探测,当有Agent加入(启动)或离开(故障)集群时其他Agent会得到通知,实现服务健康检查和监视功能。

Gossip协议常用于集群组关系管理和故障检测,每个节点都通过一个或多个引导节点加入集群,引导节点有集群中所有节点列表,每个节点都从自己所知节点列表中随机选择一组节点周期性地发送多播消息,最终集群中所有节点都能知道其他节点。这个过程看起来很神奇,实际上Gossip协议能在几秒内将消息传遍有上百节点的集群。Akka、Riak、Cassandra都使用Gossip协议维护集群成员列表和故障探测。

微服务架构中常用的解决方案,总结了传统服务发现方案

此外Consul和Etcd都非常适合容器环境,因为Docker容器启动、停止都会发送事件(Event),基于事件通知机制非常便于将服务实例从Consul或Etcd上注册、注销。

总结

本文总结了传统服务发现方案如DNS、mDNS以及微服务架构中常用的解决方案,基于Zookeeper、Etcd、Consul框架方案核心思想是通过一组实例(3个或者5个)提供线性强一致性(Linearizable)分布式高可用Key-Value存储服务,将Key-Value存储作为服务注册中心,当相关Key发生变化时监视器能及时通知客户端,通知机制配合服务健康检查当有服务实例启动或故障时客户端能及时感知服务拓扑变化以实现智能路由,从实现方式上看它们可以看作是中心化的服务发现方案。

其实对于服务发现来说线性强一致性并不是唯一必须的,最终一致性在数据传播足够快的情况下一样能满足需求,实践中Gossip协议即使在大型集群也能快速传播数据并收敛到最终一致,将服务实例作为Gossip集群节点,使用CRDT(conflict-free replicated data type)存储服务注册信息通过Gossip快速传播实现集群中所有节点状态最终一致,每个节点都存储全部服务注册信息,这样就不需要单独的服务注册中心,这种方式实现的方案叫去中心化方案,有关去中心化服务发现方案留作下次分享。