读写操作过程概述
- 读请求
客户端通过负载选择一个etcd节点发出读请求,API接口层提供Range RPC方法,etcd服务端拦截gRPC 读请求后调用的处理请求。
- 写请求
客户端通过负载均衡选择一个etcd节点发起请求etcd服务端拦截gRPC写请求,涉及校验和监控后KVServer向raft模块发起提案,内容写入数据命令,经过网络转发,当集群中多数节点达成一致持久化数据后,状态变更MVCC模块执行提案内容。
底层实现
在etcd中读请求占大部分,是高频操作,用etcdctl命令行工具进行操作:
etcdctl --endpoints http://localhost:2379 get foo
读操作
etcdctl会创建一个clientv3库对象,选取一个合适的etcd节点,调用KVserver模块的Range RPC方法,发送请求,拦截器拦截,主要做一些校验和监控,然后调用KVserver模块的Range接口获取数据。
读操作的核心步骤
- 线性读ReadIndex模块
- MVCC(包含treeindex和BlotDB)模块
线性读是相对串行读来讲的概念,集群模式下会有多个etcd节点,不同节点间可能存在一致性的问题。串行读直接返回状态数据,不需要与集群中其他节点交互。这种方式速度快,开销小,但是会存在数据不一致的情况。
线性读则需要集群成员之间达成共识,存在开销,响应速度相对慢,但是能保证数据的一致性,etcd默认的读模式线性读。
etcd中查询请求,查询单个键或者一组键及查询数量,到底层实际会调用Range keys方法。
流程如下:
- 在treeIndex中根据键利用BTree快速查询该键对应索引项KeyIndex,索引项中包含Revison
- 根据查询到的版本号信息Revision,在Backend的缓存Buffer中用二分法查找,如命中则直接返回
- 若缓存中不符合条件,在BlotDB中查找,(基于BlotDB的索引),查询后返回键值对的信息。
ReadTx和BatchTx是两个几口,用于读写请求创建Backend结构体,默认也会创建readTx和batchTx。readTx实现了ReadTx,负责处理只读请求batchTx,实现了BatchTx接口,负责处理读写请求。
对于上层的键值存储,它会利用返回的Revision从正真的存储数据中的BoltDB中,查询当前key对应的Revsion数据。BoltDB内部用类似buctket的方式存储对应MySQL中的表结构,用户key数据存放bucket的名字是key etcd mvcc元数据存放bucket的meta.
写操作
使用etcdtl命令行工具进行写操作:
etcdctl --endpoints http://localhost:2379 put foo bar
- 客户端通过负载均衡算法选择一个etcd节点,发起gRPC调用。
- etcd Server收到客户端请求
- 经过gRPC拦截,Quota校验,Quota模块用于校验etcd db文件大小是否超过了配额‘
- KVserver模块将请求发送给本模块的raft,负责与etcd raft模块进行通信,发起一个提案,命令为put foo bar,即使用put方法将foo更新为bar
- 提案经过转发之后,半数节点成功持久化
- MVCC模块更新状态机
更新和插入键值对
raft模块实现分布式一致性
在分布式环境中,常用数据复制来避免单点故障,实现多副本,提高服务的高可用性以及系统的吞吐量。
共识算法,用来解决多个节点数据一致性的问题,常见的共识算法有Paxos和Raft.
etcd raft对外提供的接口
- TIck
时钟,触发选举或者发送心跳
- Propose
通过chanel向raft StateMachine提交一个OP,提交的是本地MsgProp类型的消息
- setp
节点收到Peer节点发送的Msg时会通过该接口提供给raft状态机,step接口通过recvc channel向raft StateMachine传递msg
raft一致性算法实现的关键有Leader选举,日志复制和安全限制。raft算法的第一步是选举Leader。选举流程如下:
发起选选
只有在Candidate或者Follower状态下的节点才有可能发起一个选举流程
- 节点启动的时候,都以Follower启动,同时随机生成自己选举超时的时间
- 当选举超时的时候,节点向自己发送MsgHUP消息
- 状态机函数,收到MsgHup消息之后,节点首先判断当前有没有apply配置变更消息,如有就忽略该消息
- 没有没有该消息,则进入campaign函数进行选举,首先将任期增加1,然后广播给其他节点选举消息,带上其他字段,包括节点当前的最后一条日志索引,最后一条日志对应的任期号,选举任期号,context字段
- 如果在一个选举超时时间内,发起新的选举流程节点,得到超过半数节点投票,那么状态就切换到Leader状态。
参与选举
如果接收到消息是选举类的消息,节点会做出以下判断
- 首先判断该消息是否为强制要求进行选举的类型
- 判断当前是否在租约内。满足条件的包括: checkQuorum为true,当前节点保存的leader不为空,没有到选举超时
- 如果不是忽略选举消息的情况,除非是prevote类的选举消息,否则在收到其他消息的情况下,该节点都切换到Follower状态
- 此时需要针对投票类型带来的其他字段进行处理,同时满足新旧日志的判断和参与选举的条件。
日志复制的流程
leader将日志复制到follower节点
小结
raft库对外提供了一个node接口
节点状态:
1、candiate:候选人状态,该状态意味着将进行一次新的选举
2、follower:追随者状态,该状态意味着选举结束
3、Leader:领导着状态,选举出来的节点,所有的数据提交都必须先提交到Leader上