资讯详情

资讯详情

etcd读写链路与集群实战:从Raft、MVCC到避坑排查

简介《etcd实战及其原理分析》面向后端开发、云原生运维和分布式系统学习者围绕etcd v3.5梳理从入门到原理的完整知识链路。内容覆盖单实例、单机多实例与多节点集群部署etcdctl常用命令、用户角色权限管理以及读请求中的串行读、线性读、ReadIndex写请求中的Quota、WAL、Apply等执行流程并深入Raft选举、日志复制与MVCC增删改查机制。资料为1个PDF文件压缩包约4.55MB篇幅集中、章节清晰便于按主题查阅和对照实践。目前已有448人学习适合需要理解服务发现、分布式锁、主备选举等场景的读者。通过这份文档可掌握环境搭建与客户端操作要点建立etcd读写链路、一致性与多版本并发控制的系统认知为分布式系统选型、部署和问题排查提供参考。1. etcd 不是注册中心那么简单这份 PDF 把读写链路拆到了 treeIndex 那一层三节点集群刚上线那周某开发者碰到过一个很怪的现象同一个 key客户端连到 A 节点读到的是新值连到 B 节点读到的还是上一个版本两边日志时间戳差了不到 50 毫秒。第一反应是复制链路出问题翻完 WAL 和 Raft 日志才发现写入早就提交了问题出在读请求走的是串行读B 节点的状态机还没把已提交日志 apply 完。这份《etcd 实战及其原理分析》PDF 就是冲着这类问题写的。它基于 etcd v3.5内容分三块介绍与五层架构、实战单实例/单机多实例/多节点集群、etcdctl 命令、权限管理、原理读写请求执行流程、Raft、MVCC 的 treeIndex 与 boltdb。适合已经能把 etcd 跑起来、但一遇到读旧数据写入超时空间爆了就抓瞎的后端和运维同学。2. 三节点集群怎么搭从单实例到静态配置参数一个都不能错环境搭建这部分PDF 给了三种形态单实例、单机多实例、多节点集群。很多人第一次看会觉得单实例那节是凑数的其实不然——后面所有排错都要回到单实例上做对照实验。把单实例的端口、data-dir、日志位置摸清楚集群出问题时你才知道该看哪个文件。2.1 单实例起步2379 和 2380 是两个完全不同的端口etcd 一启动就监听两个端口这两个端口的分工必须记牢2379 面向客户端etcdctl、业务 SDK2380 面向集群内其他 member 的 peer 通信。单机部署时只用到 2379所以很多人误以为 2380 是可选的等到搭集群时才发现端口冲突。# 下载并解压 v3.5.5 二进制包 wget https://github.com/etcd-io/etcd/releases/download/v3.5.5/etcd-v3.5.5-linux-amd64.tar.gz tar -zxvf etcd-v3.5.5-linux-amd64.tar.gz cd etcd-v3.5.5-linux-amd64/ # 前台启动方便直接看日志 ./etcd # 生产/测试机上一般后台跑日志重定向到文件 nohup ./etcd /tmp/etcd.log 21 # 确认版本避免二进制和客户端库版本对不上 ./etcd --version # 放进 PATH后续直接调 etcd / etcdctl cp etcd etcdctl /usr/local/bin这段脚本没有多余动作重点在最后两行etcd --version和etcdctl version必须一致。我见过不止一次etcdctl是 v3.4、server 是 v3.5 的情况命令能跑但--rev、lease timetolive --keys这类参数行为有细微差别排查时会把人带偏。后台启动时建议显式加--data-dir默认值在/var/lib/etcd或当前目录下的default.etcd跑在容器里很容易因为工作目录变化而数据丢了。启动完成后用一对读写命令确认服务真的活着# 写一个 key返回 OK 表示 Raft 提交成功 etcdctl --endpointslocalhost:2379 put username fox # 读回来默认输出是纯文本 etcdctl --endpointslocalhost:2379 get username # 加 -wjson 能看到 revision、raft_term 等元数据 etcdctl --endpointslocalhost:2379 get username -wjson--endpoints是客户端侧的参数不是 server 参数这一点新手常混。put返回 OK 只代表这条日志已经被多数节点持久化并提交不代表已经落到 boltdb——这个区别在第 4 章讲写链路时会展开。2.2 单机多实例用 goreman 在一台机器上模拟三个 member想验证 Raft 选举、节点故障转移又不想开三台机器最省事的办法是单机起三个实例。难点在于默认 2380 端口会冲突所以每个实例的 peer 端口要错开。PDF 用的是 goreman 管理多进程本质上就是读一个 Procfile 格式的文件批量拉起进程。# goreman 依赖 Go 环境 go install github.com/mattn/goremanlatest goreman help然后写local-cluster-profile三个实例的客户端端口用 12379/22379/32379peer 端口用 12380/22380/32380etcd1: etcd --name infra1 \ --listen-client-urls http://127.0.0.1:12379 \ --advertise-client-urls http://127.0.0.1:12379 \ --listen-peer-urls http://127.0.0.1:12380 \ --initial-advertise-peer-urls http://127.0.0.1:12380 \ --initial-cluster-token etcd-cluster-1 \ --initial-cluster infra1http://127.0.0.1:12380,infra2http://127.0.0.1:22380,infra3http://127.0.0.1:32380 \ --initial-cluster-state new etcd2: etcd --name infra2 \ --listen-client-urls http://127.0.0.1:22379 \ --advertise-client-urls http://127.0.0.1:22379 \ --listen-peer-urls http://127.0.0.1:22380 \ --initial-advertise-peer-urls http://127.0.0.1:22380 \ --initial-cluster-token etcd-cluster-1 \ --initial-cluster infra1http://127.0.0.1:12380,infra2http://127.0.0.1:22380,infra3http://127.0.0.1:32380 \ --initial-cluster-state new etcd3: etcd --name infra3 \ --listen-client-urls http://127.0.0.1:32379 \ --advertise-client-urls http://127.0.0.1:32379 \ --listen-peer-urls http://127.0.0.1:32380 \ --initial-advertise-peer-urls http://127.0.0.1:32380 \ --initial-cluster-token etcd-cluster-1 \ --initial-cluster infra1http://127.0.0.1:12380,infra2http://127.0.0.1:22380,infra3http://127.0.0.1:32380 \ --initial-cluster-state new# 拉起集群 goreman -f ./local-cluster-profile start # 查 member 列表应该看到三个 healthy etcdctl --endpointslocalhost:12379 member list # 验证写入会自动转到 leader etcdctl put user fox --endpoints http://localhost:22379 # 停掉一个再拉起来观察 leader 是否切换 goreman run stop etcd2 goreman run restart etcd2这里最容易踩的是--initial-cluster三个实例的这一行必须完全一样写的是各节点的 peer 地址不是客户端地址。有人把 12379 填进去集群能起来但 member 之间永远连不上日志里一直刷dial tcp ... connect: connection refused。--initial-cluster-token是集群身份标识同一台机器上跑多套测试集群时必须改否则会串台。2.3 静态配置集群--initial-cluster 里每个字段的真实含义生产上三节点是起步配置PDF 给的是静态配置方式前提是节点 IP 在启动前就已知。三台机器分别命名为 infra1/infra2/infra3peer 端口统一 2380客户端端口统一 2379# 节点 1192.168.10.11 etcd --name infra1 \ --listen-client-urls http://192.168.10.11:2379 \ --advertise-client-urls http://192.168.10.11:2379 \ --listen-peer-urls http://192.168.10.11:2380 \ --initial-advertise-peer-urls http://192.168.10.11:2380 \ --initial-cluster-token etcd-cluster-1 \ --initial-cluster infra1http://192.168.10.11:2380,infra2http://192.168.10.12:2380,infra3http://192.168.10.13:2380 \ --initial-cluster-state new \ --data-dir /data/etcd节点 2、3 的写法和节点 1 一样只改--name和三处 URL。把这些参数摊开看参数作用常见错误--name节点在集群内的唯一名字两台机器重名集群直接起不来--listen-client-urls本机实际监听的客户端地址只写 127.0.0.1外部客户端连不上--advertise-client-urls告诉其他节点和客户端该用哪个地址访问自己写成本机回环地址客户端拿到后全部超时--listen-peer-urls节点间通信的监听地址与 client 端口混用或者被防火墙拦掉--initial-advertise-peer-urls广播给集群的 peer 地址与--initial-cluster里对应条目不一致选举失败--initial-cluster集群初始成员清单三个节点写得不完全一致出现脑裂式启动--initial-cluster-statenew表示新建集群existing表示加入已有集群扩容时误用new整个集群元数据被重置--initial-cluster-state这个参数是血泪经验三种状态混用是集群起不来的头号原因。首次搭建全部用new往后加节点才用existing而且老节点不动。2.4 动态发现与 DNS 发现什么场景才值得上静态配置要求启动前就知道所有节点 IP云上弹性伸缩场景做不到这时候才轮到动态发现。机制是用一个已有 etcd 集群或公共的 discovery 服务当通讯录新节点上线时先去那里注册自己凑齐预设的节点数后一起引导。# 申请一个 discovery tokensize3 表示目标集群三个节点 curl https://discovery.etcd.io/new?size3 # 返回类似 https://discovery.etcd.io/xxxxxxxxxxxx # 每个节点启动时把 --initial-cluster 换成 --discovery etcd --name etcd1 \ --data-dir /data/etcd \ --initial-advertise-peer-urls http://192.168.10.11:2380 \ --listen-peer-urls http://192.168.10.11:2380 \ --listen-client-urls http://192.168.10.11:2379,http://127.0.0.1:2379 \ --advertise-client-urls http://192.168.10.11:2379 \ --discovery https://discovery.etcd.io/xxxxxxxxxxxx一个 token 只能引导一个集群中途失败也不能复用这条限制在 PDF 里被单独标了出来。生产上我一般不用公共 discovery而是在内网自己起一个小 etcd 当发现服务或者直接用 DNS SRV 记录——--discovery-srv配合_etcd-server._tcp记录节点信息变更时只改 DNS不用动启动参数。代价是 DNS 解析失败会直接把启动拖死得先确认内网 DNS 可用。3. etcdctl 的键操作、租约与权限几条最容易写反顺序的命令客户端这层是日常打交道最多的地方PDF 列的命令不算多但每一组都配了返回字段解释这部分比命令本身值钱。尤其是-wjson打出来的那几个 revision读懂了才能理解 MVCC 到底在做什么。3.1 键操作与 revision用 -wjson 看清一次写到底改了什么键操作就是 PUT / GET / DELETE 三件套但范围查询和版本查询的写法值得单独过一遍# 基础读写 etcdctl put /test/name fox etcdctl get /test/name # 半开区间[name, name3)包含左端不包含右端 etcdctl get /test/name /test/name3 # 按前缀读--limit 限制返回条数 etcdctl get --prefix /test --limit 3 # 按 key 的字典序读读取大于等于 name2 的所有 key etcdctl get --from-key name2 # 读历史版本--rev 指定全局 revision etcdctl get --rev8 /test/name-wjson返回里那几个字段的分工必须分清否则排查 MVCC 问题全是盲猜cluster_id/member_id这次请求打到哪个集群、哪个节点。revision服务端当前的全局版本号。任何一次 put 或 delete 都会让它自增 1从 2 开始1 是保留值。raft_term当前 leader 的任期号和 Raft 的 Term 是同一个东西。create_revision这个 key 创建时的全局版本号。mod_revision这个 key 最后一次被修改时的全局版本号。version这个 key 自身被修改过多少次从 1 开始删掉再重建会重新从 1 计。很多人把revision和version混为一谈。前者是集群级别的全体 key 共享一个计数器后者是 key 级别的。做配置灰度时用--rev回读某个历史版本用的是全局 revision写错成 version 会直接读空。3.2 租约与 watch服务注册为什么不直接 put 一个 key服务注册如果只是put /services/order/10.0.0.5:8080服务进程被 kill 之后 key 永远留在那里调用方拿到的是死地址。正确做法是把 key 绑到租约上进程活着就续租进程挂了租约到期key 自动消失。这是 etcd 相比 ZooKeeper 临时节点更可控的地方——TTL 和续租节奏都在客户端手里。# 申请一个 TTL 30 秒的租约返回租约 ID etcdctl lease grant 30 # 把 key 绑到租约上lease ID 用上一步返回的值 etcdctl put --lease705b8474d1acd63b /services/order/1 10.0.0.5:8080 # 撤销租约绑定的 key 全部被删除 etcdctl lease revoke 705b8474d1acd63b # 手动续租生产上一般由客户端库自动做 etcdctl lease keep-alive 705b8474d1acd63b # 查租约还剩多久--keys 顺带列出绑定的 key etcdctl lease timetolive --keys 705b8474d1acd63blease keep-alive是阻塞命令会一直续下去别在脚本里不加timeout直接跑。另外租约 ID 是十六进制的手抄容易错一位建议用变量接住。watch 是 etcd 另一个核心能力和 ZooKeeper 的单次触发不同etcd 的 watch 是持续流# 终端 A监听前缀持续输出变更 etcdctl watch --prefix /services/ # 终端 B写一个 key 触发事件 etcdctl put /services/order/1 10.0.0.6:8080这里有个隐蔽的坑watch 不带--rev时从当前最新版本开始监听中间的事件会丢带--rev从指定版本开始可以把历史事件补回来。PDF 里提到 etcd 能存数十万个历史变更正是为这种客户端断线重连后补齐事件的场景准备的。但历史事件不是无限的被 compaction 掉之后就补不回来了这一点在第 5 章会展开。3.3 权限管理root 用户、角色授权和 auth enable 的先后顺序权限这块顺序写反整个集群会直接锁死。正确顺序是建 root 用户 → 建普通用户 → 建角色 → 给角色授权 → 用户绑角色 → 最后才auth enable。# 1. 创建 root 用户root 自带全部权限 etcdctl user add root # 2. 创建普通用户 etcdctl user add fox # 3. 创建角色 etcdctl role add test # 4. 给角色授权read / write / readwrite后面跟 key 范围 etcdctl role grant-permission test readwrite /name # 5. 用户绑定角色 etcdctl user grant-role fox test # 6. 开启认证 etcdctl auth enable # 7. 开启后所有命令都要带账号 etcdctl --userroot --password****** get /name etcdctl --userfox --password****** put /name foxrole grant-permission的 key 范围支持前缀写法比如--prefixtrue /test/不写前缀就只对单个 key 生效。授权之后权限变更不是实时的已有连接上的鉴权结果会缓存生产上改权限后最好让客户端重连一次。auth enable之后如果 root 密码忘了恢复流程非常麻烦要停掉所有节点用--force-new-cluster单节点拉起关认证重建用户。这个操作会丢掉部分集群元数据所以密码一定要进配置管理系统。另外etcdctl user list在开了认证之后也必须带--userroot否则直接返回etcdserver: user name is empty。4. 一次读写到底走了哪些模块拦截器、ReadIndex、WAL 与 treeIndex 的协同时序原理部分是这份 PDF 的重头戏占了将近一半篇幅。它没有停在etcd 用 Raft 保证一致性这种层面而是把一次get和一次put拆成了带序号的阶段每一步对应到具体的模块和方法。看懂这部分前面那些命令的返回值才有解释力。4.1 读请求的五段式Round-robin、拦截器、KVServer、ReadIndex、MVCC以etcdctl get hello --endpointsip1:2379,ip2:2379,ip3:2379为例链路是这样的第一段在客户端。etcdctl 解析参数后创建 clientv3 对象负载均衡算法是 Round-robin也就是对每个请求轮流从 endpoint 列表里挑一个走长连接。这意味着同一个客户端的多次读可能落到不同节点上前面那个读到旧值的现象就是这么来的。第二段是拦截器。请求到达 server 后先进 KVServer 模块etcd 用拦截器interceptor以非侵入方式挂了一堆横切逻辑metrics 统计、请求日志、来源 IP、慢查询阈值告警、Learner 节点的接口限制。还有一个关键前置校验——执行任何操作前集群必须有 Leader没有 Leader 直接返回错误不会让请求在内部空转。第三段是 Range 接口。拦截器链执行完根据 ServiceName 和 RPC Method 把请求分发到 KVServer 的 Range handler。第四段是读模式决策。串行读直接读本地状态机线性读要走 ReadIndex向 leader 确认当前提交点等到本地状态机 apply 到这个点之后才返回。第五段是 MVCC 查询。Range 接口调用 MVCC 模块先用 treeIndex 在内存 B 树里定位 key 的 revision 列表再拿 revision 去 boltdb 里取真正的 value。这五段里真正决定读到新值还是旧值的是第四段。很多性能问题也出在这里线性读要多一次 leader 往返跨机房部署时这一次往返可能就是几十毫秒。4.2 串行读、线性读与 ReadIndex一致性和延迟的取舍etcd 的读分两种串行读Serializable直接读本地状态机不经过 Raft延迟最低但可能读到旧数据。适合对一致性不敏感的场景比如读监控指标、读不常变的配置。线性读Linearizable默认模式。走 ReadIndex 流程先记录当前集群的 committed index然后向 leader 确认自己还是 leader防止旧 leader 脑裂等本地状态机 apply 到该 index 后再读。线性读的成本就是多一轮网络交互。PDF 里举的例子很典型客户端写入 helloworldleader 提交后广播三个节点中有一个磁盘 I/O 抖动apply 慢了几十毫秒此时客户端恰好连到这台慢节点做串行读就会读到旧的 hello。这不是 bug是串行读的定义决定的。在 clientv3 里切换读模式用clientv3.WithSerializable()命令行对应etcdctl get --consistencys。我的习惯是默认全用线性读只有明确知道某类 key 允许陈旧比如节点心跳时间戳才放开串行读而且要单独打标签出问题时能一眼定位。4.3 写请求的七段式Quota、WAL、Proposal、Apply 与 boltdb写请求比读复杂得多PDF 拆成 Quota、KVServer、WAL、Apply、MVCC 几个模块串起来大致是七步Quota 检查。KVServer 收到 Put 后先看后端数据库大小是否超过配额默认 2GB--quota-backend-bytes可调。超了直接返回etcdserver: mvcc: database space exceeded所有写请求全部拒绝读还能继续。这个设计是为了保护集群但对业务来说就是突然不能写了。生成 Proposal。请求被包装成一个 Raft proposal带上唯一的 proposal ID。WAL 持久化。leader 先把 proposal 写进自己的 WAL 并 fsync这一步是磁盘操作也是写延迟的主要来源。广播给 follower。通过 peer 端口发给其他节点follower 同样写 WAL 后回 ACK。标记已提交。收到多数派 ACK 后这条日志被标记为 committed同时通知等待中的客户端。Apply 到状态机。etcdserver 模块异步从 Raft 拿到已提交日志交给 Apply 模块执行。MVCC 落库。Apply 调用 MVCC更新 treeIndex 里的 key 索引把新的 key-value 写进 boltdb 的 backend。注意第 5 步和第 7 步是异步的客户端收到 OK 的时候数据可能还没进 boltdb。所以写入成功和能从状态机读到之间存在一个极短的时间窗这也是为什么同一时刻的串行读仍可能读不到刚写的值。WAL 和 Snapshot 的分工也值得记一笔WAL 记录所有变更操作用于崩溃恢复Snapshot 是某个时间点的全量状态用于防止 WAL 无限增长。节点重启时先加载最新的 Snapshot再重放 Snapshot 之后的 WAL 条目两段拼起来才是完整状态。4.4 Raft 的 Term 与 Index选举、日志复制和提交点Raft 这块 PDF 讲了三个东西Leader 选举、日志复制、安全性。节点有三种角色Leader、Follower、Candidate。Follower 在选举超时时间内没收到 leader 心跳就转成 CandidateTerm 加一向其他节点拉票拿到多数派选票就成为新 Leader。Term 是单调递增的任何节点收到比自己 Term 小的消息一律拒绝这条规则防止了旧 leader 继续发号施令。日志复制靠(Term, Index)二元组定位。Index 是 WAL 里的日志序号Term 是这条日志产生时的任期。两个节点日志不一致时leader 会从后往前找最后一个匹配点把后面的覆盖掉。这也是为什么 etcd 集群时间不同步很危险——时钟跳变会引发频繁选举Term 疯涨日志复制一直在追。安全性约束有两条一是已提交的日志不会被覆盖二是只有包含全部已提交日志的节点才能当选 leader。这两条保证了已经返回给客户端 OK 的写入不会丢。4.5 MVCC 的 treeIndex 与 boltdb更新、查询、删除三条路径MVCC 是 etcd v3 最大的变化核心是写不覆盖追加新版本。它由两部分组成treeIndex内存里的 B 树key 是用户 keyvalue 是这个 key 的所有 revision 列表。它只存索引不存数据。boltdb磁盘上的 KV 存储key 是 revisionvalue 是真正的用户数据。这是一个 B 树支持事务。更新 key 的路径先检查 key 是否存在生成新的全局 revision当前 revision 1在 treeIndex 里把新 revision 追加到该 key 的版本列表然后把{revision: {key, value}}写进 boltdb 的一个事务里。旧的 revision 不删。查询 key 的路径给定 key 和可选的 revtreeIndex 找到小于等于目标 rev 的最大版本号再拿这个版本号去 boltdb 里取 value。--rev查询历史版本就是走的这条路只要那个版本没被 compaction 掉。删除 key 的路径不是真删而是写一条 tombstone墓碑记录同时把 treeIndex 里该 key 的版本列表清空标记为已删除。历史 revision 依然在 boltdb 里直到 compaction 把它们清掉。理解了这三条路径就能解释两个常见现象为什么 etcd 的数据目录只涨不跌旧版本没清理以及为什么--rev读一个被压缩掉的版本会报etcdserver: mvcc: required revision has been compacted。5. 避坑排查五个把集群搞挂的真实操作这些坑的共同点是现象看起来像 bug根因全是配置或顺序问题。每一条我都按现象 → 原因 → 解决写方便对照自己的环境。5.1 现象节点重启后彻底起不来日志刷cluster ID mismatch原因改动过--initial-cluster或者--initial-cluster-token。etcd 会把集群 ID 写进># 压缩到当前 revision丢弃更早的历史版本 etcdctl compact $(etcdctl endpoint status --write-outjson | grep -o revision:[0-9]* | head -1 | cut -d: -f2) # 碎片整理真正把磁盘空间还回去执行期间该节点会阻塞 etcdctl defrag --endpointshttp://192.168.10.11:2379 # 解除告警 etcdctl alarm disarmdefrag是阻塞操作三节点集群要逐个做不能一起上否则会短暂失去多数派。做完之后把--auto-compaction-retention配上比如1h让 etcd 自己定期压缩。5.3 现象watch 客户端内存一路涨到 OOM原因客户端断线重连时用--rev从很旧的版本开始补事件或者 watch 的前缀范围过大比如直接 watch/把大量历史事件一次性灌进内存。etcd 服务端默认保留的历史窗口有限但客户端不做流控就会自己撑爆。解决watch 时带上WithProgressNotify定期拿服务端的进度通知校准客户端侧对事件做批量消费不要来一条处理一条前缀尽量收窄到业务命名空间。另外 watch 的数量也有上限默认每个连接能挂的 watcher 是有限的大量短连接 watch 会快速消耗服务端资源。5.4 现象auth enable之后所有客户端 401包括原来的管理脚本原因开认证之前没有创建 root 用户或者 root 用户没绑定角色就开了认证。etcd 规定 root 用户必须在auth enable之前存在否则认证一开就没人有权限了。解决如果已经锁死停掉所有节点挑一个节点用--force-new-cluster单节点启动这个模式会忽略集群配置把当前节点强行变成单节点集群。起来之后etcdctl auth disable补建 root 用户再逐个把其他节点加回来。这个操作有丢数据的风险做之前先snapshot save。5.5 现象三节点挂掉一个之后写入直接卡住几十秒然后超时原因剩下两个节点里有一个是网络分区状态虽然进程活着但拿不到多数派。三节点集群的 quorum 是 2只要有一个节点实际不可达写入就会一直等 leader 选举超时时间到了才返回错误。解决先etcdctl endpoint health --cluster逐个探活再看endpoint status里的raftTerm和raftIndex找出落后的节点。如果是磁盘慢导致的 apply 延迟看etcd_disk_wal_fsync_duration_seconds和etcd_disk_backend_commit_duration_seconds两个指标如果是网络问题检查 2380 端口。生产上建议直接上五节点容忍两个节点故障代价是写延迟会上升——每次写入要等三个节点 ACK 而不是两个。6. 版本回溯与空间回收compaction、defrag 和 --rev 查询的组合拳排查配置事故时最有用的一组操作是读历史版本 定期压缩。某次线上有个开关被人误改业务侧只看到行为变了没人记得改之前是什么值。当时就是靠--rev一路往回翻定位到具体是哪个 revision 被改的再用create_revision/mod_revision对上了操作时间。# 先拿到当前全局 revision etcdctl endpoint status --write-outjson # 从当前版本往前读定位变更点 etcdctl get /config/feature/switch --rev1024 -wjson # 对比两个 revision 的值确认是哪一次改动引入的问题 etcdctl get /config/feature/switch --rev1023 -wjson前提是这个 revision 还没被压缩掉。etcd 默认不会自动压缩除非配了--auto-compaction-retention。我的习惯是配1h然后用定时任务每小时跑一次显式 compact再每周挑低峰期做一次 defrag。compact 只清理逻辑上的旧版本boltdb 文件不会立刻变小defrag 才会重写文件、真正释放磁盘但它会阻塞当前节点所以必须逐个节点做并且确认集群健康。# 开启自动压缩保留最近 1 小时的历史版本 etcd --auto-compaction-retention1h ... # 手动压缩到指定 revision etcdctl compact 1024 # 检查各节点的 dbSize 和 dbSizeInUse两者差距大说明需要 defrag etcdctl endpoint status --cluster -wtabledbSize是文件实际大小dbSizeInUse是有效数据大小前者远大于后者就是碎片多了。这两个数我一般加到监控里比值超过 2 就安排 defrag。还有个容易被忽略的点snapshot save出来的快照包含全部历史 revision恢复之后dbSize会比原来小但dbSizeInUse不会变。所以快照恢复不能替代 compaction两者解决的是不同问题。从那以后我每次搭新集群都会在启动参数里把--auto-compaction-retention和--quota-backend-bytes一起配上并且在交付文档里写清楚写满之后怎么救。这两条不加出事的时候只能干等。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →