当前位置: 首页 > news >正文

Etcd详解(raft算法保证强一致性)

面试字节的时候遇到的一个问题。写了一个RPC框架,使用etcd作为注册中心。就被面试官逮着问etcd。etcd是什么?如果是存储系统为什么不选择redis?你说到高可用和强一致性,那etcd是如何保证的?raft算法是什么?leader选举策略是什么?

ETCD 是一个分布式、高可用的键值存储系统,提供了强一致性。它提供了优秀的数据模型、一致性协议、Watch机制和租约API。它的设计目标非常明确:强一致性、高可用性、持久化和优异的读性能。
在这里插入图片描述
网络层:提供网络数据读写功能,监听服务端口,完成集群节点之间数据通信,收发客户端数据。
Raft模块:Raft强一致性算法的具体实现。
存储模块:涉及KV存储、WAL文件、Snapshot管理等,用户处理etcd支持的各类功能的事务,包括数据索引 节点状态变更、监控与反馈、事件处理与执行 ,是 etcd 对用户提供的大多数 API 功能的具体实现。
复制状态机:这是一个抽象的模块,状态机的数据维护在内存中,定期持久化到磁盘,每次写请求都会持久化到 WAL 文件,并根据写请求的内容修改状态机数据。
在这里插入图片描述

1. 存储层

etcd 数据的最终归宿,确保了数据的持久化和一致性。

1.1 WAL(Write-Ahead Log)

预写日志,是 Raft 共识协议的核心实现之一。任何修改集群状态的请求(如 PUT、DELETE)在应用到内存状态机之前,都必须先以追加写入的方式持久化到 WAL 文件中。
客户端写请求 —到达—> leader,leader将写请求封装成一条日志,写入WAL ----> 将这条日志复制给其他 followers,followers同样持久化到自己的WAL文件中 ----> 半数以上的followers写入成功后才视作这条日志已提交。
WAL 保证了即使在节点宕机重启后,也能通过重放 WAL 日志来恢复崩溃前的状态,确保了数据的持久性。

1.2 snapshot快照

随着运行时间的增长,WAL 文件会无限膨胀,并且节点重启后重放全部日志会非常耗时。Snapshot 机制会定期将 etcd 内存中的数据状态进行全量快照,并持久化存储。快照完成后,之前的 WAL 文件就可以被清理掉了。
etcd 会根据配置(如日志条目数超过一定阈值)自动创建一个快照。这个快照文件本身也是持久化的。
解决了 WAL 日志无限增长的问题,加快了节点重启后的数据恢复速度。

1.3 BoltDB

真正的持久化键值存储引擎。它是一个用 Go 编写的嵌入式、事务性的 KV 数据库。当已提交的日志条目被应用到 etcd 的状态机时,其带来的数据修改(如 key=value)会被写入到 BoltDB 中。BoltDB 负责将用户存储的数据(如服务注册信息)安全地保存在磁盘上。它本身也使用 WAL 来保证事务的 ACID 特性,为 etcd 提供了可靠的底层存储。

  • put操作

一个写请求 PUT foo=bar 的完整数据落盘顺序是:WAL → (内存应用) → BoltDB → (周期性) → Snapshot
客户写请求 --> raft创建日志条目 --> 阻塞等待WAL调用fsync写入磁盘 --> WAL写入成功,日志条目标记为已提交 --> BoltDB开始事务,数据写入BoltDB的内存页缓存,页缓存由操作系统异步刷盘,也可以配置ColtDB是同步的:事务提交时立即调用fsync。
snapshot由计数器或时间周期性触发,异步创建snapshot,清理旧的WAL缓存。

WAL的写入:在 Raft 协议达成共识、日志条目被标记为已提交之前。为什么必须是同步的?如果 WAL 写入是异步的,那么在节点崩溃时,可能有一个已回复给客户端“写入成功”的请求,其日志条目却丢失了。这违反了分布式系统的持久性和一致性原则。通过同步 WAL,etcd 确保了任何向客户端返回成功的写操作,其数据都已经安全地存储在大多数节点的磁盘上,即使发生断电也不会丢失。

BoltDB的异步写入:在日志条目被 Raft 模块标记为已提交之后。使用MVCC模块开启一个BoltDB事务,将键值对 foo=bar 连同其版本号(Revision)一起写入 BoltDB 的 内存页缓存 中。事务提交时不立即调用 fsync。数据写入页缓存后即返回成功。操作系统会在未来某个时刻将页缓存刷入磁盘。这种方式性能极高。

