Etcd-v3架构与实现解析
概述
前一段时间的项目里用到了 Etcd,所以研究了一下它的源码以及实现。网上关于 Etcd 的使用介绍的文章不少,但分析具体架构实现的文章不多,同时 Etcd v3的文档也非常稀缺。本文通过分析 Etcd 的架构与实现,了解其优缺点以及瓶颈点,一方面可以学习分布式系统的架构,另外一方面也可以保证在业务中正确使用 Etcd,知其然同时知其所以然,避免误用。最后介绍 Etcd 周边的工具和一些使用注意事项。
Etcd
Etcd is a distributed, consistent key-value store for shared configuration and service discovery
是一个分布式的,一致的 key-value 存储,主要用途是共享配置和服务发现。Etcd 已经在很多分布式系统中得到广泛的使用,本文的架构与实现部分主要解答以下问题:
- Etcd是如何实现一致性的
- Etcd的存储是如何实现的
- Etcd的watch机制是如何实现的
- Etcd的key过期机制是如何实现的
为什么需要Etcd
所有的分布式系统,都面临的一个问题是多个节点之间的数据共享问题,这个和团队协作的道理是一样的,成员可以分头干活,但总是需要共享一些必须的信息,比如谁是 leader, 都有哪些成员,依赖任务之间的顺序协调等。所以分布式系统要么自己实现一个可靠的共享存储来同步信息(比如 Elasticsearch),要么依赖一个可靠的共享存储服务,而 Etcd 就是这样一个服务
Etcd提供什么能力
- 提供存储以及获取数据的接口,它通过协议保证Etcd集群中的多个节点数据的强一致性。用于存储元信息以及共享配置
- 提供监听机制,客户端可以监听某个key或者某些key的变更。用于监听和推送变更
- 提供key的过期以及续约机制,客户端通过定时刷新来实现续约。用于集群监控以及服务注册发现
- 提供原子的CAS(Compare-and-Swap)和CAD(Compare-and-Delete)支持(v2通过接口参数实现,v3通过批量事务实现)。用于分布式锁以及leader选举
Etcd如何实现一致性
说到这个就不得不说起 Raft 协议。但这篇文章不是专门分析 Raft 的,篇幅所限,不能详细分析,有兴趣的建议看文末原始论文地址以及 Raft 协议的一个动画。便于看后面的文章,我这里简单做个总结:
Raft 通过对不同的场景(选主,日志复制)设计不同的机制,虽然降低了通用性(相对paxos),但同时也降低了复杂度,便于理解和实现。
Raft 内置的选主协议是给自己用的,用于选出主节点,理解 Raft 的选主机制的关键在于理解 Raft 的时钟周期以及超时机制。理解 Etcd 的数据同步的关键在于理解 Raft 的日志同步机制。
Etcd 实现 Raft 的时候,充分利用了 Go 语言 CSP 并发模型和 chan 的魔法,想更进行一步了解的可以去看源码,这里只简单分析下它的 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 协议
Raft 协议本身不关心应用数据,也就是 data 中的部分,一致性都通过同步 wal 日志来实现,每个节点将从主节点收到的 data apply 到本地的存储,Raft 只关心日志的同步状态,如果本地存储实现的有 bug,比如没有正确的将 data apply 到本地,也可能会导致数据不一致
Etcd v2与v3
Etcd v2 和 v3 本质上是共享同一套 raft 协议代码的两个独立的应用,接口不一样,存储不一样,数据互相隔离。也就是说如果从 Etcd v2 升级到 Etcd v3,原来v2 的数据还是只能用 v2 的接口访问,v3 的接口创建的数据也只能访问通过 v3 的接口访问。所以我们按照 v2 和 v3 分别分析。
Etcd v2 是个纯内存的实现,并未实时将数据写入到磁盘,持久化机制很简单,就是将 store 整合序列化成 json 写入文件。数据在内存中是一个简单的树结构。比如以下数据存储到 Etcd 中的结构就如图所示。