1.ETCD概述
1.1 ETCD概述
etcd是一个高可用的分布式的键值对存储系统,常用做配置共享和服务发现。由CoreOS公司发起的一个开源项目,受到ZooKeeper与doozer启发而催生的项目,名称etcd源自两个想法,即Linux的/etc文件夹和d分布式系统。/etc文件夹是用于存储单个系统的配置数据的地方,而etcd用于存储大规模分布式的配置信息,具有以下特点:
- 简单:基于HTTP+JSON的API,用curl就可以轻松使用
- 可信:使用Raft算法充分实现了分布式
- 安全:可选SSL客户认证机制
- 快速:每个节点可支持上万QPS读写
1.2 ETCD工作原理
etcd集群本身是一个分布式系统,由多个节点相互通信构成整体对外服务,每个节点都存储了完整的数据,并且通过Raft协议保证每个节点维护的数据是一致的,在ETCD集群中任意时刻最多存在一个有效的主节点,由主节点处理所有来自客户端写操作,通过Raft协议保证写操作对状态机的改动会可靠的同步到其他节点,Raft协议如下所示:
1.2.1 选举
Raft协议是用于维护一组服务节点数据一致性的协议。这一组服务节点构成一个集群,并且有一个主节点来对外提供服务。当集群初始化,或者主节点挂掉后,面临一个选举问题。集群中每个节点,任意时刻处于Leader(领导者)、Follower(追随者)、Candidate(候选者)这三个角色之一,选举特点如下:
- 当集群初始化时候,每个节点都是Follower角色
- 集群中存在最多1个有效的主节点,通过心跳与其他节点同步数据
- 当Follower在一定时间内没有收到来自主节点的心跳,会将自己角色改变为Candidate,并发起一次选举投票
- 当收到包括自己在内超过半数节点赞成后,选举成功
- 当收到票数不足半数选举失败,或者选举超时
- 若本轮未选出主节点,将进行下一轮选举(出现这种情况,是由于多个节点同时选举,所有节点均为获得过半选票) - Candidate节点收到来自主节点的信息后,会立即终止选举过程,进入Follower角色
为了避免陷入选举失败循环,每个节点未收到心跳发起选举的时间是一定范围内的随机值,这样能够避免2个节点同时发起选举。
示意图如下所示:
1.2.2 复制日志
日志复制是指主节点将每次操作形成日志条目,并持久化到本地磁盘,然后通过网络IO发送给其他节点。其他节点根据日志的逻辑时钟(TERM)和日志编号(INDEX)来判断是否将该日志记录持久化到本地。当主节点收到包括自己在内超过半数节点成功返回,那么认为该日志是可提交的(committed),并将日志输入到状态机,将结果返回给客户端。
主节点通过网络IO向其他节点追加日志。若某节点收到日志追加的消息,首先判断该日志的TERM是否过期,以及该日志条目的INDEX是否比当前以及提交的日志的INDEX跟早。若已过期,或者比提交的日志更早,那么就拒绝追加,并返回该节点当前的已提交的日志的编号。否则将日志追加,并返回成功。
当主节点收到其他节点关于日志追加的回复后,若发现有拒绝,则根据该节点返回的已提交日志编号,发送其编号下一条日志
主节点向其他节点同步日志,还作了拥塞控制。主节点发现日志复制的目标节点拒绝了某次日志追加消息,将进入日志探测阶段,一条一条发送日志,直到目标节点接受日志,然后进入快速复制阶段,可进行批量日志追加。
按照日志复制的逻辑,我们可以看到,集群中慢节点不影响整个集群的性能。另外一个特点是,数据只从主节点复制到Follower节点,这样大大简化了逻辑流程。Raft日志复制路程如下图所示:
1.2.3 安全
选举和复制日志并不能保证节点间数据一致。当一个某个节点挂掉了,一段时间后再次重启,并刚好当选为主节点。而在其挂掉这段时间内,集群若有超过半数节点存活,集群会正常工作,那么会有日志提交,这些提交的日志无法传递给挂掉的节点。当挂掉的节点再次当选举节点,它将缺失部分已提交的日志。在这样场景下,按Raft协议,它将自己日志复制给其他节点,会将集群已经提交的日志给覆盖掉,这显然是不可接受的,对于出现这种问题解决办法:
- 其他协议解决这个问题的办法是,新当选的主节点会询问其他节点,和自己数据对比,确定出集群已提交数据,然后将缺失的数据同步过来。这个方案有明显缺陷,增加了集群恢复服务的时间(集群在选举阶段不可服务),并且增加了协议的复杂度。
- Raft解决的办法是,在选举逻辑中,对能够成为主节点加以限制,确保选出的节点已定包含了集群已经提交的所有日志。如果新选出的主节点已经包含了集群所有提交的日志,那就不需要从和其他节点比对数据了,简化了流程,缩短了集群恢复服务的时间
为什么只要仍然有超过半数节点存活,一定能够选出包含所有日志数据的节点作为主节点呢?因为已经提交的日志必然被集群中超过半数节点持久化,显然前一个主节点提交的最后一条日志也被集群中大部分节点持久化。当主节点挂掉后,集群中仍有大部分节点存活,那这存活的节点中一定存在一个节点包含了已经提交的日志了,因此要求etcd集群节点数量为奇数(3,5,7,9……)
1.3 ETCD应用场景
ETCD服务发现示意图如下图所示:
服务发现是分布式系统中最常见的需要解决的问题之一,即在同一个分布式集群中的进程或服务,客户端通过名字就可以查找和连接服务端。要解决服务发现的问题,需要有下面三点:
- 一个强一致性、高可用的服务存储目录。基于Raft算法的etcd天生就是这样一个强一致性高可用的服务存储目录
- 一种注册服务和监控服务健康状态的机制。用户可以在etcd中注册服务,并且对注册的服务设置key TTL,定时保持服务的心跳以达到监控健康状态的效果
- 一种查找和连接服务的机制。通过在etcd指定的主题快速找到服务地址
1.3.1 服务发现
1.3.1.1 在微服务中使用etcd服务发现
随着Docker容器的流行,多种微服务共同协作,构成一个相对功能强大的组织架构。使用etcd服务发现机制,在etcd中注册某个服务名字的目录,在该目录下存储可用的服务节点的IP。服务使用者从etcd目录下查找可用的服务节点IP来连接和调用,达到透明化的动态添加这些服务目的,示意图如下图所示:
1.3.1.2 在PaaS平台中使用etcd服务发现
PaaS平台中的应用一般都有多个实例,通过域名不仅可以透明的对多个实例进行访问,而且还可以做到负载均衡。但是应用的某个实例随时都有可能故障重启,这时就需要动态的配置域名解析(路由)信息,通过etcd的服务发现功能就可以轻松解决这个动态配置的问题,实现多实例与实例故障重启透明化目的,示意图如下图所示:
1.3.2 发布订阅消息
etcd的发布订阅消息示意图如下图所示:
在分布式系统中,消息发布与订阅最适合使用在组件之间通信。使用etcd发布订阅功能可以实现一个配置共享中心,数据提供者在配置中心发布消息,消息消费者订阅他们关心的主题,一旦主题有新消息发布,就会实时通知订阅者,通过这种方式可以做到分布式系统配置的集中式管理与动态更新。
etcd发布订阅最典型应用在kubernetes上,其他场景应用:
- app或服务用到的一些配置信息放到etcd上进行集中管理。在启动的时候主动从etcd获取一次配置信息,在etcd节点上注册一个Watcher并等待,以后每次配置有更新的时候,etcd都会实时通知订阅者,以此达到获取最新配置信息的目的。
- 分布式搜索服务中,索引的元信息和服务器集群机器的节点状态存放在etcd中,供各个客户端订阅使用。使用etcd的key TTL功能可以确保机器状态是实时更新的。
- 分布式日志收集系统。 这个系统的核心工作是收集分布在不同机器的日志。收集器通常是按照应用(或主题)来分配收集任务单元,因此可以在etcd上创建一个以应用(主题)命名的目录,并将这个应用(主题相关)的所有机器ip,以子目录的形式存储到目录上,然后设置一个etcd递归的Watcher,递归式的监控应用(主题)目录下所有信息的变动。这样就实现了机器IP(消息)变动的时候,能够实时通知到收集器调整任务分配
- 系统中信息需要动态自动获取与人工干预修改信息请求内容的情况。只需要要将信息存放到指定的etcd目录中,etcd的这些目录就可以通过HTTP的接口在外部访问
1.3.3 负载均衡
etcd的负载均衡示意图如下图所示:
etcd本身分布式架构存储的信息访问支持负载均衡,etcd集群化以后,每个etcd的核心节点都可以处理用户的请求。所以把数据量小但是访问频繁的消息数据直接存储到etcd中也是个不错的选择。 etcd可以监控一个集群中多个节点的状态,利用etcd维护一个负载均衡节点表,当有一个请求发过来后,可以轮询式的把请求转发给存活着的节点。
分布式系统中,为了保证服务的高可用以及数据的一致性,通常都会把数据和服务部署多份,以此达到对等服务,即使其中的某一个服务失效了,也不影响使用。由此带来的坏处是数据写入性能下降,而好处则是数据访问时的负载均衡。因为每个对等服务节点上都存有完整的数据,所以用户的访问流量就可以分流到不同的机器上。
1.3.4 分布式通知与协调
分布式通知与协调,与消息发布和订阅有些相似。都用到了etcd中Watche机制,通过注册与异步通知机制,实现分布式环境下不同系统之间 的通知与协调,从而对数据变更做到实时处理。实现方式:不同系统都在etcd上对同一个目录进行注册,同时设置Watcher观测该目录的变化(如果对子目录的变化也有需要,可以设置递归模式),当某个系统更新了etcd的目录,那么设置了Watcher的系统就会收到通知,并作出相应处理。其工作原理如下所示:
- 通过etcd进行低耦合的心跳检测:检测系统和被检测系统通过etcd上某个目录关联而非直接关联起来,这样可以大大减少系统的耦合性
- 通过etcd完成系统调度:某系统有控制台和推送系统两部分组成,控制台的职责是控制推送系统进行相应的推送工作。管理人员在控制台作的一些操作,实际上是修改了etcd上某些目录节点的状态,而etcd就把这些变化通知给注册了Watcher的推送系统客户端,推送系统再作出相应的推送任务。
- 通过etcd完成工作汇报:大部分类似的任务分发系统,子任务启动后,到etcd来注册一个临时工作目录,并且定时将自己的进度进行汇报(将进度写入到这个临时目录),这样任务管理者就能够实时知道任务进度
1.3.5 分布式锁
因为etcd使用Raft算法保持了数据的强一致性,某次操作存储到集群中的值必然是全局一致的,所以很容易实现分布式锁,锁有两种使用方式:
- 保持独占:即所有获取锁的用户最终只有一个可以得到
etcd为此提供了一套实现分布式锁原子操作CAS(CompareAndSwap)的API。通过设置prevExist值,可以保证在多个节点同时去创建某个目录时只有一个成功,而创建成功的用户就可以认为是获得了锁。
- 控制时序:即所有想要获得锁的用户都会被安排执行,但是获得锁的顺序也是全局唯一的,同时决定了执行顺序
etcd为此也提供了一套API(自动创建有序键),对一个目录建值时指定为POST动作,这样etcd会自动在目录下生成一个当前最大的值为键,存储这个新的值(客户端编号)。同时还可以使用API按顺序列出所有当前目录下的键值。此时这些键的值就是客户端的时序,而这些键中存储的值可以是代表客户端的编号。
示意图如下所示:
1.3.6 分布式队列
分布式队列的常规用法与分布式锁的控制时序用法类似,创建一个先进先出的队列,保证顺序。另一种比较有意思的实现是在保证队列达到某个条件时再统一按顺序执行。这种方法的实现可以在/queue这个目录中另外建立一个/queue/condition节点,condition可以表示信息如下:
- condition可以表示队列大小。比如一个大的任务需要很多小任务就绪的情况下才能执行,每次有一个小任务就绪,就给这个condition数字加1,直到达到大任务规定的数字,再开始执行队列里的一系列小任务,最终执行大任务,如下图所示:
condition可以表示某个任务在不在队列。这个任务可以是所有排序任务的首个执行程序,也可以是拓扑结构中没有依赖的点。通常必须执行这些任务后才能执行队列中的其他任务。
condition还可以表示其它的一类开始执行任务的通知。可以由控制程序指定,当condition出现变化时,开始执行队列任务。
1.3.7 集群监控
使用etcd来实现集群的实时性的监控,可以第一时间检测到各节点的健康状态,以完成集群的监控要求。etcd本身就有自带检点健康监控功能,实现起来也比较简单
- 使用Watcher机制,当某个节点消失或有变动时,Watcher会第一时间发现并告知用户
- 节点可以设置TTL key,比如每隔30s发送一次心跳使代表该机器存活的节点继续存在,否则节点消失
1.3.8 Leader竞选
使用分布式锁,可以完成Leader竞选。这种场景通常是一些长时间CPU计算或者使用IO操作的机器,只需要竞选出的Leader计算或处理一次,就可以把结果复制给其他的Follower,从而避免重复劳动,节省计算资源。
可使用在搜索系统中建立全量索引。如果每个机器都进行一遍索引的建立,不但耗时而且建立索引的一致性不能保证。通过在etcd的CAS机制同时创建一个节点,创建成功的机器作为Leader,进行索引计算,然后把计算结果分发到其它节点。
原文地址:https://www.jianshu.com/p/f1b731766d5a
本文同步在微信订阅号上发布,如各位小伙伴们喜欢我的文章,也可以关注我的微信订阅号:woaitest,或扫描下面的二维码添加关注: