1. 整体分层
下图是etcd 架构 的模型图:
按照分层模型,etcd 可分为 Client 层、API 网络层、Raft 算法层、逻辑层和存储层。这些层的功能如下:
Client 层:Client 层包括 client v2 和 v3 两个大版本 API 客户端库,提供了简洁易用的 API,同时支持负载均衡、节点间故障自动转移,可极大降低业务使用 etcd 复杂度,提升开发效率、服务可用性。
API 网络层:API 网络层主要包括 client 访问 server 和 server 节点之间的通信协议。一方面,client 访问 etcd server 的 API 分为 v2 和 v3 两个大版本。v2 API 使用 HTTP/1.x 协议,v3 API 使用 gRPC 协议。同时 v3 通过 etcd grpc-gateway 组件也支持 HTTP/1.x 协议,便于各种语言的服务调用。另一方面,server 之间通信协议,是指节点间通过 Raft 算法实现数据复制和 Leader 选举等功能时使用的 HTTP 协议。
Raft 算法层:Raft 算法层实现了 Leader 选举、日志复制、ReadIndex 等核心算法特性,用于保障 etcd 多个节点间的数据一致性、提升服务可用性等,是 etcd 的基石和亮点。
功能逻辑层:etcd 核心特性实现层,如典型的 KVServer 模块、MVCC 模块、Auth 鉴权模块、Lease 租约模块、Compactor 压缩模块等,其中 MVCC 模块主要由 treeIndex 模块和 boltdb 模块组成。
存储层:存储层包含预写日志 (WAL) 模块、快照 (Snapshot) 模块、boltdb 模块。其中 WAL 可保障 etcd crash 后数据不丢失,boltdb 则保存了集群元数据和用户写入的数据。
2. 读流程
etcd 中有两种读模式:
- 串行 (Serializable) 读:直接读状态机数据返回、无需通过 Raft 协议与集群进行交互,适合对数据一致性要求不高的场景。
- 线性读:需要经过 Raft 协议模块,反应的是集群共识,因此在延时和吞吐量上相比串行读略差一点,适用于对数据一致性要求高的场景。
说具体点,串行读就是直接返回数据,而线性读先问一下leader 节点的进度,然后对比一下自身的进度,如果慢了就等一下数据同步,等同步完了之后再返回对应的数据
下面这张图展示了follower节点发起线性读的过程:
- 客户端发送请求到Etcd服务器(会先进行认证、鉴权、限速等)
- Etcd服务器为每个请求创建一个唯一的请求ID,并阻塞等待Raft模块的响应
- 在raft模块中,follower 节点向leader节点询问最新的进度,也就是请求readIndex
- 在raft模块中,Leader 收到 ReadIndex 请求时,会向所有的 Follower 节点发送心跳,一半以上节点确认 Leader 身份后,才能将已提交的索引 (committed index)(这里committed index 等于readIndex) 返回给请求节点(主要是为了防止脑裂)
- follower 节点接受readIndex ,比较当前进度是否落后于leader 节点,如果落后,等待数据落盘
- 之后执行对应的读请求,返回对应的数据给客户端
3. 写流程
- 客户端发送写请求到Etcd服务器(会先进行认证、鉴权、限速等)
- Etcd服务器将写请求转发给Raft子系统,如果是follower节点,则将请求转发给leader节点
- leader节点将写请求持久化到其WAL中,并向其他follower节点发送同步消息,并要求它们复制该数据到各自的WAL中,之后对应的follower 接待你返回响应
- leader节点收到一半以上的响应之后,会标记为已提交,并应用到状态机上(也就是etcd server执行对应的写请求),同时也进行广播,让follower也标记为已提交,并应用到状态机上
- Etcd服务器执行对应的写请求,并将数据应用到MVCC存储和后端数据库(BoltDB)
- 最后follower节点返回成功响应
下面是两张更详细的图,详细解释不同模块是如何协作的:
线性读请求(串行读是不会经过raft模块处理,直接返回数据):
写请求:
4. 源码目录
etcd的源码分成三个仓库:
下面是etcd 源码 目录介绍
├── CHANGELOG // 项目的更新日志
├── Documentation // etcd 项目文档
├── api // 网关层,包含与 gRPC 相关的文件,定义服务的 API
├── client // 客户端使用的 API,通常是 `etcd` 的客户端接口
├── contrib // `etcd` 的一些示例代码和贡献的示例
│ ├── lock // 分布式锁示例
│ ├── mixin
│ ├── raftexample // Raft 协议的示例
│ └── systemd
├── etcdctl // `etcdctl` 命令行工具,提供与 `etcd` 交互的命令行接口
├── logos // 项目的 logo 文件
├── pkg // 工具包,包含项目的通用库和功能模块
├── scripts // 脚本文件,用于自动化任务或其他项目相关操作
├── security // 安全相关功能和模块,处理认证、加密等
├── server // `etcd` 的服务器端实现
│ ├── auth // 认证模块
│ ├── config
│ ├── embed
│ ├── etcdmain // `etcd` 服务器的主入口
│ ├── etcdserver // `etcd` 服务器的实现
│ │ ├── api // 与 API 相关的实现
│ │ ├── apply // 处理应用变更的通道,连接 `mvcc`、`wal` 和 `boltdb`
│ │ ├── cindex
│ │ ├── errors
│ │ ├── txn // 事务相关的逻辑
│ │ └── version
│ ├── features
│ ├── lease // `Lease`(租约)管理模块
│ ├── mock
│ ├── proxy
├── storage // 存储系统,管理持久化数据
│ ├── backend // 负责数据同步到 `boltdb`
│ ├── datadir
│ ├── mvcc // 多版本并发控制模块,支持 MVCC 存储
│ ├── schema // `boltdb` 表结构定义
│ └── wal // 预写日志模块
│ └── verify
├── tests // 测试用例和测试代码
└── tools