snapshot:由特定条件周期性触发(如累计WAL文件数量超过阈值,默认1w,或距离上次快照间隔了一定时间),etcd 会 Fork 一个进程,在不阻塞主进程的情况下,将当前内存中的 MVCC 状态(其背后是 BoltDB 文件)完整地拷贝到一个新的 Snapshot 文件中。快照创建成功后,etcd 会删除所有被该快照覆盖的旧 WAL 日志文件。它是一个后台清理任务,不影响数据一致性,只影响磁盘空间和重启后的恢复速度。

综上用同步的 WAL 保证安全,用异步的 BoltDB 提升性能。用户查询数据时,总是从 BoltDB 中读取,所以读性能不受影响。

  • WAL文件生命周期

每次写请求不会创建新的 WAL 文件,而是以追加模式写入到当前的 WAL 文件中。只有当达到特定条件时才会开启新的.wal文件。
当 etcd 启动时,它会创建或打开一个 WAL 文件(如 0000000000000000-0000000000000000.wal),所有后续的写请求(Raft 日志条目)都会以追加的方式写入到这个文件中,这类似于数据库的 redo log。
WAL 文件不会无限增长,它会在以下情况下"滚动"(roll over)到新的文件:

  1. 达到大小限制:当当前 WAL 文件大小达到预设阈值(默认 64MB)时。
  2. 创建快照时:当 etcd 创建快照后,会开始使用新的 WAL 文件。
  3. 成员配置变更:当集群成员关系发生变化时。

WAL 文件名格式通常为:SEQ-INDEX.wal,即序列号-第一个raft索引.wal,如滚动过程:

0000000000000001-0000000000000000.wal  (已满,64MB)
0000000000000002-0000000000000001.wal  (当前活跃文件,正在写入)
  • WAL文件批fsync处理

etcd并不会每次写入都调用fsync,而是采用批处理的方式。即etcd会将多个写请求累积起来,然后一次fsync将它们全部持久化到磁盘。同时引入了并行写入,使用多个goroutine同时处理不同的批处理。但是etcd仍然保证在WAL落盘前不会修改成已提交状态,主线程仍然在阻塞等待着WAL落盘。

为什么fsync昂贵?fsync() 是一个系统调用,它要求操作系统将所有缓存的脏数据立即写入物理磁盘。这个过程涉及:

  1. 上下文切换:从用户态切换到内核态
  2. 刷新页缓存:将文件在内存中的修改页写回磁盘
  3. 磁盘 I/O:等待磁盘控制器确认数据已物理写入
  4. 阻塞等待:调用线程会被阻塞,直到所有数据安全落盘

因此,etcd 不会为每个日志条目调用一次 fsync,而是将多个条目批量处理。如果批次已满立即刷新;如果等待超时了也立即刷新,避免延迟过高。
同时还支持流水线并行化:客户端请求 → 序列化处理(goroutine A) → 网络复制(goroutine B) → WAL写入(goroutine C) → 应用状态机(goroutine D)。当一个批次在等待 WAL写入的fsync 完成时,其他 goroutine 可以继续准备下一个批次的数据、处理客户端的其他请求、处理来自其他节点的复制请求。

参数配置:

# 控制批处理的大小和超时
--wal-batch-size=1000        # 每批次最多条目数
--wal-batch-timeout=10ms     # 批次最大等待时间# WAL 文件大小
--max-wal-files=5           # 保留的 WAL 文件数量
--wal-dir=/var/lib/etcd/wal # WAL 目录

etcd 通过 批处理 + 流水线 + 预分配 + 顺序I/O ,将昂贵的 fsync 操作的开销分摊到多个请求上,使得 etcd 在保证强一致性的同时,仍能提供很高的写入性能。

2. 逻辑层

负责处理共识和数据逻辑。

2.1 Raft State Machine

Raft状态机,实现了Raft的共识算法,管理etcd节点的角色(leader、follower、candidate)、WAL日志复制、leader选举和心跳。

raft算法

Raft 的核心是将一致性问题分解为三个相对独立的子问题:

  1. Leader 选举:当现有 Leader 故障时,必须选出一个新的 Leader。
  2. 日志复制:Leader 必须接收客户端的请求,并将其复制到集群中的大多数节点,从而让状态机应用这些日志。
  3. 安全性:这是最关键的部分,确保任何状态机都不会应用错误的日志条目。其中最重要的安全属性是:如果一个日志条目在某个任期号中被提交,那么所有更高任期号的 Leader 必须包含这个条目。

