etcd 与其他键值存储的比较

etcd 的历史与使用,以及与其他工具的比较

“etcd”这个名字来源于两个概念:Unix 系统中的“/etc”目录和“d”istributed(分布式)。在单个系统中,“/etc”目录用于存储配置数据,而 etcd 则用于存储大规模分布式系统的配置信息。因此,“分布式 /etc”即为“etcd”。

etcd 被设计为大规模分布式系统的通用基础组件。这类系统绝不容忍脑裂(split-brain)现象的发生,并愿意以牺牲可用性为代价来确保这一点。etcd 以一致且容错的方式存储元数据。etcd 集群旨在提供具备业界最佳稳定性、可靠性、可扩展性和性能的键值存储服务。

分布式系统使用 etcd 作为一致的键值存储,用于配置管理、服务发现以及协调分布式任务。许多组织使用 etcd 构建生产级系统,例如容器调度器、服务发现服务和分布式数据存储。常见的基于 etcd 的分布式模式包括领导者选举分布式锁以及监控机器存活状态。

使用场景

  • CoreOS 的 Container Linux:Container Linux 上运行的应用程序能够自动完成零停机时间的 Linux 内核更新。Container Linux 使用locksmith来协调更新过程。locksmith 基于 etcd 实现了一个分布式信号量,确保集群中任意时刻只有部分节点正在重启。
  • Kubernetes 将服务发现和集群管理所需的配置数据存储到 etcd 中;etcd 的一致性对正确调度和运行服务至关重要。Kubernetes API 服务器将集群状态持久化保存至 etcd,并利用 etcd 的 watch API 监控集群状态变化,推动关键配置的更新。

对比图表

也许 etcd 看起来已经是一个不错的选择,但与所有技术决策一样,请谨慎行事。请注意,本文档由 etcd 团队编写。尽管理想情况是对技术和功能进行客观比较,但作者的专业背景和倾向显然更偏向于 etcd。请仅按指导使用。

下表是一个方便的快速参考,可帮助您一目了然地识别 etcd 及其最流行替代方案之间的差异。表格之后的章节将对每一列提供进一步的评论和详细说明。

