ETCD 原理剖析
ETCD
etcd 官方地址
https://etcd.io/
etcd 是 CoreOS 公司, 最初用于解决集群管理系统中 OS 升级时的分布式并发控制、配置文件的存储于分发等问题。
- etcd 被设计为提供高可用、强一致性的小型
key/value
数据存储服务。
- etcd 被设计为提供高可用、强一致性的小型
etcd 目前已经隶属于
CNCF (Cloud Native Computing Foundation)
基金会, 被包含 AWS 、Google、Microsoft、Alibaba 等大型互联网公司广泛使用。etcd 最早在 2013年6月份 于
github
中开源。etcd 于 2014年6月份 正式被
kubernetes
使用, 用于存储kubernetes
集群中的元数据。etcd 于 2015年2月份 发布 2.0 版本, 正式支持分布式协议 Raft, 并支持 1000/s 的并发
writes
。etcd 于 2017年1月份 发布 3.1 版本, 全面优化了
etcd
, 新的API
、重写了强一致性(read)读的方法,并提供了 gRPC Proxy 接口, 以及大量gc
的优化。etcd 于 2018年11月 加入 CNCF 基金会。
etcd 于 2019年 发布 etcd v3.4 该版本又
Google、AWS、Alibaba
等大公司联合打造的一个版本。将进一步优化稳定性以及性能。
Etcd 架构与内部机制
Etcd 术语简介
名称 | 含义 |
---|---|
Raft | etcd 使用的一致性算法 |
WAL | 预写Log , 持久化数据, 防止节点重启等导致数据丢失 |
Snapshot | 快照, 数据更新超过阈值时, 会通过快照的方式来压缩 WAL 文件大小 |
MVCC | 多版本并发控制 |
DB | boltdb /bboltdb , 实际存储 etcd v3 的数据 |
Revision | 版本号, 作为 etcd 数据的逻辑时钟 |
Auth revision | 鉴权操作所用的版本号, 为了避免 TOCTOU 问题引入 |
Propose | 发起一次 Raft 请求提案 |
Committed | 一半以上的节点同意这次请求后的状态, 此时数据可以被应用层 apply |
Apply | 应用层实际将 Committed 的数据应用到 DB |
Compact | 压缩历史版本数据 |
Defrag | 碎片整理, 压缩 etcd 的 db 存储大小 |
Endpoint | 客户端指定的 etcd 访问地址 |
Node | 组成 etcd 集群的节点 |
Term | Leader 任期, 每进行一次 leader 选举 Term 会增加 1 |
Index | 单调递增, 每次经过 Raft 模块发起变更操作时由 leader 增加 |
CommittedIndex | 经过 Raft 协议同意提交的数据 Index |
AppliedIndex | 已经被应用层应用的 Index |
ConsistentIndex | 为保证不重复 Apply 同一条数据引入, 保证 Apply 操作的幂等性 |
ReadIndex | 通过 Raft 模块获取 leader 当前的 committedIndex |
etcd 基础概念
etcd 是一个
分布式
、强一致性可靠
、key/value
存储系统。 它主要用于存储分布式系统中的 关键数据。etcd
key/value
存储是按照 有序key
排列的, 可以顺序遍历。因为
key
有序, 所以etcd
支持按目录结构高效遍历。支持复杂事务, 提供类似
if ... then ... else ...
的事务能力。基于租约机制实现
key
的TTL
过期。
etcd 包含二种状态:
Leader
Follower
etcd 集群通常由 奇数(最低3)个 etcd 组成。
集群中多个 etcd 通过
Raft consensus algorithm
算法进行协同, 多个 etcd 会通过Raft
算法选举出一个Leader
, 由Leader
节点进行数据同步, 以及数据分发。etcd
通过boltdb
持久化存储数据。当
Leader
出现故障时, 集群中的 etcd 会投票选举出另一个Leader
, 并重新进行数据同步以及数据分发。客户端从任何一个 etcd 都可以进行
读/写
操作。在 etcd 集群中 有一个关键概念
quorum
、quorum = ( n + 1 ) / 2
, 也就是说超过集群中半数节点组成的一个团体。 集群中可以容忍故障的数量, 如(3 + 1) / 2 = 1
可以容忍的故障数为1台。在 etcd 集群中 任意两个 quorum 的成员之间一定会有交集, 只要有任意一个
quorum
存活, 其中一定存在某一个节点它包含 etcd 集群中最新的数据, 基于这种假设,Raft
一致性算法就可以在一个 quorum 之间采用这份最新的数据去完成数据的同步。quorum
: 在Raft
中超过一半以上的人数就是法定人数。
etcd 提供了如下
API
Put(key, value)
增加Delete(key)
删除Get(key)
/Get(keyFrom, keyEnd)
查询Watch(key)
/Watch(key前缀)
事件监听, 监听key的变化Transactions(if / then / else ops).Commit()
事务操作, 指定某些条件, 为true
时执行其他操作。Leasesi Grant / Revoke / KeepAlive
etcd 写操作流程
1.
etcd
任一节点的etcd Server
模块收到Client
写请求- 1.1. 如果是
follower
节点, 会先通过Raft
模块将请求转发至leader
节点处理。
- 1.1. 如果是
2.
etcd Server
将请求封装为Raft
请求, 然后提交给Raft
模块处理。3.
leader
通过Raft
协议与集群中follower
节点进行交互, 将消息复制到follower
节点, 于此同时, 并行将日志持久化到WAL
。4.
follower
节点对该请求进行响应, 回复自己是否同意该请求。5. 当集群中超过半数节点
((n/2)+1 members )
同意接收这条日志数据时, 表示该请求可以被Commit
,Raft
模块通知etcd Server
该日志数据已经Commit
, 可以进行Apply
。6. 各个节点的
etcd Server
的applierV3
模块异步进行Apply
操作, 并通过MVCC
模块写入后端存储BoltDB
。7. 当
client
所连接的节点数据apply
成功后, 会返回给客户端apply
的结果。
etcd 读操作流程
1.
etcd
任一节点的etcd Server
模块收到客户端读请求(Range 请求)
。2. 判断读请求类型, 如果是串行化读
(serializable)
则直接进入Apply
流程。3. 如果是线性一致性读
(linearizable)
, 则进入Raft
模块。4.
Raft
模块向leader
发出ReadIndex
请求, 获取当前集群已经提交的最新数据Index
。5. 等待本地
AppliedIndex
大于或等于ReadIndex
获取的CommittedIndex
时, 进入Apply
流程。6.
Apply
流程: 通过Key
名从KV Index
模块获取Key
最新的Revision
, 再通过Revision
从BoltDB
获取对应的Key
和Value
。
etcd 数据版本号机制
etcd 的数据版本号机制非常重要
term
: 全局单调递增 (64bits),term
表示整个集群中leader
的任期, 当集群中发生leader
切换, 如:leader
节点故障、leader
网络故障、整个集群重启都会发生leader
切换, 这个时候term = term + 1
。revision
: 全局单调递增 (64bits),revision
表示在整个集群中数据变更版本号, 当集群中数据发生变更, 包括创建
、修改
、删除
的时候,revision = revision + 1
。Key/Vaule
:Create_revision
: 表示在当前Key/Value
中在整个集群数据中创建时(revision)的版本号。每个Key/Value
都有一个Create_revision
。mod_revision
: 表示当前Key/Value
等于当前修改时的全局的版本数 (revision) 既mod_version = 当前 revision
。version
: 表示当前Key/Value
被修改了多少次。
实际例子操作
- 查看 key 的相关版本信息
|
|
etcd leader 选举机制
集群选举
Leader
需要半数以上节点参与节点
revision
版本最大的允许选举为Leader
节点中
revision
相同, 则term
越大的允许选举为Leader
etcd mvcc && watch
mvcc
: 全称Multi-Version Concurrency Control
即多版本并发控制。mvcc
: 是一种并发控制的方法, 一般在数据库管理系统中, 实现对数据库的并发访问。
在
etcd
中, 支持对同一个Key
发起多次数据修改。因为已经知道每次数据修改都对应一个版本号(mod_revision)
, 多次修改就意味着一个key
中存在多个版本, 在查询数据的时候可以通过不指定版本号查询Get key
, 这时etcd
会返回该数据的最新版本。当我们指定一个版本号查询数据后Get --rev=1 Key
, 可以获取到一个Key
的历史版本。在
watch
的时候指定数据的版本, 创建一个watcher
, 并通过这个watcher
提供的一个数据管道, 能够获取到指定的revision
之后所有的数据变更。如果指定的revision
是一个旧版本, 可以立即拿到从旧版本到当前版本所有的数据更新。并且,watch
的机制会保证etcd
中, 该Key
的数据发生后续的修改后, 依然可以从这个数据管道中拿到数据增量的更新。在
etcd
中 所有的数据都存储在一个b + tree
中。b + tree
是保存在磁盘中, 并通过mmap
的方式映射到内存用来查询操作。mmap
是一种内存映射文件的方法, 即将一个文件或者其对象映射到进程的内存地址空间, 实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。
b + tree
维护着revision
到value
的映射关系。也就是说当指定revision
查询数据的时候, 就可以通过该b + tree
直接返回数据。当我们通过watch
来订阅数据的时候, 也可以通过这个b + tree
维护的revision
到value
映射关系, 从而通过指定的revision
开始遍历这个b + tree
,拿到所有的数据更新。
在
etcd
内部还维护着另外一个b + tree
。它管理着key
到revision
的映射关系。当需要查询Key
对应数据的时候, 会通过etcd
内部的b + tree
, 将key
翻译成revision
。再通过磁盘中的b + tree
的revision
获取到对应的value
。在
etcd
中 一个数据是存在多个版本的。在
etcd
持续运行过程中会不断的发生修改, 意味着etcd
中内存及磁盘的数据都会持续增长。这对资源有限的场景来说是无法接受的。因此在etcd
中会周期性的运行一个Compaction
的机制来清理历史数据。 对于一个Key
的历史版本数据, 可以选择清理掉。
etcd mini-transactions
- etcd 的
transaction
机制比较简单, 基本可以理解为一段if else
程序, 在if
中可以提供多个操作。
|
|
在
etcd
内部会保证整个事务操作的原子性。也就是说If
操作所有的比较条件, 其看到的视图, 一定是一致的。同时它能够确保在争执条件中, 多个操作的原子性不会出现etc
仅执行了一半的情况。通过
etcd
提供的事务操作, 我们可以在多个竞争中去保证数据读写的一致性, 比如Kubernetes
, 它正是利用了etcd
的事务机制, 来实现多个Kubernetes API server
对同样一个数据修改的一致性。
Kubernetes
在使用etcd
做为元数据存储后元数据实现高可用, 无单点故障
系统无状态, 故障修复相对容易
系统可水平扩展, 横向提升性能以及容量
简化整体架构, 降低维护的复杂度
etcd lease
lease
是分布式系统中一个常见的概念, 用于代表一个租约。通常情况下, 在分布式系统中需要去检测一个节点是否存活的时候, 就需要租约机制。etcd
通过CreateLease(时间)
来创建一个租约。如:lease = CreateLease(10s)
创建一个 10s 过期的一个租约。通过 Put(key1, value1, lease) 可以将之前创建的 租约绑定到
key1
中(同一个租约可以绑定多个key)。当租约过期时,etcd
会自动清理key1
对应的value1
值。KeepAlive
方法: 可以续约租期。- 比如说需要检测分布式系统中一个进程是否存活, 那么就会在这个分布式进程中去访问
etcd
并且创建一个租约, 同时在该进程中去调用KeepAlive
的方法, 与etcd
保持一个租约不断的续约。当进程挂掉了, 租约在进程挂掉的一段时间就会被etcd
自动清理掉。所以可以通过这个机制来判定节点是否存活。
- 比如说需要检测分布式系统中一个进程是否存活, 那么就会在这个分布式进程中去访问
etcd 性能优化
etcd 性能分析
Etcd Server 硬件需求
etcd
硬件需求 (如下为官方提供的参考数据)小型集群
少于100个客户端, 每秒少于200个请求, 存储数据少于100MB。如: 少于50节点的Kubernetes
集群。
CPU | 内存 | 最大并发 | 磁盘吞吐量 |
---|---|---|---|
2核 | 4G | 1500IOPS | 50MB/S |
中型集群
少于500个客户端, 每秒少于1000个请求, 存储数据少于500MB。如: 少于250节点的Kubernetes
集群。
CPU | 内存 | 最大并发 | 磁盘吞吐量 |
---|---|---|---|
4核 | 16G | 5000IOPS | 100MB/S |
大型集群
少于1500个客户端, 每秒少于10000个请求, 存储数据少于1GB。 如: 少于1000节点的Kubernetes
集群。
CPU | 内存 | 最大并发 | 磁盘吞吐量 |
---|---|---|---|
8核 | 32G | 8000IOPS | 200MB/S |
超大型集群
大于1500个客户端, 每秒处理大于10000个请求, 存储数据大于1GB。如: 少于3000个节点的Kubernetes
集群
CPU | 内存 | 最大并发 | 磁盘吞吐量 |
---|---|---|---|
16核 | 64G | 15000IOPS | 300MB/S |
etcd 运维
etcd 集群数据备份
- 准备一些测试数据
|
|
备份 Etcd 快照
snapshot save
命令备份。在集群状态正常的情况下对任意一个节点进行数据备份都可以。
etcd v3.4
只能一个节点进行数据备份。Snapshot can only be requested from one etcd node
在使用
ETCD API
3 的情况下,只会备份 3 的数据。
|
|
- 删除创建的数据
|
|
etcd 集群数据恢复
恢复集群数据
etcdctl snapshot restore
命令恢复恢复数据, 需要将快照覆盖到所有
ETCD
集群节点。
节点-1
--data-dir
指定恢复数据的目录, 如果数据目录存在会报错, 可以选择删除原来的, 或者备份为新的目录。--name
每个节点的name
都不相同initial-cluster
配置为集群所有节点的 2380 内部通讯端口initial-cluster-token
必须配置与原来相同initial-advertise-peer-urls
配置为恢复节点的IP:2380
|
|
节点-2
--data-dir
指定恢复数据的目录, 如果数据目录存在会报错, 可以选择删除原来的, 或者备份为新的目录。--name
每个节点的name
都不相同initial-cluster
配置为集群所有节点的 2380 内部通讯端口initial-cluster-token
必须配置与原来相同initial-advertise-peer-urls
配置为恢复节点的IP:2380
|
|
节点-3
--data-dir
指定恢复数据的目录, 如果数据目录存在会报错, 可以选择删除原来的, 或者备份为新的目录。--name
每个节点的name
都不相同initial-cluster
配置为集群所有节点的 2380 内部通讯端口initial-cluster-token
必须配置与原来相同initial-advertise-peer-urls
配置为恢复节点的IP:2380
|
|
重启所有
etcd
服务- 如上恢复数据到新的数据目录
--data-dir
- 如上恢复数据到新的数据目录
|
|
- 备份原来的数据目录
|
|
- 使用新的数据
|
|
|
|
- 查看节点的情况
|
|
- 查询恢复数据
|
|