一个Raft集群一般包含数个节点,典型的是5个,这样可以承受其中2个节点故障。每个节点实际上就是维护一个状态机,节点在任何时候都处于以下三个状态中的一个,:

  1. leader:负责日志的同步管理,处理所有来自客户端的请求,与Follower保持这heartBeat的联系
  2. follower:刚启动时所有节点为Follower状态,响应来自 Leader 和 Candidate 的 RPC,不发起任何请求,并且把请求到Follower的事务转发给Leader。
  3. candidate:用于竞选 Leader,负责选举投票。Raft刚启动时由一个节点从Follower转为Candidate发起选举,选举出Leader后从Candidate转为Leader状态

其中每个节点都维护了一个倒计时,有leader的时候,由leader定期心跳检测续期,当倒计时结束之后没有被续期就视作leader宕机,此时倒计时最先结束的那个follower变成candidate,进行leader选举。
在这里插入图片描述

leader选举

  1. 触发选举

节点启动以后,首先都是follower状态,在follower状态下,会有一个选举超时时间的随机计时器(这个时间是在配置的超时时间基础上加一个随机的时间得来的)。如果在这个时间内没有收到leader发送的心跳包,则节点状态会变成candidate状态,也就是变成了候选人,触发选举。
在这里插入图片描述

  1. 选举过程

Raft把时间划分为任期(Term),任期是一个递增的整数,一个任期是从开始选举leader到leader失效的这段时间。有点类似于一届总统任期,只是它的时间是不一定的,也就是说只要leader工作状态良好,它可能成为一个独裁者,一直不下台。
在这里插入图片描述
最先倒计时结束的Follower增加自己的当前任期号,将自己的状态转换为 Candidate,首先这个节点会投自己一票,然后广播RPC请求它们投票给自己。

  1. 投票规则

先来后到:在一个任期内,每个节点最多只能投一票。因此如果要投票,只能投给第一个请求的节点。
检查日志:这是保证安全性的关键!Candidate 的日志必须至少和接收者一样新。比较规则:比较双方最后一条日志的任期号。任期号大的更新。如果任期号相同,则日志索引大的更新。这条规则确保了只有包含了所有已提交日志的节点才有可能成为 Leader,防止数据丢失或回滚。
检查任期号:如果 RPC 中的任期号小于接收者的当前任期,则拒绝投票。

  1. 选举结果

Candidate 会等待,直到以下三种情况之一发生:
赢得选举:它收到了集群大多数节点(包括自己)的同意票。立即转换为 Leader,并开始向所有节点发送心跳 AppendEntries RPC,确立自己的领导地位并阻止新的选举。
输掉选举:在等待投票期间,它收到了来自其他节点的 AppendEntries RPC(心跳),并且该 RPC 中的任期号大于或等于自己的当前任期。承认该节点的 Leader 地位,将自己的状态转换回 Follower。
选举超时:一段时间后,既没有赢也没有输(通常是因为票数分散,没有任何 Candidate 获得大多数投票)。自增任期号,重置随机选举超时时间,然后立即开始新一轮选举。

  1. 随机化选举超时时间

如果所有 Follower 的选举超时时间相同,它们会同时超时,同时成为 Candidate,同时发起投票,导致票数分散,无人获得大多数票(分裂投票)。因此为每个节点设置一个随机的选举超时时间(例如 150ms-300ms 之间)。在大多数情况下,只有一个节点会最先超时。它成为 Candidate 并发起投票,在其他节点超时之前,它就已经联系到了大多数节点并赢得了选举。这大大减少了选举冲突的可能性。

选举成功

一旦 Leader 被选出,它就开始工作,处理客户端请求。客户端向 Leader 发送一个命令(如 set x = 5),Leader 将其作为一个新日志条目追加到自己的日志中,Leader 通过 AppendEntries RPC 并行地将该条目复制给所有 Follower,当大多数节点都成功复制了该条目后,Leader 就认为这个条目是已提交的,然后将其应用到自己的状态机,并返回结果给客户端。在后续的 AppendEntries RPC 中,Leader 会通知 Follower 哪些条目已经被提交,Follower 随后也将已提交的条目应用到自己的状态机。

一旦日志条目被提交,Raft 模块就会将其应用到 MVCC 存储中,从而真正修改数据。

2.2 MVCC Key-Value Store

etcd 暴露给用户的键值存储的实际逻辑实现。每次修改(如 PUT)都不是覆盖原有数据,而是创建一个新的 Revision(版本),每个 Key 都关联着多个版本的值,版本号是全局递增的。
实现了无锁的读写并发,读写操作互不阻塞。支持按版本号查询历史数据。

每个键值对不再只是一个 (key, value),而是一个 (key, revision, value) 的元组。

  • 主版本号:全局单调递增的 64 位整数,每次修改(事务提交)都会增加。
  • 次版本号:在同一主版本的事务内,多个操作的顺序编号。
