【etcd】读路径与一致性:ReadIndex、Serializable 与 Lease read
内容提要
etcd v3.5.33 读路径分三种:默认线性一致读用 ReadIndex 确认 Leader 并等待 apply;Serializable 读本地执行,可能读到旧数据;Lease 读不经 Raft,直接访问 Leader 的 lessor。混用易致排障混淆。线性读等待 apply 后走 MVCC,历史读超 compact 返回 ErrCompacted。选型建议:控制面用线性读,扩展用 Serializable,锁用线性加版本比较。
延伸解读
三种读语义的排障分叉
etcd 默认线性一致读、Serializable 读和 Lease 相关 RPC 走不同路径,混用是排障时“读到旧 resourceVersion”或“锁 Lease 已过期但 TTL 仍显示正数”的第一分叉。理解各自语义(线性读等待 apply、Serializable 本地读可能 stale、Lease 读直接访问 lessor)有助于快速定位问题根源。
Serializable 读的适用边界
Serializable 读跳过 ReadIndex 和 apply 等待,延迟更低、吞吐更高,但可能读到落后数据,尤其 follower 的 applied index 可能落后于 Leader。它适合缓存预热、可容忍 lag 的统计等场景,但 K8s apiserver 对 etcd 的 Get/List 默认应走线性一致读,自行用 WithSerializable() 接 follower 做读扩展不等于 apiserver 语义。
Lease 读的两种含义
“Lease read”在 etcd 语境下需区分:一是 Raft 层的 ReadOnlyLeaseBased 选项,依赖 Leader lease 跳过 quorum ack,但 etcd v3.5.33 默认使用 ReadOnlySafe,未启用该选项;二是 Lease RPC(KeepAlive、TimeToLive)在 Leader 上直接访问 lessor,不写 Raft 日志,TTL 以 Leader 上 lessor 为准,Leader 切换时存在 Promote/Demote 窗口。
Q&A
etcd 默认的读一致性是什么?如何实现?
etcd 默认采用线性一致读(linearizable read),通过 ReadIndex 机制实现:客户端请求到达 Leader 后,Leader 向 Raft 发起 ReadIndex 请求,确认当前 commit index,然后等待本地 applied index 追上该 commit index,最后在本地 MVCC 上执行读操作。这保证了读取到的数据是已 apply 且不超过确认 commit 点的状态。
etcd 的 Serializable 读有什么特点?适合什么场景?
Serializable 读(通过设置 RangeRequest.serializable=true 或 etcdctl --consistency=s)跳过线性一致读的等待,直接在本地 member 上执行读操作,因此延迟更低、吞吐更高,但可能读到旧数据(stale)。适合缓存预热、可容忍 lag 的统计等场景,不适合需要强一致性的控制面操作。
etcd 的 Lease read 具体指什么?与 KV Lease 字段有何区别?
在 etcd 语境下,Lease read 有两层含义:一是 Raft 层的 ReadOnlyLeaseBased 选项,它依赖 Leader lease 跳过 quorum ack,但 etcd v3.5.33 默认未启用,且时钟漂移时不安全;二是 Lease 相关的 RPC(如 LeaseKeepAlive、LeaseTimeToLive),这些操作在 Leader 上直接访问 lessor,不经过 Raft 日志,TTL 以 Leader 上 lessor 为准,与 MVCC key 的 ModRevision 无关。
etcd 线性一致读在 Leader 切换或 ReadIndex 超时时如何处理?
当 Leader 切换时,线性一致读会返回 ErrLeaderChanged,客户端应重试到其他 member;在新 term 首条 commit 前,会重发 ReadIndex;如果 ReadIndex 超时,会重试并可能导致 slow_read_index_total 计数堆积。
etcd 中历史读(指定 revision)在什么情况下会返回 ErrCompacted?
当 RangeRequest 中指定了 revision(revision > 0)进行历史读时,如果该 revision 小于 compactMainRev(即已被压缩),则会返回 ErrCompacted 错误。
etcd 中如何实现分布式锁或 CAS 操作?推荐使用哪种读一致性?
分布式锁或 CAS 操作推荐使用线性一致读(linearizable)并结合 ModRevision 比较。因为线性一致读能保证读取到最新已提交状态,配合 ModRevision 比较可以安全地实现乐观锁或条件更新。
etcd 的 LeaseTimeToLive 在 Leader 切换时可能遇到什么问题?
LeaseTimeToLive 在 Leader 上直接访问 lessor,如果 Leader 切换,会存在 Promote/Demote 窗口,期间可能返回 ErrLeaderChanged 或 ErrNotPrimary,客户端应切换 endpoint 重试。