摘要

本博文将详细介绍分布式注册中心etcd在dubbo的实践。

一、Etcd的概念

ETCD 是一个高可用的分布式键值数据库,可用于服务发现。ETCD 采用raft 一致性算法,基于 Go语言实现。etcd作为一个高可用键值存储系统,天生就是为集群化而设计的。由于Raft算法在做决策时需要多数节点的投票,所以etcd一般部署集群推荐奇数个节点,推荐的数量为3、5或者7个节点构成一个集群。

二、Etcd的架构

Dubbo——注册中心(etcd)原理_dubbo

三、Etcd数据结构

etcd所有元数据信息都是基于key-value (键值对)存储的,和ZooKeeper中节点和子节点不同,etcd存储是通过前缀区分的。

在ZooKeeper中有临时节点的概念,它是通过TCP连接状态断开自动删除临时节点数据的。etcd注册中心也有临时节点的概念,但不是根据TCP连接状态,而是根据租约到期自动删除对应的key实现的。当provider和consumer上线时,会自动向注册中心写临时节点。可能有读者会有疑问,通过租约到期删除key会不会不可靠?当JVM关闭时,Dubbo会触发优雅停机逻辑,也会及时删除临时key,所以问题不大。

接口子目录存储为key-value格式

Dubbo——注册中心(etcd)原理_推送_02

临时节点存储为key-value格式

Dubbo——注册中心(etcd)原理_dubbo_03

+- /dubbo
| +- com.alibaba.service.HelloService
| | \- providers
| | +- protocol://ip:port/HelloService?timeout=1000
| | +- protocol://ip:port/HelloService?timeout=2000
| | \- consumers
| | +- protocol://ip:port/HelloService?timeout=2000
! | +- protocol://ip:port/HelloService?timeout=1000
| | \- routers
| | \- configurators

四、Etcd的特点

实际上,etcd作为一个受到Zookeeper与doozer启发而催生的项目,除了拥有与之类似的功能外,更具有以下4个特点:

  • 简单:基于HTTP+JSON的API让你用curl命令就可以轻松使用。
  • 安全:可选SSL客户认证机制。
  • 快速:每个实例每秒支持一千次写操作。
  • 可信:使用Raft算法充分实现了分布式。

Dubbo默认支持ZooKeeper和Redis等注册中心实现,但是生产环境中使用较多的还是ZooKeepero后起之秀etcd广泛应用于Kubernates中(用于服务发现),经过了生产环境的考验。相比于ZooKeeper实现,基于etcd实现的注册中心有很多优点,例如:不需要每次子节点变更都重新全量拉取节点数据,大大降低了网络的压力。

  • etcd使用增量快照,可以避免在创建快照时暂停。
  • 数据持久化。etcd默认数据一更新就进行持久化。
  • 安全性高,etcd支持SSL客户端安全认证。
  • etcd使用堆外存储,没有垃圾收集暂停功能。
  • etcd己经在微服务Kubernates领域中有大量生产实践,其稳定性经得起考验。
  • 基于etcd实现服务发现时,不需要每次感知服务进行全量拉取,降低了网络冲击。
  • etcd具备更简单的运维和使用特性,基于Go开发更轻量。
  • etcd的watch可以一直存在。
  • ZooKeeper会丢失一些旧的事件,etcd设计了一个滑动窗口来保存一段时间内的事件,客户端重新连接上就不会丢失事件了。
  • etcd支持很多跨语言客户端直接通信。,而ZooKeeper官方只提供了Java和C两种语言的接口。

五、Etcd相关名词

  • Raft:etcd所采用的保证分布式系统强一致性的算法。
  • Node:一个Raft状态机实例。
  • Member: 一个etcd实例。它管理着一个Node,并且可以为客户端请求提供服务。
  • Cluster:由多个Member构成可以协同工作的etcd集群。
  • Peer:对同一个etcd集群中另外一个Member的称呼。
  • Client: 向etcd集群发送HTTP请求的客户端。
  • WAL:预写式日志,etcd用于持久化存储的日志格式。
  • snapshot:etcd防止WAL文件过多而设置的快照,存储etcd数据状态。
  • Proxy:etcd的一种模式,为etcd集群提供反向代理服务。
  • Leader:Raft算法中通过竞选而产生的处理所有数据提交的节点。
  • Follower:竞选失败的节点作为Raft中的从属节点,为算法提供强一致性保证。
  • Candidate:当Follower超过一定时间接收不到Leader的心跳时转变为Candidate开始Leader竞选。
  • Term:某个节点成为Leader到下一次竞选开始的时间周期,称为一个Term。
  • Index:数据项编号。Raft中通过Term和Index来定位数据。

