跳到主要内容

ETCD 探索

· 阅读需 8 分钟

写这篇文章的起因,是我在整理 Kubernetes 相关知识时,反复撞见 etcd 这个名字:集群状态存在它里面,服务发现依赖它,连不少配置中心的底层也是它。与其把它当黑盒,不如单独拿出来搭一遍、理一遍。在分布式系统中,经常会遇到这样的问题:

  • 服务节点需要共享配置
  • 系统需要做服务发现
  • 分布式锁需要一个协调中心
  • 集群需要一个一致性的状态存储

这些问题,本质上都需要一个 可靠的分布式协调系统

单机时代这些需求可以用一个数据库表甚至一个文件解决,但节点一多,问题就变成了:多个副本之间如何对"当前状态"达成一致?谁说了算?某个节点挂了之后数据还可信吗?这正是分布式协调系统要回答的问题。

而在现代云原生体系中,最常用的组件就是 etcd

例如:

  • Kubernetes
  • CoreDNS
  • service mesh
  • 分布式配置中心

这些系统的底层都依赖 etcd。

etcd 是什么

简单来说:

etcd 就是一个高可靠的分布式 Key-Value 数据库。

但它和普通数据库最大的区别是:

它是为"分布式协调"而设计的。

这个定位决定了它的取舍:它不追求海量数据和极限吞吐,而是追求"存进去的每一条数据都可信"。所以它适合存元数据、配置、状态这类小而关键的数据,而不适合当业务数据库用。

它的主要特点有:

  • 强一致(Strong Consistency)
  • 支持分布式集群
  • 提供 Watch 监听机制
  • 支持事务
  • 提供租约(Lease)机制

这几个特性组合起来很有意思:

  1. Watch 让客户端可以订阅某个 key 或前缀的变化,配置一改,所有节点实时感知,不用轮询。

  2. Lease 相当于带 TTL 的"心跳凭证",key 可以绑定在租约上,客户端一旦停止续约,key 自动删除——服务注册与故障剔除就是靠它实现的。

  3. 事务(Txn)提供 compare-and-swap 语义,"如果这个 key 不存在就写入"可以原子完成,这是分布式锁的基础。

很多分布式系统都会用 etcd 做:

  • 服务注册中心
  • 配置中心
  • 分布式锁
  • Leader 选举

例如 Kubernetes 就把整个 集群状态 存在 etcd 中。kube-apiserver 是唯一直接读写 etcd 的组件,其他控制器都通过 apiserver 的 Watch 机制间接感知状态变化。

强一致性(Raft 共识算法)

etcd 内部使用的是 Raft 共识算法

Raft 的核心思想是:

整个集群中会选举出一个 Leader 节点

所有写请求必须经过 Leader。

流程大致是:

客户端 → Leader → 同步到多数节点 → 提交成功

只有当:

超过一半节点确认写入

数据才算真正提交。

这里可以再拆细一点。Raft 中每个节点处于三种角色之一:Leader、Follower、Candidate。正常运行时只有一个 Leader,它周期性地向所有 Follower 发送心跳;如果某个 Follower 在超时时间内收不到心跳,它会把自己变成 Candidate 发起选举,拿到多数票的节点成为新 Leader。

写入走的是日志复制(Log Replication):Leader 先把写操作追加到自己的日志,再并行发给所有 Follower;当多数派(majority,即超过半数)节点都持久化了这条日志,Leader 才把它标记为已提交,然后应答客户端。

"多数派"这个设计是强一致的关键:任何两个多数派集合必然有交集,所以即使 Leader 崩溃,新选出的 Leader 也一定包含所有已提交的数据,不会丢写。

这也解释了为什么 etcd 集群节点数推荐是奇数(3、5、7):

  • 3 节点允许挂 1 个,5 节点允许挂 2 个
  • 4 节点同样只允许挂 1 个(多数派需要 3 个),容错能力和 3 节点一样,反而多了一份同步开销

etcd 集群搭建

部署环境:

操作系统:

Debian 11.5.0

1 下载 etcd

从 github 下载 etcd 二进制文件:

https://github.com/etcd-io/etcd/releases

release 页面按平台提供压缩包,选对应架构(如 linux-amd64)下载即可。etcd 是单个静态二进制,没有额外的运行时依赖,这也是它部署简单的原因之一。

2 安装 etcd

解压之后,将以下文件放入:

/usr/local/bin

主要是:

etcd
etcdctl

两个文件分工明确:etcd 是服务端进程,etcdctl 是命令行客户端,后面所有的查询、写入、维护操作都通过它完成。放进 /usr/local/bin 是为了让它们进入 PATH,任意目录下都能直接调用。