PUT key1 = valueA  → Revision (100, 0)
PUT key2 = valueB  → Revision (100, 1)  // 同一事务
PUT key1 = valueC  → Revision (101, 0)  // 新事务

写操作:创建新的 Revision,不影响旧的 Revision。使用乐观锁保证原子更新:

# 1. 首先读取 key 的当前版本
etcdctl get key --write-out="json"
# 返回: {"kvs":[{"key":"key","value":"valueA","mod_revision":100}],"count":1}# 2. 更新时指定必须基于该版本
etcdctl txn --interactive
compares:
mod("key") = 100  # 只有当前版本是100时才更新success requests (then):
put key "newValue"  # 执行更新,版本变为101failure requests (else):
get key  # 如果版本已变,获取最新值

读操作:任何读操作都可以指定一个 Revision,etcd 会返回该 Revision 时的数据快照。

2.3 Watch机制

Watch 机制允许客户端监听键的变化,是 etcd 作为配置中心、服务注册中心的核心。客户端可以订阅特定键或前缀的变化,当变化发生时,etcd 主动推送通知。

3. API接口

3.1 gRPC Server

处理来自客户端的 gRPC 请求:所有 KV 操作(Range, Put, Delete等)、Watch、租约操作。

3.2 Http Server

提供 HTTP/JSON API(主要是 etcd v2 API,现已废弃),同时用于处理集群成员管理、健康检查等 HTTP 请求。

3.3 Raft Proxy

在领导者节点上,它会将来自客户端的写请求代理到 Raft 状态机中进行共识流程。

4. 核心工作流程

一个写入请求(如 PUT foo=bar)的完整生命周期如下:

  1. 客户端请求:客户端通过 gRPC 调用将请求发送到 etcd 集群的领导者节点。
  2. API 接收:领导者的 gRPC Server 接收请求。
  3. Raft 提案:Raft Proxy 将请求包装成一个日志条目,提交给本地的 Raft State Machine。
  4. WAL 持久化:Raft State Machine 首先将该日志条目持久化到 WAL。
  5. 日志复制:领导者通过 Raft 协议将日志条目复制给其他追随者节点。追随者同样先写入自己的 WAL,然后回复确认。
  6. 提交与应用:当领导者确认日志条目已被大多数节点持久化后,将其标记为已提交,然后将其应用到 MVCC Key-Value Store。
  7. 数据落地:MVCC 模块将 key=foo, value=bar 这个修改,连同新的 Revision,写入到 BoltDB 中进行最终持久化。
  8. 客户端响应:应用成功后,领导者通过 gRPC 向客户端返回成功响应。
http://www.dtcms.com/a/578046.html

相关文章:

  • 东莞网站建设对比建筑模板有几种
  • AIShareTxt入门:快速准确高效的为金融决策智能体提供股票技术指标上下文
  • 赋能智慧监管:视频汇聚平台EasyCVR助力智慧电梯监控智能化监管
  • 【银行测试】对公渠道转账+网银转账+对私转账功能测试点(汇总)
  • 2013年建设工程发布网站字形分析网站
  • 高端网站制作系统百度指数怎么看城市
  • 网站搜索排名查询余姚物流做网站
  • langchain agent的中间件
  • Mysql中隔离级别可重复读解决不可重复读的底层原理是什么?
  • MySQL的DATE_FORMAT函数介绍
  • 涞水县建设局网站电子商务网站建设哪本教材比较适合中等专业学校用
  • 建阳网站建设wordpress手机验证码注册
  • C4D服装建模实战:纽扣、嵌条与拉链工具使用详解
  • Shell高手必备:30字搞定XML注释过滤
  • 律师网站建设哪家好软文范文
  • C++编译期间验证单个对象可以被释放、验证数组可以被释放和验证函数对象能否被指定类型参数调用
  • 机器学习训练过程中的回调函数BaseCallback
  • Cordys CRM正式开源,AI驱动客户关系管理加速演进
  • 河北省 建设执业注册中心网站长沙 汽车 网站建设
  • 手机如何定位:从时间差到地图上的“小蓝点”
  • Rust : Send、Sync与现实世界的映射
  • PHP推荐权重算法以及分页
  • 做软件赚钱的网站有哪些淘宝客seo推广教程
  • 企业网站制作建设建设通app官方下载
  • 【FAQ】HarmonyOS SDK 闭源开放能力 — Form Kit
  • 学习:JavaScript(8)
  • Docker的host网络模式
  • HORIBA 新型便携式废气测量系统技术解析
  • 建设自有网站需要什么杭州网站建设设计公司哪家好
  • 常州网站建设方案维护小皮搭建本地网站