概要

Etcd是一个分布式的,一致的 key-value 存储,主要用途是共享配置和服务发现。

主要提供以下能力

  • 提供存储以及获取数据的接口,它通过协议保证 Etcd 集群中的多个节点数据的强一致性。用于存储元信息以及共享配置。
  • 提供监听机制,客户端可以监听某个key或者某些key的变更(v2和v3的机制不同,参看后面文章)。用于监听和推送变更。
  • 提供key的过期以及续约机制,客户端通过定时刷新来实现续约(v2和v3的实现机制也不一样)。用于集群监控以及服务注册发现。
  • 提供原子的CAS(Compare-and-Swap)和 CAD(Compare-and-Delete)支持(v2通过接口参数实现,v3通过批量事务实现)。用于分布式锁以及leader选举。

一致性

通过Raft协议提供一致性

数据存储

etcd的存储分为内存存储和持久化(硬盘)存储两部分

  • 内存中的存储除了顺序化的记录下所有用户对节点数据变更的记录外,还会对用户数据进行索引、建堆等方便查询的操作。
  • 持久化则使用预写式日志(WAL:Write Ahead Log)进行记录存储。

在WAL的体系中,所有的数据在提交之前都会进行日志记录。

在etcd的持久化存储目录中,有两个子目录。

  • WAL,存储着所有事务的变化记录;
  • snapshot,用于存储某一个时刻etcd所有目录的数据。

通过WAL和snapshot相结合的方式,etcd可以有效的进行数据存储和节点故障恢复等操作。

etcd默认每10000条记录做一次snapshot,经过snapshot以后的WAL文件就可以删除

首次启动时,etcd会把启动的配置信息存储到data-dir参数指定的数据目录中。

配置信息包括本地节点的ID、集群ID和初始时集群信息。

用户需要避免etcd从一个过期的数据目录中重新启动,因为使用过期的数据目录启动的节点会与集群中的其他节点产生不一致

WAL

WAL(Write Ahead Log)预写式日志, 最大的作用是记录了整个数据变化的全部历程

在etcd中,所有数据的修改在提交前,都要先写入到WAL中。

使用WAL进行数据的存储使得etcd拥有两个重要功能。

  • 故障快速恢复: 当你的数据遭到破坏时,就可以通过执行所有WAL中记录的修改操作,快速从最原始的数据恢复到数据损坏前的状态。
  • 数据回滚(undo)/重做(redo):因为所有的修改操作都被记录在WAL中,需要回滚或重做,只需要方向或正向执行日志中的操作即可。

WAL与snapshot在etcd中的命名规则

在etcd的数据目录中,WAL文件以**$seq-$index.wal**的格式存储。

最初始的WAL文件是0000000000000000-0000000000000000.wal,表示是所有WAL文件中的第0个,初始的Raft状态编号为0。

运行一段时间后可能需要进行日志切分,把新的条目放到一个新的WAL文件中。

假设,当集群运行到Raft状态为20时,需要进行WAL文件的切分时,下一份WAL文件就会变为0000000000000001-0000000000000021.wal。

如果在10次操作后又进行了一次日志切分,那么后一次的WAL文件名会变为0000000000000002-0000000000000031.wal。

可以看到-符号前面的数字是每次切分后自增1,而-符号后面的数字则是根据实际存储的Raft起始状态来定。

snapshot的存储命名则比较容易理解,以**$term-$index.wal**格式进行命名存储。

term和index就表示存储snapshot时数据所在的raft节点状态,当前的任期编号以及数据项位置信息。

WAL结构

wal日志是二进制的,解析出来后是以上数据结构LogEntry。其中第一个字段type,只有两种,一种是0表示Normal,1表示ConfChange(ConfChange表示 Etcd 本身的配置变更同步,比如有新的节点加入等)。第二个字段是term,每个term代表一个主节点的任期,每次主节点变更term就会变化。第三个字段是index,这个序号是严格有序递增的,代表变更序号。第四个字段是二进制的data,将raft request对象的pb结构整个保存下。Etcd 源码下有个tools/etcd-dump-logs,可以将wal日志dump成文本查看,可以协助分析raft协议。

关键源码逻辑

从代码逻辑中可以看到,WAL有两种模式,读模式(read)和数据添加(append)模式,两种模式不能同时成立。

一个新创建的WAL文件处于append模式,并且不会进入到read模式。

一个本来存在的WAL文件被打开的时候必然是read模式,并且只有在所有记录都被读完的时候,才能进入append模式,进入append模式后也不会再进入read模式。这样做有助于保证数据的完整与准确。

集群在进入到etcdserver/server.go的NewServer函数准备启动一个etcd节点时,会检测是否存在以前的遗留WAL数据。

检测的第一步是查看snapshot文件夹下是否有符合规范的文件,若检测到snapshot格式是v0.4的,则调用函数升级到v0.5。

从snapshot中获得集群的配置信息,包括token、其他节点的信息等等

然后载入WAL目录的内容,从小到大进行排序。根据snapshot中得到的term和index,找到WAL紧接着snapshot下一条的记录,然后向后更新,直到所有WAL包的entry都已经遍历完毕,Entry记录到ents变量中存储在内存里。

此时WAL就进入append模式,为数据项添加进行准备。

当WAL文件中数据项内容过大达到设定值(默认为10000)时,会进行WAL的切分,同时进行snapshot操作。

这个过程可以在etcdserver/server.go的snapshot函数中看到。

所以,实际上数据目录中有用的snapshot和WAL文件各只有一个,默认情况下etcd会各保留5个历史文件。