深入理解etcd(三)--- etcd 的架构是怎么样的?

375 阅读5分钟

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 这个etcd server 的具体实现
  • bbolt 这是etcd 使用的底层数据引擎
  • raft 这是raft 算法实现

下面是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