etcd 凭借自身高效的一致性共识协议 (Raft) 和对存储数据高效的组织结构 (B-Tree 和 B+Tree), 已事实上成为云原生社区 “水和电” 一般的基础设施, 其重要性不言而喻;
因此有必要了解一下 etcd 的相关原理知识;

etcd 的逻辑时钟

Revision 是 etcd 中用于标识数据变更顺序的全局逻辑时钟: 每次对 etcd 的写操作(如 Put、Delete)都会生成一个新的 Revision,且 Revision 是单调递增的;
Revision 是一个 64 位整数,由两部分组成:

  • 主版本号(Main Revision):高 56 位,表示全局的变更次数;
  • 子版本号(Sub Revision):低 8 位,表示同一主版本下的多次变更(例如,在一次事务中可能包含多个操作);

etcd 的索引

key 索引

etcd 使用了一个内存数据结构 kvindex 实现了对 key 的索引, kvindex 使用了 google 开源的 btree 库提供的一个高效的 B-Tree 实现, 支持并发操作和自定义的键值比较逻辑; 其中每个 B-Tree 节点存储了以下内容:

  • key: key 的名称, 用于索引;
  • 版本信息(Revision):键的版本号,用于记录键的修改历史;
  • 生成索引(Generation Index):用于跟踪键的生命周期(创建、更新、删除);
  • 其他元数据:如键的创建时间、修改时间等;

revision 索引

用于追踪 Key 的历史版本和变更事件, etcd 使用了 google 开源的 BoltDB 实现, 该单机数据库使用了 B+Tree 来维护 Revision 索引, 每个 Revision 都关联一个事件 (对应到一个或多个 Key-Value 的变化):

  • 每次对 etcd 的写事件 (如 Put、Delete) 都会生成一个新的全局单调递增的 Revision;
  • 通过 Revision 索引, 可以查询 Key 的历史版本或特定时间点的数据;

lease 索引

用于管理 Key 的租约(Lease)和生存时间(TTL):

  • 租约是一个时间期限,可以为 Key 设置租约,当租约到期时,Key 会自动删除;
  • etcd 维护一个 Lease 索引,记录每个租约及其关联的 Key;
  • 通过 Lease 索引,可以快速查找某个租约关联的所有 Key;

watch 索引

用于支持 Watch 机制,实时监听 Key 的变更事件:

  • etcd 维护一个 Watch 索引,记录每个客户端注册的 Watch;
  • 当 Key 发生变更时,etcd 会查找 Watch 索引,找到所有监听该 Key 的客户端,并发送事件通知;

 
Watch 索引的数据结构: watcherGroup, 它使用一个 Map 来存储每个 Key 对应的 watchers 列表:

  • keyWatchers:
    • key: 被监听的精确 key;
    • value: watchers 列表,包含所有监听该 Key 的客户端 Watch;
  • rangeWatchers:
    • key: 被监听的 key 的前缀 (使用 B-Tree 作为索引);
    • value: watchers 列表,包含所有监听该 Key 前缀的客户端 Watch;
1
2
3
4
5
6
7
8
9
10
type watcherGroup struct {
// 按 Key 分组的 watchers
keyWatchers map[string][]*watcher
}

type watcher struct {
key string // 被监听的 Key
startRev int64 // 起始 Revision
eventChan chan Event // 事件通道
}

具体举例:

  1. 用户发起 etcd watch 请求:

    1
    2
    3
    4
    5
    6
    7
     # client1 发起
    etcdctl watch /zookeeper
    etcdctl watch /zoo/ --prefix
    # client2 发起
    etcdctl watch /zoo/ --prefix
    etcdctl watch /zoo/tiger --prefix
    etcdctl watch /zoo/tiger-big
  2. etcd 注册 watch 对象:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    watcherGroup:
    - keyWatchers:
    +------------------------------+
    | /zookeeper -> [watcher1] |
    | /zoo/tiger-big -> [watcher2] |
    +------------------------------+
    - rangeWatchers:
    +------------------------------+
    | /zoo -> [watcher1, wathcer2] |
    | /zoo/tiger -> [watcher2] |
    +------------------------------+
  3. key /zoo/tiger-big 发生了变更, etcd 生成对应的事件;

  4. etcd 去 watcherGroup keyWatchers 查找有无 key 精确匹配, 如有则将事件下发到对应的 watchers;
  5. etcd 去 watcherGroup rangeWatchers 根据 B-Tree 索引查找有无匹配的 key 前缀, 如有则将事件下发到对应的 watchers;

compact 索引

用于管理数据压缩和历史数据的清理:

  • etcd 支持基于 Revision 的数据压缩,可以删除指定 Revision 之前的所有历史数据;
  • Compact 索引记录每次压缩操作的 Revision,确保不会误删未压缩的数据;

参考资料