ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

etcd核心机制与高可用集群实战:从Raft共识到故障排查

etcd核心机制与高可用集群实战:从Raft共识到故障排查 1. 先搞清楚etcd 到底是个什么东西1.1 一个“分布式键值库”怎么就成了大脑我第一次接触 etcd 的时候脑袋里浮现的就是一个长得像 Redis 的 KV 存储。后来深入进去才明白etcd 的重点从来就不是“存数据”而是“管状态”——它是一套分布式系统里专门用来存共识状态、做协调的组件。你可以把它理解成整个系统的神经系统各个服务节点把心跳、配置、选主结果、服务地址这类“决策信息”都交给它保存然后通过它的 watch 机制第一时间感知变化。etcd 的全称是 etc distributed意思是分布式的/etc目录。这个定位很准确——它存储的正是那些本该写在配置文件里的东西只是这些配置和状态需要被很多节点同时看到并且要保证所有节点看到的内容是一致的。在分布式系统里很多问题的本质就是“参与者之间无法达成一致”etcd 就是用来解决这个一致性问题的基础组件。1.2 etcd 为什么能干这活市面上 KV 存储不少但 etcd 的核心卖点不是性能而是三个能力叠加强一致、可 watch、带租约。强一致写入成功之后后续的读请求无论落在哪个节点上拿到的都是最新写入的值。这靠 Raft 共识协议实现。可 watch客户端可以对某个 key 或者 key 前缀持续监听一旦 value 变动立即收到通知。这是做服务发现、配置热更新的基础。带租约可以给 key 设置 TTL到期自动过期。配合心跳续租机制就能判断某个节点是否“活着”。这三个能力组合起来正好覆盖了分布式系统里最常遇到的几个问题配置同步、服务注册与发现、Leader 选举、分布式锁、集群成员管理。Kubernetes 把整个集群的状态全部存在 etcd 里你就能明白这个组件的份量了。1.3 谁在用 etcd用在哪里日常开发里用到 etcd 的典型场景大概是这几类服务注册与发现服务启动后把自己的地址写进 etcd设置租约并周期性续期其他服务通过 watch 某个前缀实时感知服务列表的变化。微服务体系里这是很经典的模式。分布式配置中心把应用的配置项统一放到 etcd修改配置后所有订阅的客户端自动收到更新免去逐个重启的麻烦。分布式锁与选主通过 etcd 的事务和租约能力实现跨节点的互斥锁或者竞选出一个主节点负责特定任务。Kubernetes 存储后端这是目前最知名的用法。整个集群的期望状态和实际状态都存在 etcd 里。如果是刚接触分布式系统的人我的建议是先别急着把 etcd 用在项目里先把它当做一个“共识黑盒”去理解搞清楚它解决了什么问题再上手实操会顺畅很多。2. 核心机制逐个拆你以为 etcd 只是个 Redis2.1 基于 Raft 的共识选主和数据复制那点事Raft 是 etcd 保证强一致性的地基。我第一次看 Raft 论文的时候有点晕但后来发现可以把它类比成一个严格的团队决策流程所有节点分成 Leader、Follower、Candidate 三种角色。正常情况下一个集群只有一个 Leader所有的写请求都必须经过 Leader。Leader 把写操作封装成日志条目广播给 Follower多数派确认后才提交。如果 Leader 挂掉Follower 会变成 Candidate 发起新一轮选举得票过半的新 Leader 产生继续对外服务。这里有几个关键参数值得注意--heartbeat-interval和--election-timeout。心跳间隔是 Leader 给 Follower 发心跳的周期选举超时是 Follower 多久没收到心跳就认为 Leader 失联并开始选举。这俩值不是拍脑袋定的它们决定了集群故障恢复的速度也决定了正常情况下系统的“紧张程度”。注意生产环境我一般不推荐把心跳间隔调得太短比如小于 100ms。网络抖动一次就可能导致频繁选主整个集群性能断崖式下跌。默认值 100ms/1000ms 在大多数局域网场景下表现都足够稳定修改前一定要想清楚。读请求在 etcd 里有两种模式。**串行读Serialize**不经过共识直接从本地状态机读性能好但不能保证读到最新值**线性读Linearizable**会走一轮共识确认当前 leader然后从 leader 上读取能保证读到最新提交数据。我的经验是对一致性要求高的配置读取务必用线性读对容忍轻微滞后的监控类查询可以用串行读缓解 Leader 压力。2.2 Watch、Revision、MVCC、Lease真正拉开差距的四个概念如果你只把 etcd 当成一个能 set/delete 的 KV那你可能会错过它最值钱的能力。真正让 etcd 区别于 Redis 的是下面这套机制。Revision 是全局版本号。etcd 里每一次修改put 或 delete都会让全局版本号加一并带上这个版本号作为历史记录的一部分。你可以把它理解为数据库里的事务序列号只是它是全局单调递增的。MVCC 多版本并发控制。同一个 key 的旧版本不会立刻被覆盖而是保留一段时间。这带来的直接好处就是 watch 可以按版本号增量推送客户端只需要记录自己上次看到的 revision下次请求从这个 revision 之后的事件既不丢数据也不重复推。这也是 etcd 能做配置同步的底气。Watch 是事件订阅的通道。客户端可以 watch 某个 key也可以 watch 某个前缀甚至可以在 watch 的时候指定从某个 revision 开始。当 key 发生变化时服务端通过长连接把事件推给客户端。这个机制比客户端轮询优雅太多也实时得多。Lease 是分布式心跳模型。etcd 中的 key 可以绑定到一个 Lease 上Lease 有 TTL到期了 key 就被清理。客户端只要定期续约就相当于对外宣告“我还活着”。这套模型做服务探活非常顺手服务下线没来得及优雅注销租约一到相关 key 自动消失其他节点立刻通过 watch 感知并将流量切走。这四者组合起来能实现很多有意思的能力。比如分布式任务调度里的“选主 保活 断线感知”核心逻辑不过几十行代码背后全是这几个原语的功劳。2.3 WAL 与 Snapshot崩溃恢复的秘密etcd 写入数据时不是直接改内存或者直接刷数据库文件而是先把操作记录追加到 WALWrite-Ahead Log日志里再更新内存状态。这样即使进程崩溃重启时也能重放 WAL 恢复到崩溃前的状态。但 WAL 不能无限增长否则恢复时间会变得不可接受。所以 etcd 每隔一段时间会生成一个 Snapshot快照把当前内存里的状态落盘。恢复时就简单了加载最近的快照再重放快照之后的日志即可。这里有个我在生产中踩过的坑如果业务写入量很大snapshot 的频率跟不上日志积累的速度磁盘占用会持续上涨恢复时间也会变长。遇到这种情况可以从两方面入手一是调整--snapshot-count二是优化业务写入模式尽量避免高频大量的小写入。2.4 读请求的两种模式怎么读才不会“读到旧数据”这个问题在接入 etcd 客户端时几乎必问我用客户端默认配置读到的数据到底是最新的吗如果使用的是串行读模式请求可能落在一个网络分区中落后于 Leader 的 Follower 节点上读到的自然可能是旧值。对大部分中间件场景旧一点没关系但如果是分布式锁或者选主逻辑读到旧值就会出大问题。所以客户端 SDK 里给的WithSerializable()选项用之前一定要想清楚。我自己的习惯是凡是参与业务决策的读一律不带这个参数默认走线性读。虽然会多一次 RTT但换来的是逻辑上的确定性这笔账非常划算。3. 从零搭一个高可用 etcd 集群3.1 部署前的自我拷问单机还是集群很多人一开始图省事直接在服务器上跑一个单节点 etcd。如果你只是本地开发验证没问题但如果是生产环境甚至预发环境我强烈不建议这么做。单点 etcd 挂了后果不亚于数据库挂了。正确姿势是搭一个奇数节点集群最少 3 个常用 5 个。因为 Raft 要求多数派存活才能继续服务3 节点容忍挂 1 个5 节点容忍挂 2 个。6 节点、8 节点这种偶数节点没有额外收益反而增加复制开销。节点分布上也有讲究。尽量把 etcd 节点分散到不同的物理机、不同的机架甚至不同的可用区。曾经遇到过整个集群放在同一个机柜里结果机柜断电所有 etcd 一起下线业务全部停摆的事故。这种分布的事儿等出问题再后悔就来不及了。3.2 静态配置启动一个三节点集群这里记录一下我最近搭建三节点集群的完整过程。以三个节点192.168.1.11、192.168.1.12、192.168.1.13为例。每个节点上准备好 etcd 二进制后写一个配置文件或者直接用启动参数。我习惯用 systemd 管理配置文件放在/etc/etcd/etcd.conf.yml。第一个节点大概这个样子name: etcd-1>etcdctl --endpointshttp://192.168.1.11:2379,http://192.168.1.12:2379,http://192.168.1.13:2379 member list正常情况下能看到三行 member并且isLeader字段标记出当前 Leader。再测试写入读取etcdctl put /test hello etcdctl get /test能正常存取集群就算通了。注意listen-peer-urls和advertise-peer-urls一定要区分清楚。前者是监听地址后者是广播给其他节点的地址。如果涉及 NAT 或多网卡环境advertise 地址必须要写其他节点能真正访问到的地址否则集群之间会互相发现不了。集群搭建完成后initial-cluster-state千万别在已有数据目录上改成existing去重启那个参数只能用于向已有集群添加节点时的恢复场景。用错了轻则节点起不来重则数据错乱。3.3 常见配置项和参数选择搭建完成后建议再花点时间过一遍这些配置项数据目录与存储相关配置作用实践经验--data-dir数据存储目录独立分区预留充足磁盘最好用 SSD--wal-dirWAL 日志目录可与>etcdctl compact $(etcdctl endpoint status --write-outjson | jq -r .[0].Status.header.revision)这条命令把历史版本压缩到当前版本老版本就会被清理。压缩之前要确认所有 watch 客户端都已经不在旧 revision 上否则客户端会收到ErrCompacted错误需要重新建立 watch。压缩之后空间不一定立刻释放因为底层还有碎片。这时候需要做一次碎片整理etcdctl defrag --endpointshttp://192.168.1.11:2379注意defrag是一个会阻塞请求的操作生产环境建议在低峰期执行并且最好先对节点做一次备份。多个节点要逐个操作不要同时对所有节点执行。存储配额也需要管起来。默认 2GB 的--quota-backend-bytes对很多集群来说太紧业务稍微写点东西就容易被mvcc: database space exceeded的报错卡死。这个报错一旦出现集群会进入只读模式必须马上处理。我一般会在部署时就把配额显式设置成 8GB 或者更高并辅以空间使用率的监控告警。4.3 网络分区下会发生什么理解网络分区是理解 etcd 行为模式的关键一步。假设一个 3 节点集群中间的网络交换机故障把节点 1 和节点 2 与其他节点隔开。此时 Raft 的多数派也许已经不在节点 1 这一侧那么这一侧的请求会因为无法获得多数派确认而超时失败。等网络恢复之后节点 1、2 会从 Leader 那里同步丢失的日志恢复到一致状态。这里最容易让人迷惑的是网络分区时少数派一侧的 etcd 节点还在运行但业务侧发过去的写请求会全部失败。很多系统在这种状态下仍然会持续重试如果重试逻辑写得不好还会进一步放大故障。我的经验是业务侧连接 etcd 时务必配置好超时并且对请求失败要有明确的降级逻辑不要无脑重试。4.4 性能压测心得知道系统极限才能睡得好觉用etcdctl benchmark压测的时候我通常会记录三个数据写入 QPS、读取 QPS、以及 P99 延迟。附一段常用的压测命令etcdctl benchmark put --endpointshttp://192.168.1.11:2379 \ --total100000 --val-size1024 --concurrency100 --key-patternrand在普通 SSD 的 3 节点集群上纯写入的吞吐大致在每秒 1 万到 2 万次左右读取能高一些。如果数据和这个数量级差得太多就要考虑是否网络有瓶颈或者有没有大量 watch 客户端在拖慢节点。压测的目的不是为了炫数字而是为了给生产容量规划提供依据。你知道了集群的极限在哪做限流、扩容、容量评估的时候心里就有底了。4.5 日常巡检清单我总结了一份自己的巡检清单每次值班会快速过一遍etcd_server_is_leader确认每个节点的角色正常。etcd_server_leader_changes_seen_total短期变化次数超过 2就要查网络和磁盘。etcd_server_slow_apply_totalraft 日志应用耗时过长说明系统负载偏高。etcd_server_proposals_failed_total提案失败一般意味着共识被破坏要看日志。etcd_mvcc_db_total_size_in_bytes存储占用接近配额时提前压缩。etcd_disk_wal_fsync_duration_secondsP99 超过 200ms 时强烈建议升级存储。这套巡检跑下来大部分“隐形故障”都能在影响业务之前被抓住。5. 我的一点经验分享5.1 尽量别拿 etcd 当数据库用经常有人问etcd 能存 XXX 数据吗技术上行不行是一回事设计上应不应该是另一回事。etcd 是协调系统不是数据库。它不适合存超过几 GB 的业务数据不适合放多媒体文件也不适合做高吞吐的日志存储。它的价值区间很明确数据量小、读写不能丢、多节点强一致、需要实时感知变化。一旦你发现你的数据体量开始往 GB 级别走或者查询模式开始变得复杂就该引入专业的数据库了。这是我踩过几次坑之后最深的一个体会。5.2 升级和备份的教训etcd 升级之前完整备份是必须的。我见到过因为跳版本升级导致数据目录不兼容、服务起不来的案例也遇到过升级后因为旧备份格式问题恢复不了的情况。备份命令很简单但一定要验证备份文件可用。我每次都会把 snapshot 文件拉到一台干净的机器上用etcdctl snapshot restore恢复到一个临时目录起一个临时节点确认数据完整再放心结束。这套流程看起来多花几分钟但真到了需要恢复的时候你知道这个备份是能用的那种安心感才值钱。5.3 最后给新手的建议如果你是第一次用 etcd不要一上来就折腾集群和网络。先在本地跑一个单节点用 etcdctl 亲手操作一遍 put、get、watch、lease 这些基础功能感受一下它的行为模式。然后搭一个 3 节点集群故意 kill 掉 Leader观察集群怎么恢复的客户端怎么处理的。这些实验比看十篇文档都有用。我自己在维护 etcd 的过程中最大的感受是这个组件本身并不难难的是理解它和业务之间的关系。它的每一次选主、每一条日志复制、每一个 watch 事件背后都可能对应着你系统的可用性和用户体验。把它的脾气摸透了你再回过头看整个分布式系统的状态管理都会有豁然开朗的感觉。
返回列表