六、Etcd应用场景

6.1 服务发现:

服务发现(Service Discovery)要解决的是分布式系统中最常见的问题之一,即在同一个分布式集群中的进程或服务如何才能找到对方并建立连接。从本质上说,服务发现就是想要了解集群中是否有进程在监听udp或tcp端口,并且通过名字就可以进行查找和连接。

6.2 消息发布与订阅:

在分布式系统中,最为适用的组件间通信方式是消息发布与订阅机制。具体而言,即构建一个配置共享中心,数据提供者在这个配置中心发布消息,而消息使用者则订阅他们关心的主题,一旦相关主题有消息发布,就会实时通知订阅者。通过这种方式可以实现分布式系统配置的集中式管理与实时动态更新

  • 应用中用到的一些配置信息存放在etcd上进行集中管理。这类场景的使用方式通常是这样的:应用在启动的时候主动从etcd获取一次配置信息,同时,在etcd节点上注册一个Watcher并等待,以后每次配置有更新的时候,etcd都会实时通知订阅者,以此达到获取最新配置信息的目的。
  • 分布式搜索服务中,索引的元信息和服务器集群机器的节点状态信息存放在etcd中,供各个客户端订阅使用。使用etcd的​​key TTL​​功能可以确保机器状态是实时更新的。
  • 分布式日志收集系统。这个系统的核心工作是收集分布在不同机器上的日志。收集器通常按照应用(或主题)来分配收集任务单元,因此可以在etcd上创建一个以应用(或主题)命名的目录P,并将这个应用(或主题)相关的所有机器ip,以子目录的形式存储在目录P下,然后设置一个递归的etcd Watcher,递归式地监控应用(或主题)目录下所有信息的变动。这样就实现了在机器IP(消息)发生变动时,能够实时通知收集器调整任务分配。
  • 系统中信息需要动态自动获取与人工干预修改信息请求内容的情况。通常的解决方案是对外暴露接口,例如JMX接口,来获取一些运行时的信息或提交修改的请求。而引入etcd之后,只需要将这些信息存放到指定的etcd目录中,即可通过HTTP接口直接被外部访问。

6.3 负载均衡:

在分布式系统中,为了保证服务的高可用以及数据的一致性,通常都会把数据和服务部署多份,以此达到对等服务,即使其中的某一个服务失效了,也不影响使用。这样的实现虽然会导致一定程度上数据写入性能的下降,但是却能实现数据访问时的负载均衡。因为每个对等服务节点上都存有完整的数据,所以用户的访问流量就可以分流到不同的机器上。

  • etcd本身分布式架构存储的信息访问支持负载均衡。etcd集群化以后,每个etcd的核心节点都可以处理用户的请求。所以,把数据量小但是访问频繁的消息数据直接存储到etcd中也是个不错的选择,如业务系统中常用的二级代码表。二级代码表的工作过程一般是这样,在表中存储代码,在etcd中存储代码所代表的具体含义,业务系统调用查表的过程,就需要查找表中代码的含义。所以如果把二级代码表中的小量数据存储到etcd中,不仅方便修改,也易于大量访问。
  • 利用etcd维护一个负载均衡节点表。etcd可以监控一个集群中多个节点的状态,当有一个请求发过来后,可以轮询式地把请求转发给存活着的多个节点。类似KafkaMQ,通过Zookeeper来维护生产者和消费者的负载均衡。同样也可以用etcd来做Zookeeper的工作。

6.4 分布式通知与协调:

这里讨论的分布式通知与协调,与消息发布和订阅有些相似。两者都使用了etcd中的Watcher机制,通过注册与异步通知机制,实现分布式环境下不同系统之间的通知与协调,从而对数据变更进行实时处理。实现方式通常为:不同系统都在etcd上对同一个目录进行注册,同时设置Watcher监控该目录的变化(如果对子目录的变化也有需要,可以设置成递归模式),当某个系统更新了etcd的目录,那么设置了Watcher的系统就会收到通知,并作出相应处理。

  • 通过etcd进行低耦合的心跳检测。检测系统和被检测系统通过etcd上某个目录关联而非直接关联起来,这样可以大大减少系统的耦合性。
  • 通过etcd完成系统调度。某系统有控制台和推送系统两部分组成,控制台的职责是控制推送系统进行相应的推送工作。管理人员在控制台做的一些操作,实际上只需要修改etcd上某些目录节点的状态,而etcd就会自动把这些变化通知给注册了Watcher的推送系统客户端,推送系统再做出相应的推送任务。
  • 通过etcd完成工作汇报。大部分类似的任务分发系统,子任务启动后,到etcd来注册一个临时工作目录,并且定时将自己的进度进行汇报(将进度写入到这个临时目录),这样任务管理者就能够实时知道任务进度。

