目录
一:etcd是什么
二:为什么k8s里要用etcd
三:etcd相关名词
四:etcd数据架构
V2和V3版本的区别?
为什么V3版本支持数据持久化?
为什么用boltdb存储?
历史版本数据如何清理?
五:etcd存储过程
etcd如何实现的数据持久化?
etcd高可用性实现方式?
六:Raft 保证数据一致性
Raft 中一个 Term(任期)是什么意思?
Raft 状态机是怎样切换的?
启动后如何选举 Leader 节点?
客户端从集群中的哪个节点读写数据?
etcd 的节点增多算法性能如何?
七:k8s中etcd操作
1)通过etcdhelper工具查看etcds数据信息
2)构建镜像,编译go
3)cp编译后的go文件
4)通过etcdhelper查看etcd里的数据信息
etcd是一个分布式、一致性的键值存储系统,主要用于配置共享和服务发现。
etcd 的 K/V 存储
v3版本的数据存储没有目录层级关系了,而是采用平展(flat)模式,换句话说/a与/a/b并没有嵌套关系,而只是key的名称差别而已, 没有目录的概念,但是key名称支持/字符,从而实现看起来像目录的伪目录,但是存储结构上不存在层级关系。
etcd 目前支持 V2 和 V3 两个大版本,这两个版本在实现上有比较大的不同。
一方面是对外提供接口的方式,V3提供grpc调用,提高性能。
另一方面就是底层的存储引擎,V2 版本的实例是一个纯内存的实现,所有的数据都没有存储在磁盘上(快照和日志数据除外),而 V3 版本的实例就支持了数据的持久化。
由于etcd v3 实现了MVCC(每一个写操作都会创建一个新版本的数据,读操作会从有限多个版本的数据中挑选一个“最合适” 要么是最新版本,要么是指定版本的结果直接返回),保存了每个key-value pair的历史版本,数据量大了很多,不能将整个数据库都放在内存里了。 因此etcd v3摒弃了内存数据库,转为磁盘数据库,整个数据库都存储在磁盘上,底层的存储引擎使用的是BoltDB。
BoltDB是基于B树和mmap的数据库,基本原理是用mmap将磁盘的page映射到内存的page,而操作系统则是通过COW (copy-on-write) 技术进行page管理,通过cow技术,系统可实现无锁的读写并发,但是无法实现无锁的写写并发,这就注定了这类数据库读性能超高,但写性能一般,因此非常适合于“读多写少”的场景。同时BoltDB支持完全可序列化的ACID事务。因此最适合作为etcd的底层存储引擎。
etcd提供了命令行工具以及配置选项,供用户手动删除老版本数据,或者每隔一段时间定期删除老版本数据,etcd中称这个删除老版本数据的操作为数据压缩(compact)。
raft
中有重要的组件:
WAL
:预写日志器,用于以顺序形式写入操作记录,以便故障时数据恢复;Snapshot
:数据快照,一般用于启动时快速恢复数据;上图中8、9、11 是涉及 I/O 的操作,其他均为内存操作。
对WAL
的操作在每次写事务操作中都会存在,因此其是制约etcd
写性能的一个重要因素。
etcd对数据的持久化,采用的是binlog预写日志(也称为WAL, 即Write-Ahead-Log)加Snapshot(快照)的方式。在计算机科学中,预写式日志(Write-Ahead-Log,WAL)是关系数据库系统中用于提供原子性和持久性的一系列技术。在使用WAL的系统中,所有的修改在提交之前都要先写人log文件中。etcd数据库的所有更新操作都需要先写到binlog中,而binlog是实时写到磁盘上的,因此这样就可以保证不会丢失数据,即使机器断电,重启以后etcd也能通过读取并重放binlog里的操作记录来重建整个数据库。
etcd数据的高可用和一致性是通过Raft来实现的,Master节点会通过Raft协议向Slave节点复制binlog, Slave节点根据binlog对操作进行重放,以维持数据的多个副本的一致性。也就是说binlog不仅仅是实现数据库持久化的一种手段,其实还是实现不同副本间一致性协议的最重要手段。客户端对数据库发起的所有写操作都会记录在binlog中,待主节点将更新日志在集群多数节点之间完成同步以后,便在内存中的数据库中应用该日志项的内容,进而完成一次客户的写请求。
如果一个etcd集群运行了很久,那么就会有很多binlog,这样在故障恢复时,需要花很多时间来复原数据,这时候就需要快照系统,它会把当前存储的当前数据存储下来。然后删除生成快照之前的log内容,这样只需要重现少量的log就能恢复数据了。
etcd v3的日志管理和快照管理的流程与v2的基本一致,区别是做快照的时候etcd v2是把内存里的数据库序列化成JSON,然后持久化到磁盘,而etcd v3是读取磁盘里的数据库的当前版本(从BoltDB中读取),然后序列化到磁盘。
Raft 算法中,从时间上,一个 Term(任期)即从一次竞选开始到下一次竞选开始之间。从功能上讲,如果 Follower 接收不到 Leader 的心跳信息,就会结束当前 Term,变为 Candidate 继而发起竞选,继而帮助 Leader 故障时集群的恢复。发起竞选投票时,Term Value 小的 Node 不会竞选成功。如果 Cluster 不出现故障,那么一个 Term 将无限延续下去。另外,投票出现冲突也有可能直接进入下一任再次竞选。
Raft 刚开始运行时,Node 默认进入 Follower 状态,等待 Leader 发来心跳信息。若等待超时,则状态由 Follower 切换到 Candidate 进入下一轮 Term 发起竞选,等到收到 Cluster 的 “多数节点” 的投票时,该 Node 转变为 Leader。Leader 有可能出现网络等故障,导致别的 Nodes 发起投票成为新 Term 的 Leader,此时原先的 Old Leader 会切换为 Follower。Candidate 在等待其它 Nodes 投票的过程中如果发现已经竞选成功了一个 Leader,那么也会切换为 Follower。
假设 etcd Cluster 中有 3 个 Node,Cluster 启动之初并没有被选举出的 Leader。此时,Raft 算法使用随机 Timer 来初始化 Leader 选举流程。比如说上面 3 个 Node 上都运行了 Timer(每个 Timer 的持续时间是随机的),而 Node1 率先完成了 Timer,随后它就会向其他两个 Node 发送成为 Leader 的请求,其他 Node 接收到请求后会以投票回应然后第一个节点被选举为 Leader。
成为 Leader 后,该 Node 会以固定时间间隔向其他 Node 发送通知,确保自己仍是 Leader。有些情况下当 Follower 们收不到 Leader 的通知后,比如说 Leader 节点宕机或者失去了连接,其他 Node 就会重复之前的选举流程,重新选举出新的 Leader。
读取:可以从任意 Node 进行读取,因为每个节点保存的数据是强一致的。
写入:etcd Cluster 首先会选举出 Leader,如果写入请求来自 Leader 即可直接写入,然后 Leader 会把写入分发给所有 Follower;如果写入请求来自其他 Follower 节点那么写入请求会给转发给 Leader 节点,由 Leader 节点写入之后再分发给集群上的所有其他节点。
Node 数量越多,由于数据同步涉及到网络延迟,会根据实际情况越来越慢,而读性能会随之变强,因为每个节点都能处理用户请求。
GitHub - webner/etcdhelper
修改Dockfile
FROM golang:1.11-alpine
WORKDIR /go/src/app
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o server *.go
CMD [ "tail", "-f"]
docker build -t etcd/etcdhelper:v1 .
docker run --name etcdhelper -d --rm etcd/etcdhelper:v1
docker cp etcdhelper:/go/src/app/server /tmp
mv /tmp/server /tmp/etcdhelper
etcdhelper -cacert /etc/kubernetes/ssl/ca.pem -key /etc/kubernetes/ssl/etcd-key.pem -cert /etc/kubernetes/ssl/etcd.pem -endpoint https://10.19.14.11:2379 ls /registry/deployments/default/
etcdhelper -cacert /etc/kubernetes/ssl/ca.pem -key /etc/kubernetes/ssl/etcd-key.pem -cert /etc/kubernetes/ssl/etcd.pem -endpoint https://10.19.14.11:2379 get /registry/deployments/default/nginx-deployment
参考连接:
Kubernetes之etcd操作 – fage's Blog
分布式键值存储 etcd - 知乎
etcd — 架构原理_mb60ed300273df6的技术博客_51CTO博客