etcdZooKeeperConsulNewSQL(Cloud Spanner、CockroachDB、TiDB)
并发原语锁 RPC选举 RPC命令行锁命令行选举、Go 语言中的实现示例Java 中通过外部curator 实现示例原生锁 API极少或没有
线性一致性读取有帮助没有帮助有帮助有时支持
多版本并发控制有帮助没有帮助没有帮助有时支持
事务字段比较、读取、写入版本检查、写入字段比较、加锁、读取、写入类 SQL 方式
变更通知历史及当前的键区间当前的键和目录当前的键和前缀触发器(有时)
用户权限基于角色访问控制列表(ACLs)访问控制列表(ACLs)各不相同(按表的GRANT,按数据库的角色
HTTP/JSON API有帮助没有帮助有帮助很少
成员重新配置有帮助>3.5.0有帮助有帮助
最大可靠的数据库大小数吉字节数百兆字节(有时可达数吉字节)数百兆字节太字节以上
最小读取线性化延迟网络往返时间(RTT)无读取线性化RTT + fsync时钟屏障(原子操作,NTP)

ZooKeeper

ZooKeeper 与 etcd 解决的是相同的问题:分布式系统的协调和元数据存储。然而,etcd 的设计得益于在工程和运维方面对 ZooKeeper 设计与实现的经验总结。从 ZooKeeper 中吸取的教训无疑影响了 etcd 的设计,帮助其支持 Kubernetes 等大规模系统。etcd 相较于 ZooKeeper 的改进包括:

  • 动态集群成员重新配置
  • 在高负载下稳定的读写性能
  • 多版本并发控制(MVCC)数据模型
  • 可靠的键监控,不会静默丢弃事件
  • 租约原语,将连接与会话解耦
  • 用于安全分布式共享锁的 API

此外,etcd 开箱即用地支持多种语言和框架。ZooKeeper 使用其独有的 Jute RPC 协议,该协议完全专属于 ZooKeeper,限制了其支持的语言绑定;而 etcd 的客户端协议基于流行的 RPC 框架 gRPC 构建,支持 Go、C++、Java 等多种语言。同样,gRPC 可以序列化为通过 HTTP 传输的 JSON,因此即使是通用命令行工具如 curl 也能与其通信。由于系统可以从多种选择中进行挑选,因此它们可以使用原生工具构建在 etcd 之上,而不是围绕 etcd 使用单一固定的技术栈。

在考虑功能、支持和稳定性时,计划使用 ZooKeeper 作为一致性键值存储的新应用最好选择 etcd 取而代之。

Consul

Consul 是一个端到端的服务发现框架。它提供内置的健康检查、故障检测和 DNS 服务。此外,Consul 还通过 RESTful HTTP API 暴露了一个键值存储。就 Consul 1.0 的现状而言,其存储系统在键值操作方面的扩展性不如 etcd 或 ZooKeeper 等其他系统;需要处理数百万个键的系统将面临高延迟和内存压力。其键值 API 明显缺少多版本键、条件事务以及可靠的流式监控功能。

etcd 和 Consul 解决的是不同的问题。如果需要一个分布式一致的键值存储系统,etcd 是比 Consul 更好的选择。但如果需要端到端的集群服务发现功能,etcd 就不够用了;此时应选择 Kubernetes、Consul 或 SmartStack。

NewSQL(Cloud Spanner、CockroachDB、TiDB)

etcd 和 NewSQL 数据库(例如 CockroachTiDBGoogle Spanner)都能在高可用的前提下提供强数据一致性保证。但由于系统设计参数存在显著差异,它们的客户端 API 和性能特征也大不相同。

NewSQL 数据库旨在跨数据中心实现水平扩展。这类系统通常将数据划分为多个独立的一致性复制组(分片),可能分布较远,存储的数据量可达数 TB 甚至更多。这种扩展方式导致其不适合用于分布式协调任务,因为它们依赖时钟同步而带来较长延迟,并且期望更新操作之间的依赖图主要集中在局部。数据以表的形式组织,支持类 SQL 查询功能,语义比 etcd 更丰富,但代价是查询处理、计划和优化带来了更高的复杂性。

简而言之,若需存储元数据或协调分布式应用,请选择 etcd。如果需要存储超过几 GB 的数据,或需要完整的 SQL 查询能力,则应选择 NewSQL 数据库。

使用 etcd 存储元数据

etcd 在单个一致性复制组内复制所有数据。对于最多几 GB 的数据存储且要求一致排序的场景,这是最高效的方案。每次对集群状态的修改(可能涉及多个键)都会被分配一个全局唯一的 ID,在 etcd 中称为“修订号”(revision),该 ID 来自一个单调递增的计数器,用于判断操作顺序。由于只有一个复制组,修改请求只需通过 Raft 协议提交即可。通过将共识限制在一个复制组内,etcd 以简单的协议实现了分布式一致性,同时获得低延迟和高吞吐的表现。

etcd 的复制机制无法水平扩展,因为它缺乏数据分片能力。相比之下,NewSQL 数据库通常将数据分片到多个一致性复制组中,可存储 TB 级甚至更大的数据集。但为了给每个修改分配全局唯一且递增的 ID,每个请求必须在多个复制组之间执行额外的协调协议。这一额外的协调步骤可能导致全局 ID 冲突,迫使原本有序的请求重试。结果是,相比 etcd,这种方式更为复杂,且在严格排序场景下的性能通常更差。

如果应用程序主要处理元数据或元数据的顺序(例如用于协调进程),请选择 etcd。如果应用程序需要一个跨越多个数据中心的大规模数据存储,且不高度依赖强全局顺序特性,则应选择 NewSQL 数据库。

使用 etcd 进行分布式协调

etcd 开箱即用地提供了分布式协调原语,例如事件监听、租约、选举以及分布式共享锁(需要注意的是,在分布式共享锁的情况下,用户必须了解其一些非显而易见的特性。详细内容见下文)。这些原语均由 etcd 开发团队维护和支持;如果将这些基础功能推给外部库,则相当于推卸了构建基础分布式软件的责任,实质上会使系统变得不完整。NewSQL 数据库通常期望这些分布式协调原语由第三方提供。同样,ZooKeeper 著名地拥有一个独立且分离的协调配方库。Consul 虽然提供了原生的锁定 API,但甚至会为其“并非一种防弹方法”而道歉。

理论上,可以在任何提供强一致性的存储系统之上构建这些原语。然而,相关算法往往非常微妙;很容易设计出一个看似正常工作的锁算法,却因惊群效应或时序偏差而突然失效。此外,etcd 支持的其他原语(例如事务内存)依赖于 etcd 的 MVCC 数据模型;仅仅具备强一致性是不够的。

在分布式协调方面,选择 etcd 可以帮助避免运维难题并节省工程投入。

关于锁和租约使用的说明

etcd 提供了基于租约机制及其在 etcd 中的实现方式锁 API。租约机制的基本思想是:服务器向请求客户端授予一个称为租约(lease)的令牌。当服务器授予租约时,会为其关联一个 TTL(生存时间)。当服务器检测到经过的时间超过该 TTL 时,就会撤销该租约。只要客户端持有的租约未被撤销,它就可以声明自己拥有与该租约相关联的资源的访问权。在 etcd 中,这个资源就是 etcd 键空间中的某个键。etcd 基于此机制提供了锁 API。然而,这些锁 API 本身并不能直接用作互斥机制。之所以称其为“锁”,是出于历史原因。不过,这些 API 可以如以下所述,作为互斥机制的一种优化手段来使用。

租约机制最重要的特点是,TTL 被定义为一个物理时间间隔。服务器和客户端各自使用自己的时钟来测量时间的流逝。这可能导致一种情况:服务器已经撤销了租约,但客户端仍认为自己持有该租约。

那么,租约机制如何保证锁机制的互斥性呢?事实上,租约机制本身并不能保证互斥性。持有租约并不能确保持有者真正拥有对该资源的锁。

在使用 etcd 锁控制对 etcd 自身键的互访时,互斥性是基于版本号验证机制实现的(在 Consul 等其他系统中,这种机制有时被称为比较并交换 compare and swap)。在 etcd 的 RPC 操作(如 PutTxn)中,我们可以为操作指定关于修订版本号和租约 ID 的前提条件。如果条件不满足,操作将失败。借助这一机制,etcd 为客户实现了分布式锁。这意味着,当客户端的请求被 etcd 集群成功处理后,客户端便可确认自己已获取了对应键的锁。

在分布式锁的相关文献中,也描述了类似的设计:

  • Chubby 论文中,提出了 sequencer(序列器)的概念。我们认为,sequencer 与 etcd 中的修订版本号和租约 ID 的组合几乎等同。
  • 如何实现分布式锁一文中,Martin Kleppmann 提出了围栏令牌(fencing token)的概念。作者认为,在 etcd 的场景中,围栏令牌即为修订号。
  • 分布式系统中同步时钟的实际应用一文中,我们可以找到一段描述:Thor 实现了一种基于版本号验证和租约的分布式锁机制。

如果 etcd 和其他系统已经提供了基于版本号验证的互斥机制,为何还要提供租约?实际上,租约提供了一种优化机制,可减少被中止的请求数量。

需要注意的是,对于 etcd 中的键而言,由于具备租约和版本号验证机制,可以高效地实现加锁。但如果用户需要保护与 etcd 无关的资源,则这些资源自身必须提供类似 etcd 键那样的版本号验证机制以及副本一致性保障。etcd 自身的锁功能无法用于保护外部资源。


最后更新于 2025 年 6 月 3 日:递归地将 v3.6 的内容复制到 v3.7(a90b2a6)