菜单

Raft 共识

相关源文件

目的和概述

本文档介绍了 etcd 如何实现 Raft 共识协议来在分布式系统中维护一致的状态。Raft 是 etcd 提供所有数据操作的强一致性保证的核心机制。实现涵盖了领导者选举、日志复制、成员变更和状态机复制。

有关 etcd 服务器整体实现的更多信息,请参阅 核心服务器。有关数据如何存储和管理的信息,请参阅 存储引擎

etcd 中的 Raft 架构

Raft 组件结构

来源:server/etcdserver/raft.go server/etcdserver/server.go

etcd 采用了分层架构来实现 Raft,将共识协议与状态机应用分开。主要组件包括:

  1. raftNode:包装 Raft 实现的核心结构。它管理通信通道并与 etcd 的其余部分进行接口。

  2. raft.Node:来自 go.etcd.io/raft/v3 库的实际 Raft 状态机实现。它执行共识算法操作。

  3. rafthttp.Transport:处理 Raft 节点之间的网络通信。

  4. WAL (Write-Ahead Log):在应用 Raft 日志条目之前将其持久化到磁盘,以确保持久性。

  5. snap.Snapshotter:管理系统状态快照的创建和应用,以防止日志无限增长。

EtcdServer 结构包含一个 raftNode 实例(r raftNode),作为共识层和应用之间的集成点。

关键数据结构

raftNode 和支持组件

来源:server/etcdserver/raft.go80-104 server/etcdserver/raft.go106-120 server/etcdserver/raft.go69-78 server/etcdserver/raft.go756-764

实现 etcd 中 Raft 共识的核心结构包括:

  • raftNode:包装 Raft 实现细节并管理通信通道的主要结构。
  • raftNodeConfig:包含 Raft 节点的配置,包括实际的 Raft 状态机。
  • toApply:传输需要应用到状态机的 Raft 条目。
  • raftReadyHandler:用于处理 Raft Ready 事件和系统更新的接口。

Raft 共识流程

写请求通过 Raft 的流程

来源:server/etcdserver/v3_server.go144-151 server/etcdserver/raft.go171-340

当客户端向 etcd 发送写请求时:

  1. EtcdServer 将其转换为 InternalRaftRequest 并调用 raftRequest()
  2. 请求通过 Node.Propose() 提议给 Raft 状态机。
  3. Raft 领导者将该条目附加到其日志中,并将其复制给跟随者。
  4. 当足够多的跟随者确认该条目(达到法定人数)后,即认为该条目已提交。
  5. raftNode.start() 方法处理已提交的条目,并通过 applyc 通道将其传递给 EtcdServer
  6. 服务器将已提交的条目应用到状态机(存储),并响应客户端。

此过程确保所有写操作在被视为成功之前已在集群中一致复制。

读请求处理

来源:server/etcdserver/v3_server.go104-144

etcd 支持两种类型的读操作:

  1. 串行化读:这些读操作直接从本地状态机读取,无需咨询 Raft,性能更好,但可能读取到陈旧数据。

  2. 可线性化读:这些读操作会通过 Raft 协议,以确保节点在读取之前拥有最新数据。这是通过 ReadIndex 算法实现的,该算法会咨询领导者以确定本地状态是否最新。

处理范围(读)请求的代码展示了这一点。

核心 Raft 功能

raftNode 启动和事件循环

etcd Raft 实现的核心是 raftNode.start() 中的事件循环 server/etcdserver/raft.go171-340,它:

  1. 处理用于超时的 tick 事件
  2. 处理来自 Raft 库的 Ready 结构,其中包含:
    • 状态变更(领导者变更)
    • 需要发送给其他节点的消息
    • 需要应用的条目
    • 需要安装的快照
  3. 管理持久化存储(WAL 和快照)
  4. 将已提交的条目应用到状态机

领导者选举和心跳

Raft 使用心跳机制来维持领导地位。领导者会定期向所有跟随者发送心跳,以防止它们启动新的选举。

在 etcd 中,这由 raftNode.start() 方法中的 ticker.C 生成的 tick 驱动。如果在某个时期(选举超时)内,跟随者未收到心跳,它将转为候选人状态并开始新的选举。

当发生领导者变更时,etcd 会更新指标并通知依赖于领导者状态的组件。

快照和预写日志

快照和 WAL 交互

来源:server/etcdserver/server.go977-996 server/etcdserver/raft.go242-270

etcd 使用两种机制来确保持久性和管理日志增长:

  1. 预写日志 (WAL):所有 Raft 条目在应用前都会写入 WAL,以确保重启后的持久性。

  2. 快照:etcd 会定期创建当前状态的快照,允许它截断 WAL 并防止无限增长。

快照过程由 EtcdServer 中的 snapshot() 方法管理 server/etcdserver/server.go977-996,它:

  1. 根据已应用的索引检查是否需要快照
  2. 创建当前状态的快照
  3. 将快照保存到磁盘
  4. 更新索引以跟踪快照
  5. 可能需要压缩 Raft 日志

此过程基于配置的 SnapshotCount(默认值:10000 个条目)自动发生。

成员变更

etcd 使用 Raft 的联合共识算法来安全地更改集群成员。添加或移除成员时:

  1. 一个 ConfChange 提案通过 Raft 传递。
  2. 一旦提交,所有节点都会应用该更改。
  3. 对于添加节点,现有成员将其添加为对等节点。
  4. 对于移除节点,成员停止向其发送消息。

实现位于 applyConfChange() server/etcdserver/server.go,并处理各种类型的配置更改,包括添加成员、移除成员以及将学习者提升为投票成员。

监控与可观测性

etcd 暴露了多个与 Raft 相关的指标用于监控:

指标描述
etcd_server_leader_changes_seen_total领导者变更的总数
etcd_server_proposals_committed_total已提交提案的数量
etcd_server_proposals_applied_total已应用提案的数量
etcd_server_proposals_failed_total失败提案的数量
etcd_server_proposals_pending待处理提案的数量
etcd_server_is_leader该成员是领导者(1)还是跟随者(0)

来源:tests/e2e/metrics_test.go142-210

这些指标有助于监控 Raft 共识的健康和性能,并可能指示网络问题、磁盘性能问题或领导者频繁切换等问题。

结论

etcd 的 Raft 实现是其作为分布式键值存储的可靠性基础。通过共识提供强一致性保证,etcd 确保集群中的所有节点在任何已提交的索引处对数据具有相同的视图。

理解 Raft 在 etcd 中的工作方式对于有效运行 etcd 集群和诊断运行中可能出现的问题至关重要。