读《云原生分布式存储基石:etcd 深入解析》

最近一个月时间一直都在忙着搬家,国庆又带老婆回家待了几天,一直都没什么时间静下心来看看书,终于国庆过完了,又开一个新坑。这本书是华为做容器服务的团队写的,etcd 的源码大家多多少少都看过一些,这本书系统过了一下整个 etcd 的设计和一些重要的设计亮点和实现原理,对于我们自己对 etcd 的理解也能有一个梳理和印证。

本书分为三个大部分,分布式基础、 etcd 实战以及源码分析,总共十二章。

分布式基础

分布式系统的设计目标主要有以下几个方面:

  • 可用性:可用性是分布式系统的核心需求,其用于衡量一个分布式系统持续对外提供服务的能力。可用性方面,主要是通过系统冗余(备份、多节点)来保证容灾。
  • 可扩展性:增加机器后不会改变或极少改变系统行为,并且能获得近似线性的性能提升。这一点对于业务增长来说十分重要。
  • 容错性:系统发生错误时,具有对错误进行规避以及从错误中恢复的能力。数据中心能够出现的错误往往超过你的想象,怎么在出现错误时提供稳定的服务,这一点在设计时也需要深入思考。
  • 性能:对外服务的响应延时和吞吐率要能满足用户的需求。这点就不多说了,尤其是 ToC的业务,系统响应往往决定了用户的留存。

这里又要老生常谈 CAP 了,我们复习一下,CAP 分别是一致性(Consistency)、可用性(Availability)和分区容忍性(Tolerance to the partition of network)。分布式环境下网络分区是常态,所以分布式系统往往是AP 或者 CP,要么提高可用性降低一致性需求,要么要求强一致性但是可用性会降低。一般除了一些特定的场景(比如金融业务)会要求 CP,其他基本都是以 AP 为主,满足最终一致性即可。

我们重点聊聊一致性,首先什么是一致性,在分布式场景下,用通俗的话来说,就是怎么保证多个副本的数据是否一致,或者说系统对外呈现的状态是否一致。对于一致性,可以分别从客户端和服务端两个不同的视角来理解。从客户端来看,一致性主要是指多并发访问时如何获取更新过的数据的问题。从服务端来看,则是更新如何复制分布到整个系统,以保证数据最终的一致性。因此,可以从两个角度来查看一致性模型:以数据为中心的一致性模型和以用户为中心的一致性模型。

以数据为中心的一致性模型常见的四种语义如下(强 → 弱):

  1. 强一致性(Strong Consistency):任意时刻所有副本对读操作都返回最新写入的值,etcd 通过 Raft 日志同步来实现这一点。
  2. 顺序一致性(Sequential Consistency):保证所有操作遵守同一全序,但写入值传播可延迟,读到旧数据是允许的。
  3. 因果一致性(Causal Consistency):只要求具有因果关系的操作按顺序传播,独立事件的顺序可以不同副本各自决定。
  4. 可串行化一致性(Serializable Consistency):所有操作可映射为某个串行执行次序,常用于数据库事务模型。

从用户角度,还可以定义读己之所写(Read-your-writes)、会话一致性(Session Consistency)等保证,对 API 设计和客户端缓存策略很重要。

以用户为中心的一致性模型主要是指实际业务需求中需要满足针对某个用户业务的一致性,比如最终一致性,即允许一个不一致性窗口,只要最终所有副本达到一致性即可。

当同一份数据存在多个副本时,我们通过复制状态机来管理它们。复制状态机(Replicated State Machine)是一种分布式系统的设计模式,用于确保在多个节点上保持相同的状态副本。在复制状态机模型中,多个服务器节点通过相互通信和协作来保持一致的状态,并且客户端可以向任意一个节点发送请求,系统会确保处理请求的顺序和结果在所有节点上都是一致的。

复制状态机的核心思想是将分布式系统中的状态看作是一个状态机,这个状态机会接收客户端的请求并且按照特定的顺序进行处理,从而达到一致的状态。为了实现复制状态机,通常会使用一种称为共识算法(Consensus Algorithm)的技术,例如Paxos、Raft等。这些共识算法可以确保系统中的节点在面对故障和网络分区等问题时,依然能够保持一致的状态。

FLP(Fisher, Lynch, Paterson)定理是分布式计算领域的一个基本定理,指出在异步网络中,当存在一个或多个节点发生故障时,不可能设计一个算法,能够在有限的时间内,保证所有节点对一个共享的变量(例如确定某个决策)达成一致的协议。简而言之,FLP定理指出,在异步网络中,不可能同时满足一致性(Consistency)、可用性(Availability)和分区容忍性(Partition Tolerance)这三个分布式系统设计的基本要求。

具体来说,FLP定理的内容是:在一个异步网络中,如果有一个或多个节点可能发生故障,且网络不提供时钟同步(即消息传递没有上限,消息可能在任何时间间隔内被传递),那么在有限的时间内不可能设计一个分布式算法,能够确保所有节点对某个共享的变量达成一致的协议。

