微服务注册发现概述

本篇文章主要阐述当前注册发现的主要方案,以及作为一个注册中心,通常需要有哪些组成部分,这些组成部分各自承担什么职责,包含哪些功能。下面,我们首先看一看当前流行的注册发现方案。

注册发现主要方案

微服务的服务提供方以集群的方式来提供服务,所以会有多个服务实例供Consumer调用,那么必然就会有服务地址获取、健康检查、负载均衡问题,根据负载均衡方案的不同,流行的做法有以下三种:

  • 集中式LB
  • 进程内LB
  • 主机独立LB进程

    集中式LB

    #### 方案介绍
    这种方案我们非常熟悉,通常通过F5等硬件或者LVS,HAProxy,Nginx等软件来实现。这时候其实没有所谓的注册中心的,具体如下图所示:

服务地址获取:通常是有统一的域名或者虚拟IP,通过域名或者虚拟IP调用服务。在LB中进行配置,服务的地址通常是一个URL。
健康检查:LB组件通常自带健康检查功能。
负载均衡:LB组件通常也提供多种负载均衡策略。

集中式LB

优点

主要优点是简单,无论是Provider,还是Consumer,都无需关心服务的注册发现,一切都由LB搞定。

缺点

成也萧何,败也萧何。LB责任重大,所以LB同时也容易成为系统的瓶颈。

  • 所有的服务调用都会经过LB,所以LB出现性能问题或故障对于整个微服务系统的打击是毁灭性的。
  • 配置统一在LB进行,Provider的增加和删除也需要在LB手工进行配置,当微服务非常多的时候,配置是一项很繁琐并容易出错的工作。

进程内LB

为了避免集中式LB的不足,我们能不能化整为零呢?我们把服务的注册和发现分别放在了Provider和Consumer的进程内部来实现,两者信息的获取和同步都依赖于服务注册表(服务注册表通常是zookeeper、etcd、Eureka等)就是进程内LB方案。
Zookeeper和Eureka作为服务注册表还是有区别的,后续的文章再做详细的说明,感兴趣的同学也可以先看一下附录中的文章。

服务地址获取:通过注册中心,Provider向服务注册表注册服务,Consumer向服务注册表查询服务。通常,注册和查询都是通过一个服务名称,调用细节隐藏在Provider和Consumer内部。
健康检查:Provider发生变化时,更新服务注册表,Consumer从服务注册表获取到Provider的变化。
负载均衡:这种方案通常称为客户端负载均衡,因为Consumer从服务注册表获取到Provider实例列表,自己来选择合适的实例进行调用。

进程内LB

优点

  • Consumer调用Provider是直接调用,没有额外开销;服务注册表出现问题时,只影响到实例列表的获取和更新,并不会影响到服务的调用
  • 服务的注册无需手工进行配置,由provider自动完成

缺点

  • 该方案的Provider和Consumer均需要依赖SDK,如果企业内有多种语言栈,SDK的开发成本较高
  • SDK发布以后,如要升级,需要服务调用方修改代码并发布,当服务调用方很多时代价很高,并且容易出现兼容性问题。

需要说明的是,阿里的服务框架Dubbo使用的是类似机制。Netflix OSS同样使用类似机制,其中Eureka对应服务注册表,Ribbon对应客户端负载均衡。

主机独立LB进程

该方案是针对第二种方案的不足而提出的一种折中方案,原理和进程内LB类似,不同之处是,他将LB和服务发现功能从进程内移出来,变成主机上的一个独立进程,主机上的一个或者多个服务要访问目标服务时,他们都通过同一主机上的独立LB进程做服务发现和负载均衡。

Alt text

由于此种方案的部署非常负载,不符合我司的使用场景,所以我对于此种方案并没有过多研究,感兴趣的同学可以参考附录中的文章,其中有相对详细的介绍。

注册中心的主要功能

由于Eureka,Dubbo等都使用了进程内LB的方案,所以我们主要对该方案进行分析说明。
首先,我们回顾一下进程内LB方案:

进程内LB

整个系统中,有三种角色:

  1. 首先,有一个服务注册表,提供注册和查询服务
  2. Provider向服务注册表注册服务
  3. Consumer从服务注册表查询到Provider的服务实例,并且选择实例进行直接调用

下面我们分别对每种角色所需要具备的功能进行讨论。

服务注册表

对于微服务来说,服务注册表就是一个“电话本”,每个服务端在“电话本”中记录住址(主机、端口、节点名称)等其他服务调用方所需的信息。客户端在“电话本”中查找服务的结构(是否存在可用服务,如果有,在哪里?)以及服务的容量(是否能同时接受x,y,z的请求)。
在这三者当中,服务注册表承担了协调的工作,因此的责最重。一个完善的服务注册表需要至少需要解决以下问题:

  1. 数据如何存储。可以保持在内存、本地文件、数据库等等
  2. 如何提供注册服务。Http、Rpc等等
  3. 如何提供查询服务。
  4. Provider变化时,如何通知Consumer。采用推的方式还是拉的方式,实时还是定时等
  5. 服务注册表的节点之间如何进行信息的同步和复制
  6. 当所有的服务注册表节点都Down掉后重新启动,如何重新获取注册信息。从数据库中重新加载,从缓存中读取,还是需要Provider发送心跳时重新保存

Service Provider

  1. 启动时向服务注册表发送注册信息
  2. 关闭时向服务注册表发送取消信息
  3. 保持心跳,使得服务注册表能够获取Provider的最新情况

Consumer

  1. 通过服务注册表查询Provider的实例
  2. 客户端负载均衡
  3. 失败重试
  4. 客户端缓存

下篇文章中,我们将一起先使用Zookeeper实现一个最朴素的注册中心;接下来,看看Eureka和Ribbon是怎么实现的。

参考文献

  1. 实施微服务,我们需要哪些基础框架?
  2. 为什么不应该使用ZooKeeper做服务发现