日常使用golang开发,以及日常需要运维Kubernetes,所以一直不太习惯使用zookeeper或者nacos,反而使用etcd会多一点,有时候需要写一些分布式系统,使用etcd也可以很好的解决分布式系统一致性的问题。总的来说,我觉得etcd在以下3个领域的使用,很好用。
- 传统的nosql使用场景
- 基于ttl机制实现服务发现
- 分布式系统数据一致性提供者
名词解释
- Wal: 存放预写式日志,最大的作用是记录了整个数据变化的全部历程。在 etcd 中,所有数据的修改在提交前,都要先写入到 wal 中,有些类似于mysql的bin.log。
- Snap: 存放快照数据,etcd 防止 WAL 文件过多而设置的快照,存储 etcd 数据状态。
- Follower:竞选失败的节点作为Raft中的从属节点,为算法提供强一致性保证。
- Candidate:当Follower超过一定时间接收不到Leader的心跳时转变为Candidate开始竞选。
- Term:某个节点成为Leader到下一次竞选时间,称为一个Term。
- Index:数据项编号。Raft中通过Term和Index来定位数据。
- Revision: etcd为了实现了 MVCC(多版本并发控制),每当存储的数据发生改变,etcd 就会把全局的 revision 加一来表示产生了一个新的版本,并会保留每一个版本的数据。
存储
etcd 和 redis 不一样,redis虽然也有持久化功能,但是存放的其实是对应的备份数据,而etcd则不一样,etcd由于mvcc的特性,保留的版本巨多,所以在内存中,维护的其实是一套基于B树的二级索引,通过key以及revision进行value的查询。
非持久化存储(内存)
和预想的不太一样,V3版本的etcd不会在内存中维护完整的整个k-v的关系数据,考虑到如果维护整套k-v数据在内存里,一旦数据量太大,会导致全面崩盘,且etcd本身的Wal以及快照机制,需要定期将数据写入磁盘,也会导致性能问题。
通过结构体来模拟一下数据的取出过程,key为业务的key,value为keyIndex
// keyIndex 结构体
type keyIndex struct {
key []byte // 用户的key名称
modified revision // 最后一次修改key时的etcd版本号
generations []generation // generation保存了一个key若干代版本号信息,每代中包含对key的多次修改的版本号列表
}
// generations 表示一个key从创建到删除的过程,每代对应key的一个生命周期的开始与结束。当你第一次创建一个 key 时,会生成第 0 代,后续的修改操作都是在往第 0 代中追加修改版本号。当你把 key 删除后,它就会生成新的第 1 代,一个 key 不断经历创建、删除的过程,它就会生成多个代。
type generation struct {
ver int64 // 表示此key的修改次数
created revision // 表示generation结构创建时的版本号
revs []revision // 每次修改key时的revision追加到此数组
}
// revision 结构体
type revision struct {
main int64// 一个全局递增的主版本号,随put/txn/delete事务递增,一个事务内的key main版本号是一致的
sub int64// 一个事务内的子版本号,从0开始随事务内put/delete操作递增
}
业务做数据查询的时候,一般是基于Key进行查询的,以Kubernetes为例,创建资源使用的yaml本质上也是个k-v格式的描述性文件,查询etcd时候所用的key,其实就是一串接口的url,且kubernetes的内部交互非常频繁,出于效率考虑,etcd会在内存中维护一个基于B-Tree的二级索引,即上述的keyIndex,KeyIndex 维护了用户 key 与版本号关系,generations 表示一个 key 的生命周期。综上所述,在内存中,etcd维护了数据的key以及其版本信息的关系。
持久化存储
etcd默认使用BoltDB来进行持久化存储,BoltDB中的B+树存储如下所示,B+树的非叶子节点存储的是revision, revision从上面struct可以看出,包含两个字段,一个是全局递增的主版本号,一个是单个事务内的子版本号。
常见运维
# 由于可能存在多个endpoint,所以下面全部忽略地址,--endpoints="http://127.0.0.1:2379"
# 查看etcd集群当前状态
ETCDCTL_API=3; etcdctl --write-out=table endpoint status
# 备份
etcdctl snapshot save backup.db
#获取当前全局revision
rev=$(ETCDCTL_API=3 etcdctl endpoint status --write-out="json" | egrep -o '"revision":[0-9]*' | egrep -o '[0-9].*')
# 压缩指定版本号之前的数据
ETCDCTL_API=3; etcdctl compact $rev
# 整理多余的空间
ETCDCTL_API=3; etcdctl defrag
# 取消告警信息
ETCDCTL_API=3; etcdctl alarm disarm
# 将快照恢复成数据文件
export ETCDCTL_API=3
etcdctl snapshot restore /home/chenqionghe/etcdTest/backup/snapshot.db \
--data-dir="/home/chenqionghe/etcdTest/node1.etcd.restore" \
--name node1 \
--initial-cluster "node1=http://127.0.0.1:11380,node2=http://127.0.0.1:12380,node3=http://127.0.0.1:13380" \
--initial-cluster-token etcdv3-cluster \
--initial-advertise-peer-urls http://127.0.0.1:11380
export ETCDCTL_API=3
etcdctl snapshot restore /home/chenqionghe/etcdTest/backup/snapshot.db \
--data-dir="/home/chenqionghe/etcdTest/node2.etcd.restore" \
--name node2 \
--initial-cluster "node1=http://127.0.0.1:11380,node2=http://127.0.0.1:12380,node3=http://127.0.0.1:13380" \
--initial-cluster-token etcdv3-cluster \
--initial-advertise-peer-urls http://127.0.0.1:12380
export ETCDCTL_API=3
etcdctl snapshot restore /home/chenqionghe/etcdTest/backup/snapshot.db \
--data-dir="/home/chenqionghe/etcdTest/node3.etcd.restore" \
--name node3 \
--initial-cluster "node1=http://127.0.0.1:11380,node2=http://127.0.0.1:12380,node3=http://127.0.0.1:13380" \
--initial-cluster-token etcdv3-cluster \
--initial-advertise-peer-urls http://127.0.0.1:13380
# 恢复etcd就是重新启动etcd,并指向新的数据文件即可
常见报错
Erro: mvcc database space exceeded
一般是etcd未开启数据压缩以及自动清理,且数据目录的空间设置的太小,临时清理可以使用上述日常操作的清理命令进行操作,或者在启动etcd的时候添加以下参数
# 表示每隔一个小时自动压缩一次
--auto-compaction-retention=1
# 磁盘空间调整为 8G,官方建议最大 8G(单位是字节)
--quota-backend-bytes=8388608000
# 如果是给kubernetes集群使用,可以在集群的api-server的配置里,进行以下配置的修改
-etcd-compaction-interval = 5m