服务注册中心

最后更新:2020-05-05

对于任何一个微服务,原则上都应存在或者支持多个提供者,这是由微服务的分布式属性决定的。更进一步,为了支持弹性扩缩容特性,一个微服务的提供者的数量和分布往往是动态变化的,也是无法预先确定的。因此,原本在单体应用阶段常用的静态LB机制就不再适用了,需要引入额外的组件来管理微服务提供者的注册与发现,而这个组件就是服务注册中心。

1. 服务注册中心

服务注册中心主要是解决什么问题:

  • 服务注册: 实例将自身服务信息注册到注册中心,这部分信息包括服务的主机 IP 和服务的 Port,以及暴露服务自身状态和访问协议信息等。
  • 服务发现: 实例请求注册中心所依赖的服务信息,服务实例通过注册中心,获取到注册到其中的服务实例的信息,通过这些信息去请求它们提供的服务。

首先,在服务启动时,服务提供者会向注册中心注册服务,暴露自己的地址和端口等,注册中心会更新服务列表。服务消费者启动时会向注册中心请求可用的服务地址,并且在本地缓存一份提供者列表,这样在注册中心宕机时仍然可以正常调用服务。

如果提供者集群发生变更,注册中心会将变更推送给服务消费者,更新可用的服务地址列表。

除了服务注册和发现之外,对于注册中心而言,我们还需要其具备高可用。因为为了确保服务高可用性,也需要注册中心本身不能宕机。通过构建集群机制,服务只要连接集群中的任何一台注册中心服务器完成服务注册和发现即可,单台服务器的宕机不影响服务调用的正常进行。

对于服务注册中心而言,在实现上的主要难度在于在服务提供者实例状态发生变更时如何同步到服务的消费者。

从架构设计上讲,状态变更管理可以采用发布-订阅模式,体现在服务提供者可以根据服务定义发布服务,而服务消费者则通过对自己感兴趣的服务进行订阅并获取包括服务地址在内的各项元数据。发布-订阅功能还体现在状态变更推送,即当注册中心服务定义发生变化时,主动推送变更到该服务的消费者。

基于发布-订阅设计思想,就诞生了一种服务监听机制。服务监听机制确保服务消费者能够实时监控服务更新状态,是一种被动接收变更通知的实现方案,通常采用监听器以及回调机制。

另外一种确保状态信息同步的方式是采用轮询机制。轮询机制是一种主动拉取策略,即服务的消费者定期调用注册中心提供的服务获取接口获取最新的服务列表并更新本地缓存,

2. 为什么不使用 DNS?

如果我们用 DNS 来实现服务发现,所有的服务提供者节点都配置在了同一个域名下,调用方的确可以通过 DNS 拿到随机的一个服务提供者的 IP,并与之建立长连接,这看上去并没有太大问题,但在我们业界为什么很少用到这种方案呢? 现在可以思考这样两个问题:

  • 如果这个 IP 端口下线了,服务调用者能否及时摘除服务节点呢?
  • 如果在之前已经上线了一部分服务节点,这时我突然对这个服务进行扩容,那么新上线的服务节点能否及时接收到流量呢?

这两个问题的答案都是:“不能”。

这两个问题的答案都是:“不能”。这是因为为了提升性能和减少 DNS 服务的压力,DNS 采取了多级缓存机制。

dns轮询只能扩展性能,不能保证高可用

3. 实现

了解了注册中心的原理之后,我们来谈谈注册中心的实现。

3.1. Zookeeper

Zookeeper 用来做服务注册中心,主要是因为它具有节点变更通知功能,只要客户端监听相关服务节点,服务节点的所有变更,都能及时的通知到监听客户端,这样作为调用方只要使用 Zookeeper 的客户端就能实现服务节点的订阅和变更通知功能了,非常方便。

Zookeeper 保证 CP,即任何时刻对 Zookeeper 的访问请求能得到一致性的数据结果,同时系统对网络分割具备容错性,但是它不能保证每次服务的可用性。在使用 Zookeeper 获取服务列表时,如果 ZK 正在选举或者 ZK 集群中半数以上的机器不可用,那么将无法获取数据。所以说,ZK 不能保证服务可用性。

3.2. Eureka

Eureka 保证 AP,Eureka 在设计时优先保证可用性,每一个节点都是平等的。

一部分节点挂掉不会影响到正常节点的工作,不会出现类似 ZK 的选举 Leader 的过程,客户端发现向某个节点注册或连接失败,会自动切换到其他的节点。

只要有一台 Eureka 存在,就可以保证整个服务处在可用状态,只不过有可能这个服务上的信息并不是最新的信息。

数据不一致性在注册服务中会给 Eureka 带来的问题,无非就是某一个节点被注册的服务多,某个节点注册的服务少,在某一个瞬间可能导致某些 IP 节点被调用数多,某些 IP 节点调用数少的问题。也有可能存在一些本应该被删除而没被删除的脏数据。

Euerka 2.0 已经闭源

3.3. Consul

如果要基于Consul作为服务注册中心,那么首先必须在每个服务所在的机器上部署一个Consul Agent,作为一个服务所在机器的代理。

然后还得在多台机器上部署Consul Server,这就是核心的服务注册中心。

这个Consul Agent可以用来收集你的服务信息然后发送给Consul Server,还会对你的服务不停的发送请求检查他是否健康。

