ETCD是用于共享配置和服务发现的分布式,一致性的KV存储系统。该项目目前最新稳定版本为2.3.0. 具体信息请参考[项目首页]和[Github]。ETCD是CoreOS公司发起的一个开源项目,受权协议为Apache。golang
提供配置共享和服务发现的系统比较多,其中最为你们熟知的是[Zookeeper](后文简称ZK),而ETCD能够算得上是后起之秀了。在项目实现,一致性协议易理解性,运维,安全等多个维度上,ETCD相比Zookeeper都占据优点。算法
本文选取ZK做为典型表明与ETCD进行比较,而不考虑[Consul]项目做为比较对象,缘由为Consul的可靠性和稳定性还须要时间来验证(项目发起方自身服务并未使用Consul, 本身都不用)。docker
和ZK相似,ETCD有不少使用场景,包括:数组
按照官网给出的[Benchmark], 在2CPU,1.8G内存,SSD磁盘这样的配置下,单节点的写性能能够达到16K QPS, 而先写后读也能达到12K QPS。这个性能仍是至关可观的。安全
ETCD使用Raft协议来维护集群内各个节点状态的一致性。简单说,ETCD集群是一个分布式系统,由多个节点相互通讯构成总体对外服务,每一个节点都存储了完整的数据,而且经过Raft协议保证每一个节点维护的数据是一致的。
如图所示,每一个ETCD节点都维护了一个状态机,而且,任意时刻至多存在一个有效的主节点。主节点处理全部来自客户端写操做,经过Raft协议保证写操做对状态机的改动会可靠的同步到其余节点。服务器
ETCD工做原理核心部分在于Raft协议。本节接下来将简要介绍Raft协议,具体细节请参考其[论文]。
Raft协议正如论文所述,确实方便理解。主要分为三个部分:选主,日志复制,安全性。网络
Raft协议是用于维护一组服务节点数据一致性的协议。这一组服务节点构成一个集群,而且有一个主节点来对外提供服务。当集群初始化,或者主节点挂掉后,面临一个选主问题。集群中每一个节点,任意时刻处于Leader, Follower, Candidate这三个角色之一。选举特色以下:数据结构
Candidate节点收到来自主节点的信息后,会当即终止选举过程,进入Follower角色。架构
为了不陷入选主失败循环,每一个节点未收到心跳发起选举的时间是必定范围内的随机值,这样可以避免2个节点同时发起选主。并发
所谓日志复制,是指主节点将每次操做造成日志条目,并持久化到本地磁盘,而后经过网络IO发送给其余节点。其余节点根据日志的逻辑时钟(TERM)和日志编号(INDEX)来判断是否将该日志记录持久化到本地。当主节点收到包括本身在内超过半数节点成功返回,那么认为该日志是可提交的(committed),并将日志输入到状态机,将结果返回给客户端。
这里须要注意的是,每次选主都会造成一个惟一的TERM编号,至关于逻辑时钟。每一条日志都有全局惟一的编号。
主节点经过网络IO向其余节点追加日志。若某节点收到日志追加的消息,首先判断该日志的TERM是否过时,以及该日志条目的INDEX是否比当前以及提交的日志的INDEX跟早。若已过时,或者比提交的日志更早,那么就拒绝追加,并返回该节点当前的已提交的日志的编号。不然,将日志追加,并返回成功。
当主节点收到其余节点关于日志追加的回复后,若发现有拒绝,则根据该节点返回的已提交日志编号,发生其编号下一条日志。
主节点像其余节点同步日志,还做了拥塞控制。具体地说,主节点发现日志复制的目标节点拒绝了某第二天志追加消息,将进入日志探测阶段,一条一条发送日志,直到目标节点接受日志,而后进入快速复制阶段,可进行批量日志追加。
按照日志复制的逻辑,咱们能够看到,集群中慢节点不影响整个集群的性能。另一个特色是,数据只从主节点复制到Follower节点,这样大大简化了逻辑流程。
截止此刻,选主以及日志复制并不能保证节点间数据一致。试想,当一个某个节点挂掉了,一段时间后再次重启,并当选为主节点。而在其挂掉这段时间内,集群如有超过半数节点存活,集群会正常工做,那么会有日志提交。这些提交的日志没法传递给挂掉的节点。当挂掉的节点再次当选主节点,它将缺失部分已提交的日志。在这样场景下,按Raft协议,它将本身日志复制给其余节点,会将集群已经提交的日志给覆盖掉。
这显然是不可接受的。
其余协议解决这个问题的办法是,新当选的主节点会询问其余节点,和本身数据对比,肯定出集群已提交数据,而后将缺失的数据同步过来。这个方案有明显缺陷,增长了集群恢复服务的时间(集群在选举阶段不可服务),而且增长了协议的复杂度。
Raft解决的办法是,在选主逻辑中,对可以成为主的节点加以限制,确保选出的节点已定包含了集群已经提交的全部日志。若是新选出的主节点已经包含了集群全部提交的日志,那就不须要从和其余节点比对数据了。简化了流程,缩短了集群恢复服务的时间。
这里存在一个问题,加以这样限制以后,还可否选出主呢?答案是:只要仍然有超过半数节点存活,这样的主必定可以选出。由于已经提交的日志必然被集群中超过半数节点持久化,显然前一个主节点提交的最后一条日志也被集群中大部分节点持久化。当主节点挂掉后,集群中仍有大部分节点存活,那这存活的节点中必定存在一个节点包含了已经提交的日志了。
至此,关于Raft协议的简介就所有结束了。
据公开资料显示,至少有CoreOS, Google Kubernetes, Cloud Foundry, 以及在Github上超过500个项目在使用ETCD。
ETCD提供HTTP协议,在最新版本中支持Google gRPC方式访问。具体支持接口状况以下:
本文对ETCD做了一个简单的介绍,但愿对你有帮助。
https://yq.aliyun.com/articles/11035
想必不少人都知道ZooKeeper,一般用做配置共享和服务发现。和它相似,ETCD算是一个很是优秀的后起之秀了。本文重点不在描述他们之间的不一样点。首先,看看其官网关于ETCD的描述1:
A distributed, reliable key-value store for the most critical data of a distributed system.
在云计算大行其道的今天,ETCD有不少典型的使用场景。常言道,熟悉一个系统先从部署开始。本文接下来将描述,如何部署ETCD集群。
安装官网说明文档,提供了3种集群启动方式,实际上按照其实现原理分为2类:
在部署集群以前,咱们须要考虑集群须要配置多少个节点。这是一个重要的考量,不得忽略。
ETCD使用RAFT协议保证各个节点之间的状态一致。根据RAFT算法原理,节点数目越多,会下降集群的写性能。这是由于每一次写操做,须要集群中大多数节点将日志落盘成功后,Leader节点才能将修改内部状态机,并返回将结果返回给客户端。
也就是说在等同配置下,节点数越少,集群性能越好。显然,只部署1个节点是没什么意义的。一般,按照需求将集群节点部署为3,5,7,9个节点。
这里能选择偶数个节点吗? 最好不要这样。缘由有二:
当网络分割后,ETCD集群如何处理的呢?
当网络分割恢复后,少数派的节点会接受集群Leader的日志,直到和其余节点状态一致。
这里只列举一些重要的参数,以及其用途。
按照官网中的文档,便可完成集群启动。这里略。
ETCD还提供了另一种启动方式,即经过服务发现的方式启动。这种启动方式,依赖另一个ETCD集群,在该集群中建立一个目录,并在该目录中建立一个_config
的子目录,而且在该子目录中增长一个size
节点,指定集群的节点数目。
在这种状况下,将该目录在ETCD中的URL做为节点的启动参数,便可完成集群启动。使用--discovery https://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83
配置项取代静态配置方式中的--initial-cluster
和inital-cluster-state
参数。其中https://myetcd.local/v2/keys/discovery/6c007a14875d53d9bf0ef5a6fc0257c817f0fb83
是在依赖etcd中建立好的目录url。
在生产环境中,不可避免遇到机器硬件故障。当遇到硬件故障发生的时候,咱们须要快速恢复节点。ETCD集群能够作到在不丢失数据的,而且不改变节点ID的状况下,迁移节点。
具体办法是:
本文记录了ETCD集群启动的一些注意事项,但愿对你有帮助。
https://yq.aliyun.com/articles/29897?spm=5176.100239.blogcont11035.15.7bihps
在理清ETCD的各个模块的实现细节后,方便线上运维,理解各类参数组合的意义。本文先从网络层入手,后续文章会依次介绍各个模块的实现。
本文将着重介绍ETCD服务的网络层实现细节。在目前的实现中,ETCD经过HTTP协议对外提供服务,一样经过HTTP协议实现集群节点间数据交互。
网络层的主要功能是实现了服务器与客户端(能发出HTTP请求的各类程序)消息交互,以及集群内部各节点之间的消息交互。
ETCD-SERVER 大致上能够分为网络层,Raft模块,复制状态机,存储模块,架构图如图1所示。
图1 ETCD-SERVER架构图
各个节点在任什么时候候都有可能变成Leader, Follower, Candidate等角色,同时为了减小建立连接开销,ETCD节点在启动之初就建立了和集群其余节点之间的连接。
所以,ETCD集群节点之间的网络拓扑是一个任意2个节点之间均有长连接相互链接的网状结构。如图2所示。
图2 ETCD集群节点网络拓扑图
须要注意的是,每个节点都会建立到其余各个节点之间的长连接。每一个节点会向其余节点宣告本身监听的端口,该端口只接受来自其余节点建立连接的请求。
在ETCD实现中,根据不一样用途,定义了各类不一样的消息类型。各类不一样的消息,最终都经过google protocol buffer协议进行封装。这些消息携带的数据大小可能不尽相同。例如 传输SNAPSHOT数据的消息数据量就比较大,甚至超过1GB, 而leader到follower节点之间的心跳消息可能只有几十个字节。
所以,网络层必须可以高效地处理不一样数据量的消息。ETCD在实现中,对这些消息采起了分类处理,抽象出了2种类型消息传输通道:Stream类型通道和Pipeline类型通道。这两种消息传输通道都使用HTTP协议传输数据。
图3 节点之间创建消息传输通道
集群启动之初,就建立了这两种传输通道,各自特色:
若是非要作作一个类别的话,Stream就向点与点之间维护了双向传输带,消息打包后,放到传输带上,传到对方,对方将回复消息打包放到反向传输带上;而Pipeline就像拥有N辆汽车,大消息打包放到汽车上,开到对端,而后在回来,最多能够同时发送N个消息。
Stream类型通道
Stream类型通道处理数据量少的消息,例如心跳,日志追加消息。点到点之间只维护1个HTTP长连接,交替向连接中写入数据,读取数据。
Stream 类型通道是节点启动后主动与其余每个节点创建。Stream类型通道经过Channel 与Raft模块传递消息。每个Stream类型通道关联2个Goroutines, 其中一个用于创建HTTP连接,并从连接上读取数据, decode成message, 经过Channel传给Raft模块中,另一个经过Channel 从Raft模块中收取消息,而后写入通道。
具体点,ETCD使用golang的http包实现Stream类型通道:
Pipeline类型通道
Pipeline类型通道处理数量大消息,例如SNAPSHOT消息。这种类型消息须要和心跳等消息分开处理,不然会阻塞心跳。
Pipeline类型通道也能够传输小数据量的消息,当且仅当Stream类型连接不可用时。
Pipeline类型通道可用并行发出多个消息,维护一组Goroutines, 每个Goroutines均可向对端发出POST请求(携带数据),收到回复后,连接关闭。
具体地,ETCD使用golang的http包实现的:
在ETCD中,Raft协议被抽象为Raft模块。按照Raft协议,节点之间须要交互数据。在ETCD中,经过Raft模块中抽象的RaftNode拥有一个message box, RaftNode将各类类型消息放入到messagebox中,有专门Goroutine将box里的消息写入管道,而管道的另一端就连接在网络层的不一样类型的传输通道上,有专门的Goroutine在等待(select)。
而网络层收到的消息,也经过管道传给RaftNode。RaftNode中有专门的Goroutine在等待消息。
也就是说,网络层与Raft模块之间经过Golang Channel完成数据通讯。这个比较容易理解。
在ETCD-SERVER启动之初,会监听服务端口,当服务端口收到请求后,解析出message后,经过管道传入给Raft模块,当Raft模块按照Raft协议完成操做后,回复该请求(或者请求超时关闭了)。
网络层抽象为Transport类,该类完成网络数据收发。对Raft模块提供Send/SendSnapshot接口,提供数据读写的Channel,对外监听指定端口。
本文整理了ETCD节点网络层的实现,为分析其余模块打下基础。
https://yq.aliyun.com/articles/29900?spm=5176.100239.blogcont29897.12.4qqzpF