前言
上一篇文章我们知道了 etcd 是一个分布式的 key-value 存储。但"分布式"具体是怎么实现的?3 台机器组成一个集群,它们之间是怎么分工合作的?
这篇文章,我们来拆解 etcd 集群的架构。
一、3 个节点,3 种角色
在 etcd 集群中,每个节点在任意时刻都处于以下三种角色之一:
| 角色 | 职责 | 数量 |
|---|---|---|
| Leader(领导者) | 处理所有写请求,定期发心跳给 follower | 1 个 |
| Follower(追随者) | 被动同步 leader 的数据,处理读请求 | 2 个 |
| Candidate(候选者) | 选举期间的临时角色 | 0 或 多个 |
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Leader │ │ Follower │ │ Follower │
│ (主) │←───→│ (从) │←───→│ (从) │
│ 处理写请求 │ │ 同步数据 │ │ 同步数据 │
│ 发心跳 │ │ 处理读请求 │ │ 处理读请求 │
└──────────────┘ └──────────────┘ └──────────────┘
查看集群成员
bash
etcdctl member list
输出示例:
member xxxx: name=node-1 peerURLs=http://10.0.0.1:2380 clientURLs=http://10.0.0.1:2379 isLeader=false
member yyyy: name=node-2 peerURLs=http://10.0.0.2:2380 clientURLs=http://10.0.0.2:2379 isLeader=true
member zzzz: name=node-3 peerURLs=http://10.0.0.3:2380 clientURLs=http://10.0.0.3:2379 isLeader=false
从 isLeader 字段可以知道谁是当前的 leader。
二、两个端口:2379 和 2380
etcd 集群使用两个端口通信:
| 端口 | 用途 | 类比 |
|---|---|---|
| 2379 | Client 端口,给应用(客户端)用的 | 前台接待 |
| 2380 | Peer 端口,etcd 节点之间通信用的 | 员工通道 |
应用(你的服务)
│
│ 2379(客户端连接)
▼
┌──────────────────────────────┐
│ etcd 集群 │
│ │
│ ┌──────┐ 2380 ┌──────┐ │
│ │节点 A │←──────→│节点 B │ │
│ └──────┘ └──────┘ │
│ ↑ ↑ │
│ │ 2380 │ 2380 │
│ ↓ ↓ │
│ ┌──────┐ │
│ │节点 C │ │
│ └──────┘ │
└──────────────────────────────┘
关键理解:节点之间不需要 2379
etcd 节点之间通过 2380 同步数据、选举 leader。不需要互开 2379,因为 2379 是给客户端用的。
etcd 节点之间:
├── 2380 → ✅ 必须互开,用于数据同步、选举 leader
└── 2379 → ❌ 不需要互开,这是给客户端用的
三、防火墙策略实践
在实际部署时,建议用 firewalld 或 iptables 限制端口访问:
bash
# 允许 etcd 节点之间 2380 通信
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.1" port protocol="tcp" port="2380" accept'
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.2" port protocol="tcp" port="2380" accept'
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.3" port protocol="tcp" port="2380" accept'
# 允许应用访问 2379
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.100" port protocol="tcp" port="2379" accept'
firewall-cmd --reload
四、写请求的完整流程
当应用要修改一个配置值时,完整的流程是这样的:
1. 应用发请求到任意节点(比如节点 A)
2. 如果节点 A 不是 leader:
→ 节点 A 把请求转发给 leader(通过 2380 或 2379,取决于客户端版本)
3. leader 收到请求:
a. 写入自己的 WAL 日志
b. 并行发给所有 follower
4. follower 收到 → 写入自己的 WAL → 回复 leader "收到"
5. leader 收到过半确认(自己 + 至少 1 个 follower)→ 返回成功
6. 应用收到 "修改成功"
为什么需要"过半确认"?
3 节点集群,quorum = 2
如果 leader 写入成功就返回,不等待 follower:
→ leader 突然宕机
→ 数据还没同步到其他节点
→ 数据丢了 ❌
如果 leader 等待过半确认:
→ leader 写入 → 同步到至少 1 个 follower → 返回成功
→ leader 宕机 → 另一个 follower 有数据 → 当选新 leader
→ 数据不丢 ✅
五、读请求:线性读 vs 串行读
etcd 支持两种读模式:
线性读(Linearizable Read)
客户端 → 读请求 → follower → 转发给 leader
leader 确认自己还是 leader → 返回最新数据
特点:保证读到最新数据,但多了一次网络转发
串行读(Serializable Read)
客户端 → 读请求 → 任意节点 → 直接返回自己内存中的数据
特点:性能好,但可能读到毫秒级延迟的旧数据
用哪个取决于客户端 API 版本
| 客户端 API | 默认读模式 | 说明 |
|---|---|---|
| etcd v2 API | 串行读 | v2 不支持线性读,固定串行 |
| etcd v3 API | 线性读 | 默认线性读,可改为串行读 |
六、查看集群状态
bash
# 集群健康
etcdctl cluster-health
# 成员列表
etcdctl member list
# 详细状态(v3 API)
export ETCDCTL_API=3
etcdctl --endpoints=http://127.0.0.1:2379 endpoint status --write-out=table
输出示例:
+------------------+------------------+---------+---------+-----------+-----------+------------+
| ENDPOINT | ID | VERSION | DB SIZE | IS LEADER | RAFT TERM | RAFT INDEX |
+------------------+------------------+---------+---------+-----------+-----------+------------+
| 127.0.0.1:2379 | ad245981f2b01e41 | 3.3.11 | 123 MB | false | 12345 | 67890 |
+------------------+------------------+---------+---------+-----------+-----------+------------+
| 字段 | 含义 |
|---|---|
| VERSION | etcd 二进制版本 |
| DB SIZE | 数据文件大小 |
| IS LEADER | 是否 leader |
| RAFT TERM | 第几次选举周期 |
| RAFT INDEX | 已经处理了多少条写操作 |
小结
etcd 集群的核心就是:3 个节点,1 个 leader,2 个 follower,2380 同步数据,2379 服务客户端。
- 写操作必须经过 leader,需要过半确认
- 读操作取决于 API 版本(v2 串行,v3 线性)
- 防火墙策略:2380 节点间互开,2379 只对客户端开放
下一篇文章,我们来深入 Raft 协议------分布式一致性的核心算法。
上一篇:[etcd 学习系列(一):从业务需求出发,理解 etcd 是什么](#etcd 学习系列(一):从业务需求出发,理解 etcd 是什么)
下一篇:[etcd 学习系列(三):Raft 协议 ------ 分布式一致性就是这么简单](#etcd 学习系列(三):Raft 协议 —— 分布式一致性就是这么简单)