"A distributed, reliable key-value store for the most critical data of a distributed system."
一个分布式、可靠 key-value 存储分布式系统。
应用场景
- 配置管理
- 服务注册发现
- 选主
- 应用调度
- 分布式队列
- 分布式锁
架构
- biltdb:Bolt是一个纯粹Key/Value模型的程序。该项目的目标是为不需要完整数据库服务器(如Postgres或MySQL)的项目提供一个简单,快速,可靠的数据库;
- Wal:预写式日志,etcd用于持久化存储的日志格式;
- snapshot:etcd防止WAL文件过多而设置的快照,存储etcd数据状态;
- Raft:etcd所采用的保证分布式系统强一致性的算法。
一个ETCD集群一般由3个或者5个节点组成,两个quorum一定存在交集,则
即:3个节点容忍1个节点故障,5个节点容忍2个节点故障,以此类推。
主要APIs
- Put(key, value) / Delete(key): 写入 / 删除数据
- Get(key) / Get(keyFrom, keyEnd): 查询操纵 / 范围查询
- Watch(key / keyPrefix): 根据key来watch / 根据前缀Watch(实际应用场景一般使用这种)
- Transactions(if / then / else ops) . Commit(): 事务操作
- Lease: Grant / Revoke / KeepAlive: Lease接口
etcd数据版本号机制
- term:Leader任期,Leader切换时,全局单调递增, 64bits
- revision:数据版本,数据发生变更,全局单调递增,64bits
- KeyValue:
- create_revision
- mod_revision
- version
etcd MVCC和stream watch
Put(key, value1) rev = 5
Put(key, value2) rev = 6
Get(key) --> value2
Get(key, rev=5) --> value1
...
默认为最新版本,可以指定版本号
watcher := Watch(key, rev)
for {
event := watcher.Recv()
handle(event)
...
}
指定旧版本,可以拿到从旧版本到当前的所有的数据版本更新
首先,所有的数据都保存在B+树(灰色),当我们指定了版本信息之后,会直接到灰色B+树中去获取相关的数据;同时,还有另外一个B+树,它维护了key和revions的映射关系,查询key的数据时候,会根据key查询到revision,再通过revision查询到相应的key。
- 一个数据多个版本;
- 通过定期的Compaction来清理历史数据。(见附)
etcd mini-transactions
Txn.If(
Compare(Value(key1),">", "bar"),
Compare(Version(key1),"=", 2),
...
).Then(
Put(key2, valueX)
Delete(key3)
...
).Else(
Put(key2, valueY)
...
).Commit()
- 当key1的值大于“bar”,且key1的版本等于2的时候,将key2的值设为valueX,并删除key3;
- 否则,将key2的值设为valueY;
- 提交。
etcd Lease 的概念和用法
- lease = CreateLease(10s)
- Put(key1, value1, lease)
- Put(key2, value2, lease)
- ......
- lease.KeepAlive():续约。
- lease.Revoke():清理。
租约,检测一个节点是否存活。key1和key2绑定到lease租约上。
将多个key绑定到同一个lease对象之上,大幅提高etcd性能。
如何保证一致性?
etcd 使用 raft 协议来维护集群内各个节点状态的一致性。简单说,etcd 集群是一个分布式系统,由多个节点相互通信构成整体对外服务,每个节点都存储了完整的数据,并且通过 Raft 协议保证每个节点维护的数据是一致的。
每个 etcd 节点都维护了一个状态机,并且,任意时刻至多存在一个有效的主节点。主节点处理所有来自客户端写操作,通过 Raft 协议保证写操作对状态机的改动会可靠的同步到其他节点。
数据模型
etcd 的设计目标是用来存放非频繁更新的数据,提供可靠的 Watch插件,它暴露了键值对的历史版本,以支持低成本的快照、监控历史事件。这些设计目标要求它使用一个持久化的、多版本的、支持并发的数据数据模型。
当 etcd 键值对的新版本保存后,先前的版本依然存在。从效果上来说,键值对是不可变的,etcd 不会对其进行 in-place 的更新操作,而总是生成一个新的数据结构。为了防止历史版本无限增加,etcd 的存储支持压缩(Compact)以及删除老旧版本。
逻辑视图
从逻辑角度看,etcd 的存储是一个扁平的二进制键空间,键空间有一个针对键(字节字符串)的词典序索引,因此范围查询的成本较低。
键空间维护了多个修订版本(Revisions),每一个原子变动操作(一个事务可由多个子操作组成)都会产生一个新的修订版本。在集群的整个生命周期中,修订版都是单调递增的。修订版同样支持索引,因此基于修订版的范围扫描也是高效的。压缩操作需要指定一个修订版本号,小于它的修订版会被移除。
一个键的一次生命周期(从创建到删除)叫做 “代 (Generation)”,每个键可以有多个代。创建一个键时会增加键的版本(version),如果在当前修订版中键不存在则版本设置为1。删除一个键会创建一个墓碑(Tombstone),将版本设置为0,结束当前代。每次对键的值进行修改都会增加其版本号 — 在同一代中版本号是单调递增的。
当压缩时,任何在压缩修订版之前结束的代,都会被移除。值在修订版之前的修改记录(仅仅保留最后一个)都会被移除。
物理视图
etcd 将数据存放在一个持久化的 B+ 树中,处于效率的考虑,每个修订版仅仅存储相对前一个修订版的数据状态变化(Delta)。单个修订版中可能包含了 B+ 树中的多个键。
键值对的键,是三元组(major,sub,type)
- major:存储键值的修订版;
- sub:用于区分相同修订版中的不同键;
- type:用于特殊值的可选后缀,例如 t 表示值包含墓碑。
键值对的值,包含从上一个修订版的 Delta。B+ 树 —— 键的词法字节序排列,基于修订版的范围扫描速度快,可以方便的从一个修改版到另外一个的值变更情况查找。
etcd 同时在内存中维护了一个 B 树索引,用于加速针对键的范围扫描。索引的键是物理存储的键面向用户的映射,索引的值则是指向 B+ 树修该点的指针。
典型使用场景
元数据存储——Kubernetes
- 元数据高可用,无单点故障;
- 系统无状态,故障修复方案简单;
- 系统可水平扩展,提高性能及容量;
- 简化架构实现,降低系统工程复杂性。
Service Discovery(Name Service)
- 资源注册;
- 存活性检测;
- API Gateway无状态,可水平扩展;
- 支持上万个进程的规模。
Distributed Coordination: Leader Election
- 分部署系统设计模式(多个master选举一个leader对外服务)
- 选主;
- 注册IP;
- 获取主节点地址。
- 分布式系统并发控制
- 分布式信号量;
- 自动踢出故障节点;
- 存储进程执行状态。
附
1.--auto-compaction-retention
由于ETCD数据存储多版本数据,随着写入的主键增加历史版本需要定时清理,默认的历史数据是不会清理的,数据达到2G就不能写入,必须要清理压缩历史数据才能继续写入;
所以根据业务需求,在上生产环境之前就提前确定,历史数据多长时间压缩一次;例如生产环境现在升级后是默认一小时压缩一次数据。这样可以极大的保证集群稳定,减少内存和磁盘占用。
2.--max-request-bytes
etcd Raft消息最大字节数,ETCD默认该值为1.5M; 但是很多业务场景发现同步数据的时候1.5M完全没法满足要求,所以提前确定初始值很重要; 由于1.5M导致我们线上的业务无法写入元数据的问题,
例如升级之后把该值修改为默认32M,但是官方推荐的是10M,可以根据业务情况自己调整。
3.--quota-backend-bytes
ETCDdb数据大小,默认是2G,当数据达到2G的时候就不允许写入,必须对历史数据进行压缩才能继续写入;参加1里面说的,启动的时候就应该提前确定大小,官方推荐是8G。
/usr/bin/etcd --auto-compaction-retention '1' --max-request-bytes '33554432' --quota-backend-bytes '8589934592'