概述
etcd 诞生于CoreOS公司,当前隶属于CNCF基金会,供高可用、强一致的小型key value数据存储服务。
架构
- 主要的模块有:
- gRPC server:负责对外提供gRPC接口,目前最新稳定版本已经支持http访问接口。用于处理用户发送的 API 请求以及其它 etcd 节点的同步与心跳信息请求
- raft 状态机:Raft 强一致性算法的具体实现,是 etcd 的核心
- wal 日志存储:Write Ahead Log(预写式日志),是 etcd 的数据存储方式。除了在内存中存有所有数据的状态以及节点的索引以外,etcd 就通过 WAL 进行持久化存储。WAL 中,所有的数据提交前都会事先记录日志。
- Snapshot 是为了防止数据过多而进行的状态快照;Entry 表示存储的具体日志内容。
- boltdb:kv数据的存储引擎,v3支持不同的后端存储,当前采用boltdb,通过boltdb支持事务操作。
- 主要有四种角色:
- leader:负责日志的同步管理,处理来自客户端的请求,与Follower保持心跳的联系
- follower:刚启动时所有节点为follower状态,响应leader的日志同步请求,响应candidate的请求,把请求到follower的事务转发给leader
- candidate:负责选举投票,Raft刚启动时由一个节点从Follower转为Candidate发起选举,选举出; leader后从candidate转为leader状态;当follower收不到leadr的心跳之后,也会变成candidate状态
- client:请求发起者
概念
- proxy etcd的一种模式,为 etcd 集群提供反向代理服务
- term 某个节点成为leader到下一次竞选时间,称为一个term
- revision
revision 代表的是全局数据的版本。当数据发生变更,包括创建、修改、删除,其 revision 对应的都会+1。特别的,在集群中跨 leader 任期之间,revision 都会保持全局单调递增。正是 revision 的这一特性,使得集群中任意一次的修改都对应着一个唯一的 revision,因此我们可以通过 revision 来支持数据的MVCC,也可以支持数据的watch。对于每一个 key value 数据节点,etcd 中都记录了三个版本:
- 第一个版本叫做 create_revision,是 key value 在创建时对应的 revision;
- 第二个叫做 mod_revision,是其数据被操作的时候对应的 revision;
- 第三个 version 就是一个计数器,代表了 key value 被修改了多少次。
- watch 使用watch订阅数据时,可以支持从任意历史时刻(指定 revision)开始创建一个 watcher,在客户端与 etcd 之间建立一个数据管道,etcd 会推送从指定 revision 开始的所有数据变更。etcd 提供的 watch 机制保证,该 Key 的数据后续的被修改之后,通过这个数据管道即时的推送给客户端。
- lease租约 lease service提供租约的支持。lease 是一种检测客户端存活状况的机制。集群授予具有生存时间的租约。如果 etcd 集群在给定的 TTL 时间内未收到keepAlive,则租约到期。 为了将租约绑定到键值存储中,每个 key 最多可以附加一个租约。当租约到期或被撤销时,该租约所附的所有 key 都将被删除。每个过期的密钥都会在事件历史记录中生成一个删除事件。
核心raft协议
Raft协议采用分治的思想,把分布式协同的问题分为3个问题:
- 选举: 一个新的集群启动时,或者老的leader故障时,会选举出一个新的leader。
- 日志同步: leader必须接受客户端的日志条目并且将他们同步到集群的所有机器。
- 数据安全: 保证任何节点只要在它的状态机中生效了一条日志条目,就不会在相同的key上生效另一条日志条目。
leader选举
- 初始状态下,大家都是平等的follower,每个follower内部都维护了一个随机的timer。在timer时间到了的时候还没有人主动联系它的话,那它就要变成candidate,同时发出投票请求(RequestVote)给其他人。RequestVote一般包含如下信息:
- term,自身处于的选举周期
- lastLogIndex,log中最新的index值
- lastLogTerm,log中最近的index是在哪个term中产生的
- 对于相同条件的candidate,follower们采取先来先投票的策略。如果超过半数的follower都认为他是合适做领导的,那么恭喜,新的leader产生了。如果没有人愿意选这个悲剧的candidate,那它只有老老实实的变回小弟的状态。而follower投票的时候也会和自己对比下RequestVote相关信息,以确保candidate上存储的数据是最新的:
- 如果term < currentTerm,也就是说candidate的版本还没我新,返回 false
- 如果已经投票给别的candidate了(votedFor),则返回false
- log匹配,如果和自身的log匹配上了,则返回true
- 选举完成之后,leader会定时发送心跳检测(heart beat)给follower,follower通过心跳来感知leader的存在的。
- 如果follower在timer期间内没有收到leader的心跳,这时很可能leader已经跪了,新的一轮(term)选举就开始了。
数据同步
同步的流程如下:
- 集群某个节点收到 client 的 put 请求要求修改数据。节点会生成一个 Type 为 MsgProp 的 Message,发送给 leader。
- leader 收到 Message 以后,会处理 Message 中的日志条目,将其 append 到 raftLog 的 unstable 的日志中,并且调用 bcastAppend()广播 append 日志的消息。
- leader 中有协程处理 unstable 日志和刚刚准备发送的消息,newReady 方法会把这些都封装到 Ready 结构中。
- leader 的另一个协程处理这个 Ready,先发送消息,然后调用 WAL 将日志持久化到本地磁盘。WAL 中保存的持久化的日志条目会有一个定时任务定时删除。
- follower 收到 append 日志的消息,会调用它自己的 raftLog,将消息中的日志 append 到本地缓存中。随后 follower 也像 leader 一样,有协程将缓存中的日志条目通过WAL持久化到磁盘中并将当前已经持久化的最新日志 index 返回给 leader。
- leader收到follower的回复之后,如果超过半数的节点都写日志成功,那么leader会把更新数据commit到boltdb存储,然后给所有follower发起第二次持久化数据到boltdb的广播,超过半数的follower commit成功则此次写成功。
数据安全
raft为了保证数据的强一致性,所有的数据流向都是一个方向,从 leader 流向 follower,也就是所有 follower 的数据必须与 leader 保持一致,如果不一致会被覆盖。即所有用户更新数据的请求都最先由 leader 获得,然后存下来通知其他节点也存下来,等到大多数节点反馈时再把数据提交。一个已提交的数据项才是 raft 真正稳定存储下来的数据项,不再被修改,最后再把提交的数据同步给其他 follower。因为每个节点都有 raft 已提交数据准确的备份(最坏的情况也只是已提交数据还未完全同步),所以读的请求任意一个节点都可以处理。
应用场景
和zookeeper一样
优缺点
对比zookeeper的优点
- 简单:易于部署,易使用。基于 HTTP+JSON 的 API 让你用 curl 就可以轻松使用
- 安全:可选 SSL 客户认证机制
- 快速:每个实例每秒支持一千次写操作
- 可信:使用一致性 Raft 算法充分实现了分布式
缺点
- 在网络分区时,当 leader 处于小分区时,读请求会继续被处理
总结
etcd目前背靠CNCF,有k8s背书,作为新一代的协调服务未来可期,应该会逐步取代zookeeper。