然后你要发现别的服务的时候,Consul Agent也会帮你转发请求给Consul Server,查询其他服务所在机器。

Consul Server一般要求部署3~5台机器,以保证高可用以及数据一致性。

他们之间会自动实现数据同步,而且Consul Server集群会自动选举出一台机器作为leader,其他的Consul Server就是follower。

Consul基于Raft协议来实现CP,解决一致性问题

我在工作中选用了consul作为服务主从中心

  • 最小化对已有应用的侵入性,这也是贯穿我们整个微服务化改造的原则之一
  • 降低运维的复杂度,Consul Agent既可以运行在服务器模式,又可以运行在客户端模式

网上存在如下一个技术对比表格,可以参考一下

4. 健康检查

zookeeper、Eureka采用集中式的心跳机制,是让各个服务都必须每隔一定时间发送心跳到Eureka Server。如果一段时间没收到心跳,那么就认为这个服务宕机了。

但是这种集中式的心跳机制会对Eureka Server造成较大的心跳请求压力,实际上平时Eureka Server接收最多的请求之一就是成千上万服务发送过来的心跳请求。

Consul基于Agent实现的分布式健康检查机制,可以大幅度的减小Server端的压力。每个机器上的Consul Agent会不断的发送请求检查服务是否健康,是否宕机。如果服务宕机了,那么就会通知Consul Server。

在使用spring-cloud-consul实现服务注册时,发现无法保证注册中心能够将不可用的或者下线的服务节点信息从注册中心摘除。(服务为不可用,但是一直存在)

当前大多开源实现使用的是 shutdownHook 来完成,当服务进程推出时,从注册中心删除相关的服务节点信息的工作。了解 shutdownHook 应该知道,依赖 shutdownHook 是不靠谱的。如果进程被 Kill -9,或者进程异常退出等都不会触发shutdownHook的执行,也就是说会导致服务注册信息残留在注册中心。所以还需要其他方式来保证注册中心能够将不可用的或者下线的服务节点信息从注册中心摘除。

为了解决服务节点摘除问题,需要引入第三方探测节点,来探测当前服务节点是否可用,如果不可用可以直接修改注册中心服务节点状态信息,或者直接删除服务节点的注册中心,另外在服务节点重新可用时,还需要重新将服务节点状态信息更新,或者重新写入服务节点信息

5. 平滑发布

当服务提供方机器需要发布时,这时如果直接发布,重启了服务提供方的服务节点,重启时注册中心服务节点信息状态会变成不可用或者直接删除了,服务调用方会收到注册中心推送的信息,这时候才禁止流量到刚发布的那台机器,其实已经晚了,已经有些请求不能正常处理了。

所以需要提前从注册中心做状态变更或者删除服务提供方节点信息,而将此过程直接加入到发布流程是最合理的,也就是发布时,优先从注册中心摘除服务节点或者变更服务节点状态信息,目的是提前告诉服务调用方,暂时先不要将流量发往该台机器,达到发布过程中流量平滑的目的。

6. Metadata

服务注册的时候除了携带serviceName、ip、port这些信息就足够了呢?在一个大型为微服务系统中,服务支持的协议、服务的标签(比如Abtest、蓝绿发布的时候需要筛选这些tag作为服务路由信息)、服务的健康状态、服务的调度权重等信息可能都需要传递给消费者感知到。不过在生产实践中,一般不推荐将过多的信息放入注册中心,以免导致性能下降,比如swagger生成的api信息最好单独存储。

7.一致性对比

在讨论分布式系统时,一致性是一个绕不开的话题,在服务发现中也是一样。CP 模型优先保证一致性,可能导致注册中心可用性降低,AP 模型优先保证可用性,可能出现服务错误。

CP 架构:对于 CP 来说,放弃可用性,追求一致性和分区容错性。

Zookeeper 保证 CP,即任何时刻对 Zookeeper 的访问请求能得到一致性的数据结果,同时系统对网络分割具备容错性,但是它不能保证每次服务的可用性。

从实际情况来分析,在使用 Zookeeper 获取服务列表时,如果 ZK 正在选举或者 ZK 集群中半数以上的机器不可用,那么将无法获取数据。所以说,ZK 不能保证服务可用性

AP 架构:对于 AP 来说,放弃强一致性,追求分区容错性和可用性,这是很多分布式系统设计时的选择,后面的 Base 也是根据 AP 来扩展的。

Eureka 保证 AP,Eureka 在设计时优先保证可用性,每一个节点都是平等的。

一部分节点挂掉不会影响到正常节点的工作,不会出现类似 ZK 的选举 Leader 的过程,客户端发现向某个节点注册或连接失败,会自动切换到其他的节点

只要有一台 Eureka 存在,就可以保证整个服务处在可用状态,只不过有可能这个服务上的信息并不是最新的信息。

对于服务注册和发现场景来说,一般认为,可用性比数据一致性更加重要。针对同一个服务,即使注册中心的不同节点保存的服务提供者信息不相同,会出现部分提供者地址不存在等,不会导致严重的服务不可用。对于服务消费者来说,能消费才是最重要的,拿到可能不正确的服务实例信息后尝试消费,也要比因为无法获取实例信息而拒绝服务好。

Edgar

Edgar
一个略懂Java的小菜比