3 设置执行权限

# 解压出来的文件可能没有可执行位,手动补上
sudo chmod +x /usr/local/bin/etcd
sudo chmod +x /usr/local/bin/etcdctl

到这一步,单个节点的二进制就绪。集群模式下,每个节点都重复上述安装步骤,再通过启动参数声明各自的名字、监听地址和初始集群成员列表;节点间通过 2380 端口互相通信,客户端通过 2379 端口访问。我这套环境启用了 TLS,证书统一放在 /opt/etcd/ssl 下,所以下面的验证命令都要带上证书参数。

4 验证集群

可以使用 etcdctl 查看数据:

# 列出集群中所有 key(只看 key 不看 value)
# --cacert/--cert/--key:TLS 双向认证所需的 CA 证书与客户端证书
# --endpoints:把三个节点都写上,客户端会自动负载与故障转移
ETCDCTL_API=3 etcdctl \
--cacert=/opt/etcd/ssl/ca.pem \
--cert=/opt/etcd/ssl/server.pem \
--key=/opt/etcd/ssl/server-key.pem \
--endpoints="https://172.24.93.151:2379,https://172.24.93.149:2379,https://172.24.93.150:2379" \
get / --prefix --keys-only

命令能返回结果,说明客户端到集群的链路、证书、多数派状态都是通的。--prefix 表示按前缀匹配,配合 / 相当于遍历全部 key。

删除 key:

# 按前缀删除,空前缀 "" 会匹配所有 key —— 生产环境慎用
ETCDCTL_API=3 etcdctl \
--cacert=/opt/etcd/ssl/ca.pem \
--cert=/opt/etcd/ssl/server.pem \
--key=/opt/etcd/ssl/server-key.pem \
--endpoints="https://172.24.93.151:2379,https://172.24.93.149:2379,https://172.24.93.150:2379" \
del --prefix ""
注意

del --prefix "" 是全量删除。如果这套 etcd 正在给 Kubernetes 当后端,这条命令等于清空整个集群状态,执行前务必确认目标环境。

etcd 为什么比 Redis 更适合做分布式协调

很多人会问:

Redis 不是也可以做分布式锁吗?

确实可以。

但 Redis 是 AP 系统,而 etcd 是 CP 系统

简单理解就是:

Redis 更注重 可用性与性能

etcd 更注重 一致性与可靠性

具体到锁的场景,差异体现在故障时的行为:Redis 主从复制是异步的,主节点写入锁之后还没同步就宕机,从节点提升为主后锁就"丢"了,两个客户端可能同时持有锁;Redlock 方案试图缓解这个问题,但社区对其安全性一直有争议。而 etcd 的写入必须经过多数派确认才算成功,Leader 切换不会丢已提交的数据,锁的语义在故障场景下依然成立。

代价当然是性能:一次写入要走一轮多数派同步和磁盘落盘,延迟和吞吐都远不如 Redis。所以结论不是谁更好,而是分场景——缓存、计数、允许偶发失效的轻量锁用 Redis;配置、选主、必须严格互斥的锁用 etcd。

踩坑与注意

搭建和使用过程中有几点值得记录:

  1. ETCDCTL_API=3 这个环境变量别漏。老版本 etcdctl 默认走 v2 API,v2 和 v3 的数据完全隔离,用错 API 版本会出现"明明写了却查不到"的假象。较新的版本已默认 v3,但在脚本里显式声明总没错。

  2. --endpoints 建议写全所有节点。只写一个节点时,该节点一旦故障,客户端就直接失联;写全之后客户端能自动切换到健康节点。

  3. 证书路径和 SAN 要匹配。启用 TLS 后,证书里的 IP/域名必须覆盖 endpoints 里实际使用的地址,否则握手阶段就会被拒。

  4. 节点数保持奇数。前面 Raft 部分已经解释过原因:偶数节点不增加容错能力,只增加同步成本。

  5. etcd 对磁盘延迟敏感。Raft 日志每次提交都要 fsync,磁盘慢会直接拖高写延迟甚至触发 Leader 选举抖动,条件允许尽量用 SSD。

小结

etcd 本质上是"用性能换一致性"的一个典型设计:Raft 多数派提交保证了任何时刻读到的已提交数据都可信,Watch、Lease、事务这些机制则把"可信的状态存储"进一步变成了服务发现、选主、分布式锁的通用底座。搭建本身不复杂——两个二进制加一组启动参数——真正需要花心思的是 TLS 证书、节点规划和对它适用边界的判断。把它和 Redis 的分工想清楚,各用在各自擅长的场景,比争论谁强更有意义。

评论 / COMMENTS