1. 绪论
etcd 作为华为云 PaaS 的核心部件,实现了 PaaS 大多数组件的数据持久化、集群选举、状态同步等功能。如此重要的一个部件,我们只有深入地理解其架构设计和内部工作机制,才能更好地学习华为云 Kubernetes 容器技术,笑傲云原生的“江湖”。本系列将从整体框架再细化到内部流程,对 etcd 的代码和设计进行全方位解读。本文是《深入浅出 etcd》系列的第一篇,重点解析 etcd 的架构和代码框架,下文所用到的代码均基于 etcd v3.2.X 版本。
另,由华为云容器服务团队倾情打造的《云原生分布式存储基石:etcd 深入解析》一书已正式出版,各大平台均有发售,购书可了解更多关于分布式存储和 etcd 的相关内容!
2. etcd 简介
etcd 是一个分布式的 key-value 存储系统,底层通过 Raft 协议进行 leader 选举和数据备份,对外提供高可用的数据存储,能有效应对网络问题和机器故障带来的数据丢失问题。同时它还可以提供服务发现、分布式锁、分布式数据队列、分布式通知和协调、集群选举等功能。为什么 etcd 如此重要?因为 etcd 是 Kubernetes 的后端唯一存储实现,毫不夸张地说,etcd 就是 Kubernetes 的“心脏”。
2.1 Raft 协议
要理解 etcd 分布式协同的工作原理,必须提到 Raft 算法。Raft 算法是斯坦福的 Diego Ongaro、John Ousterhout 两人以易懂(Understandability)为目标设计的一致性共识算法。在此之前,提到共识算法(Consensus Algorithm)必然会提到 Paxos,但是 Paxos 的实现和理解起来都非常复杂,以至于 Raft 算法提出者的博士论文中,作者提到,他们用了将近一年时间研究这个算法的各种解释,但还是没有完全理解这个算法。Paxos 的算法原理和真正实现也有很大的距离,实现 Paxos 的系统,如 Chubby,对 Paxos 进行了很多的改进有优化,但是细节却是不为人所知的。 Raft 协议采用分治的思想,把分布式协同的问题分为 3 个问题:
选举: 一个新的集群启动时,或者老的leader故障时,会选举出一个新的leader;
日志同步: leader必须接受客户端的日志条目并且将他们同步到集群的所有机器;
安全: 保证任何节点只要在它的状态机中生效了一条日志条目,就不会在相同的key上生效另一条日志条目。
一个Raft集群一般包含数个节点,典型的是5个,这样可以承受其中2个节点故障。每个节点实际上就是维护一个状态机,节点在任何时候都处于以下三种状态中的一个。
leader:负责日志的同步管理,处理来自客户端的请求,与Follower保持这heartBeat的联系;
follower:刚启动时所有节点为Follower状态,响应Leader的日志同步请求,响应Candidate的请求,把请求到Follower的事务转发给Leader;
candidate:负责选举投票,Raft刚启动时由一个节点从Follower转为Candidate发起选举,选举出Leader后从Candidate转为Leader状态。
节点启动以后,首先都是 follower 状态,在 follower 状态下,会有一个选举超时时间的计时器(这个时间是在配置的超时时间基础上加一个随机的时间得来的)。如果在这个时间内没有收到 leader 发送的心跳包,则节点状态会变成 candidate 状态,也就是变成了候选人,候选人会循环广播选举请求,如果超过半数的节点同意选举请求,则节点转化为 leader 状态。如果在选举过程中,发现已经有了 leader 或者有更高的任期值的选举信息,则自动变成 follower 状态。处于 leader 状态的节点如果发现有更高任期值的 leader 存在,则也是自动变成 follower 状态。
Raft 把时间划分为任期(Term)(如下图所示),任期是一个递增的整数,一个任期是从开始选举 leader 到 leader 失效的这段时间。有点类似于一届总统任期,只是它的时间是不一定的,也就是说只要 leader 工作状态良好,它可能成为一个独裁者,一直不下台。
2.2 etcd 的代码整体架构
etcd 整体架构如下图所示:
从大体上可以将其划分为以下 4 个模块:
http:负责对外提供http访问接口和http client
raft 状态机:根据接受的raft消息进行状态转移,调用各状态下的动作。
wal 日志存储:持久化存储日志条目。
kv数据存储:kv数据的存储引擎,v3支持不同的后端存储,当前采用boltdb。通过boltdb支持事务操作。
相对于 v2,v3 的主要改动点为:
使用grpc进行peer之间和与客户端之间通信;
v2的store是在内存中的一棵树,v3采用抽象了一个kvstore,支持不同的后端存储数据库。增强了事务能力。
去除单元测试代码,etcd v2的代码行数约40k,v3的代码行数约70k。
2.3 典型内部处理流程
我们将上面架构图的各个部分进行编号,以便下文的处理流程介绍中,对应找到每个流程处理的组件位置。
2.3.1 消息入口
一个 etcd 节点运行以后,有 3 个通道接收外界消息,以 kv 数据的增删改查请求处理为例,介绍这 3 个通道的工作机制。
client的http调用:会通过注册到http模块的keysHandler的ServeHTTP方法处理。解析好的消息调用EtcdServer的Do()方法处理。(图中2)
client的grpc调用:启动时会向grpc server注册quotaKVServer对象,quotaKVServer是以组合的方式增强了kvServer这个数据结构。grpc消息解析完以后会调用kvServer的Range、Put、DeleteRange、Txn、Compact等方法。kvServer中包含有一个RaftKV的接口,由EtcdServer这个结构实现。所以最后就是调用到EtcdServer的Range、Put、DeleteRange、Txn、Compact等方法。(图中1)
节点之间的grpc消息:每个EtcdServer中包含有Transport结构,Transport中会有一个peers的map,每个peer封装了节点到其他某个节点的通信方式。包括streamReader、streamWriter等,用于消息的发送和接收。streamReader中有recvc和propc队列,streamReader处理完接收到的消息会将消息推到这连个队列中。由peer去处理,peer调用raftNode的Process方法处理消息。(图中3、4)
2.3.2 EtcdServer 消息处理
对于客户端消息,调用到 EtcdServer 处理时,一般都是先注册一个等待队列,调用 node 的 Propose 方法,然后用等待队列阻塞等待消息处理完成。Propose 方法会往 propc 队列中推送一条 MsgProp 消息。 对于节点间的消息,raftNode 的 Process 是直接调用 node 的 step 方法,将消息推送到 node 的 recvc 或者 propc 队列中。 可以看到,外界所有消息这时候都到了 node 结构中的 recvc 队列或者 propc 队列中。(图中 5)
2.3.3 node 处理消息
node 启动时会启动一个协程,处理 node 的各个队列中的消息,当然也包括 recvc 和 propc 队列。从 propc 和 recvc 队列中拿到消息,会调用 raft 对象的 Step 方法,raft 对象封装了 raft 的协议数据和操作,其对外的 Step 方法是真正 raft 协议状态机的步进方法。当接收到消息以后,根据协议类型、Term 字段做相应的状态改变处理,或者对选举请求做相应处理。对于一般的 kv 增删改查数据请求消息,会调用内部的 step 方法。
内部的 step 方法是一个可动态改变的方法,将随状态机的状态变化而变化。当状态机处于 leader 状态时,该方法就是 stepLeader;当状态机处于 follower 状态时,该方法就是 stepFollower;当状态机处于 Candidate 状态时,该方法就是 stepCandidate。leader 状态会直接处理 MsgProp 消息。将消息中的日志条目存入本地缓存。follower 则会直接将 MsgProp 消息转发给 leader,转发的过程是将先将消息推送到 raft 的 msgs 数组中。 node 处理完消息以后,要么生成了缓存中的日志条目,要么生成了将要发送出去的消息。缓存中的日志条目需要进一步处理(比如同步和持久化),而消息需要进一步处理发送出去。
处理过程还是在 node 的这个协程中,在循环开始会调用 newReady,将需要进一步处理的日志和需要发送出去的消息,以及状态改变信息,都封装在一个 Ready 消息中。Ready 消息会推行到 readyc 队列中。(图中 5)
2.3.4 raftNode 的处理
raftNode 的 start()方法另外启动了一个协程,处理 readyc 队列(图中 6)。
取出需要发送的 message,调用 transport 的 Send 方法并将其发送出去(图中 4)。
调用 storage 的 Save 方法持久化存储日志条目或者快照(图中 9、10),更新 kv 缓存。
另外需要将已经同步好的日志应用到状态机中,让状态机更新状态和 kv 存储,通知等待请求完成的客户端。因此需要将已经确定同步好的日志、快照等信息封装在一个 apply 消息中推送到 applyc 队列。
2.3.5 EtcdServer 的 apply 处理
EtcdServer 会处理这个 applyc 队列,会将 snapshot 和 entries 都 apply 到 kv 存储中去(图中 8)。
最后调用 applyWait 的 Trigger,唤醒客户端请求的等待线程,返回客户端的请求。
3. 重要的数据结构
3.1 EtcdServer
EtcdServer:是整个 etcd 节点的功能的入口,包含 etcd 节点运行过程中需要的大部分成员。
3.2
raftNode 是 Raft 节点,维护 Raft 状态机的步进和状态迁移。
3.3 node
4、 小结
本文简要介绍了 raft 协议和 etcd 的框架介绍了 etcd 内部的和消息流的处理。后续将分心跳和选举、数据同步、数据持久化等不同专题详细讲述 etcd 的内部机制。
本文转载自华为云产品与解决方案公众号。
原文链接:https://mp.weixin.qq.com/s/C2WKrfcJ1sVQuSxlpi6uNQ
评论