6.5 分布式锁:

因为etcd使用Raft算法保持了数据的强一致性,某次操作存储到集群中的值必然是全局一致的,所以很容易实现分布式锁。锁服务有两种使用方式,一是保持独占,二是控制时序。

  • 保持独占,即所有试图获取锁的用户最终只有一个可以得到。etcd为此提供了一套实现分布式锁原子操作CAS(​​CompareAndSwap​​​)的API。通过设置​​prevExist​​值,可以保证在多个节点同时创建某个目录时,只有一个成功,而该用户即可认为是获得了锁。
  • 控制时序,即所有试图获取锁的用户都会进入等待队列,获得锁的顺序是全局唯一的,同时决定了队列执行顺序。etcd为此也提供了一套API(自动创建有序键),对一个目录建值时指定为​​POST​​动作,这样etcd会自动在目录下生成一个当前最大的值为键,存储这个新的值(客户端编号)。同时还可以使用API按顺序列出所有当前目录下的键值。此时这些键的值就是客户端的时序,而这些键中存储的值可以是代表客户端的编号。

七、Dubbo中ETCD

Dubbo——注册中心(etcd)原理_dubbo_04

7.1 临时节点的创建

类似zkClient实现,失败时是允许重试的,默认策略是失败最多重试1次,每次重试休眠I秒,防止出现对etcd服务端的冲击,

客户端正确初始化才会触发网络调用。

用户配置的session作为keep-alive时间,默认是30秒。在实现租约保活时,做了一次优化,对同一个应用临时节点做了租约复用。因为每次租约保活会触发一次gRPC调用,租约复用机制大大降低了 stream数量占用,同时降低了 etcd集群内存使用etcd要求提供一个时间间隔,服务端会返回唯一标识来代表这个租约,需要线程池定期续租。

jetcd触发续租逻辑的主要原理是通过2个线程池处理的,第1个线程池定期刷新TTL保持存活,第2个线程池定期检测本地是否过期。

在jetcd 0.3.0之后提供了 keep-alive回调机制,允许保活失败执行开发者的自定义逻辑。

会构造key-value键值对,键对应Dubbo服务元数据,值是租约。写值时自动关联租约,如果provider节点宕机无法续租,则etcd服务端会自动将节点清除,因此无须担心有垃圾数据的问题。

7.2 获取直接子节点

因为etcd是平铺的key-value。这里有两种解决办法,

第一种是用key对应的value存储所有子节点的值,

  • 如果采用第一种方法,则不可避免地又回到了 ZooKeeper的数据结构,每次单个provider ±线都会触发所有客户端进行拉取,对网络资源消耗较大。

第二种是把所有元数据作为key,值实际上不存储有意义的元素。

  • 将元数据作为key,并且使用特定的前缀进行区分,比如服务提供者会采用 /dubbo/com.alibaba.demo.HelloService/providers 作为前缀,针对每个接口com. alibaba. demo. HelloService都采用这个前缀进行匹配。

7.3 失败重试策略

Dubbo——注册中心(etcd)原理_数据_05

为了能够处理所有与etcd server进行的交互操作,支持失败重试,这里抽象了 RetryLoop类,这个类主要持有Callable实例用于执行回调函数或真实逻辑,另外一个实例就是重试策略。是否重试应该交给具体的策略逻辑来判断,比如最大重试几次,每次重试是否需要“sleep”等,易变的逻辑抽象成一个接口是合适的。RetryNtimes代表具体的重试策略,最大重试N次,每次重试都会进行“sleep”来指定时间。

7.4 扩展 FailbackRegistry 实现

实现具体注册中心时,我们应该最大复用现有注册中心的功能,比如注册失败和订阅失败应该重试,当注册中心“挂掉”后,应用启动应该自动使用cache文件等。这些功能现有的Dubbo框架巳经给我们提供好了,因此我们的注册中心只需要继承FailbackRegistry类并重写具体订阅和注册等逻辑即可。

扩展注册中心时,我们主要的聚焦点是实现doSubscribe> doUnsubscribe> doRegister和doUnregistero这四个方法的功能是支持订阅、取消订阅、注册服务元数据和取消注册服务元数据。在实现数据订阅时,我们必须要兼容服务治理平台(dubbo-admin),服务治理平台启动时接口会传递星号(*),代表订阅所有接口数据。因此在处理服务治理场景中必须递归拉取所有接口,然后订阅接口中包含的服务数据或配置

八、Etcd搭建集群

博文参考