这个定理的结论有重要的实际意义,它告诉我们,在异步网络环境下,无法设计一个能够在所有情况下都保证一致性的分布式算法。因此,在实际系统设计中,需要根据具体需求和系统特性,权衡一致性、可用性和分区容忍性,选择适当的系统模型和算法。根据FLP定理,实际的一致性协议(Paxos、Raft等)在理论上都是有缺陷的,最大的问题是理论上存在不可终止性!至于Paxos和Raft协议在工程的实现上都做了哪些调整(例如,Paxos和Raft都通过随机的方式显著降低了发生算法无法终止的概率),从而规避了理论上存在的哪些问题,下文将会有详细的解释。

书中接下来讲到了 Paxos 、 Raft算法,这两个协议在网上都有大量的介绍,我们不多赘述,只简单说一下 Raft,Raft把一致性问题分解成了领袖选举(leader election)、日志复制(log replication)、安全性(safety)和成员关系变化(membership changes)这几个子问题,我们在面试时候如果聊到 Raft 可以从这几个角度逐一介绍。

实战篇

为什么使用 etcd?在聊到 etcd 的时候,我们往往会聊到类似的其他系统,比如 zookeeper,与 zookeeper 相比,etcd 更加稳定可靠。在服务发现的实现上,etcd使用的是节点租约(Lease),并且 支持Group(多key);而ZooKeeper使用的是临时节点。etcd支持稳定的watch,而不是ZooKeeper一样简单的单次触发式 (one time trigger)watch。etcd支持MVCC(多版本并发控制),因为有协同系统需要无锁操作。 etcd 支持更大的数据规模,支持百万到千万级别的 key。 etcd 性能更好,在 3 台八核云服务器上部署的 etcd v3 可以实现每秒数万次的写操作和数十万次的读操作。

etcd 的官方定义是“A distributed, reliable key-value store for the most critical data of a distributed system.”本质上是为了提供可靠的分布式键值存储,用来存储分布式系统中的最关键的数据。 etcd 的常见使用场景有:服务发现、分布式锁、分布式数据队列、分布式通知和协调、主备选举等。(p.s.我自己就是做服务发现相关的业务,不过是在一个没有强一致性的环境,在这种场景下怎么保证服务发现的一致性其实也是一个很有意思的问题,我们在生产环境做了大量的 workaround,这个以后有机会可以聊一下)

etcd 基于 Raft 协议,通过日志复制来保证数据的强一致性。写请求会先提交到 Leader,写入 WAL 后再复制到其他成员,只有在多数派确认后才算提交。etcd 能够容忍集群中 (n-1)/2 个节点故障。为了平衡磁盘使用:

  • 每条写入都会进入 WAL;默认每 10,000 条写操作触发一次快照,快照完成后旧 WAL 可以清理。
  • 后台还有 compactor 周期性移除过旧的 MVCC 版本,降低 Watch 需要追溯的历史长度。

实战篇的操作建议

  1. 租约与 KeepAlive:用 Lease 管理临时节点(服务发现、分布式锁)。客户端要妥善处理续约失败与重连逻辑。
  2. Watch 最佳实践:Watch 首次返回当前 revision,随后才流式推送增量更新;如果消费端落后到 compact revision 之前,要自动补全全量同步。
  3. 读写路径取舍:强一致读(serializable=false)需访问 Leader,延迟敏感场景常通过代理缓存或使用只读副本。
  4. 备份恢复:使用 etcdctl snapshot save/restore 搭配定期 compact;快照流程运行期间要关注磁盘写放大的影响。

源码分析亮点

  • Raft 内核raft 包将网络/存储解耦,RawNode 暴露 Ready 结构,上层负责发送消息、落盘和应用状态机。
  • MVCC 存储mvcc.Store 将 Key 版本化 (key + revision),为事务、历史查询和 Watch 提供基础;底层使用 BoltDB B+Tree。
  • API 层调度:gRPC 服务入口会把请求放入 apply 协程队列,串行执行保证状态机一致;WatchableStore 负责向订阅者推送事件。
  • 集群管理:Learner 节点帮助平滑扩容;成员变更采用两阶段:先添加 Learner 同步历史,再升级成投票节点。

读完后的 Checklist

  • 监控指标:etcd_disk_wal_fsync_duration_secondsetcd_network_client_grpc_received_bytes_totaldb_total_size_in_bytes 等用来判断磁盘/网络瓶颈。
  • 线上巡检:对 slow fdatasync 日志和 apply took too long 告警保持敏感,必要时扩容或调整 compaction。
  • 客户端 SDK:Watch/Lease 都要考虑断线重连、指数退避;事务必须配合幂等写或补偿逻辑,避免重复提交。

整体而言,这本书的价值在于把分布式基础 → 场景实践 → 源码细节串成一条线,有助于构建自己的 etcd 心智模型。读完后再回头看官方文档或源码,会更容易抓住重点。