etcd读写以及数据一致性原理分析
1 读流程
架构:

流程1:etcd客户端发起一个get请求,这个 请求底层是基于gRPC完成的
流程2:然后etcd服务端通过gRPC接收请求, 在gRPC中会注册多个过滤器,不过最终会调 用到KVServer中的接口
流程3:KVServer接收到请求后会调Raft模块, 去拿集群中最新的版本号
流程4:拿到版本号之后,KVServer继续调用 MVCC模块,根据key和版本号去拿treeIndex中的一个版本,根据上面那个版本去boltdb拿真实的value
1.1 客户端
也就是流程1,具体步骤:
- etcdctl启动客户端(实际就是Go语言底层执行main方法)
- 启动过程中拿底层操作系统的cmd指令,并根据不同的参数封装各种命令,然后通过传进来的参数选择执行不同的命令
- 在客户端进行负载均衡,etcd默认的是round-robin算法
- 最终etcd客户端创建gRPC客户端,调etcd服务端的KVServer模块
源码流程:

1.2 服务端
前置:启动etcdserver端
流程2:KVServer接收get请求
流程3:等待数据同步
流程4:去数据库里面拿数据
1.2.1 前置
- 启动server/main.go中的main方法
- 创建一个Etcd的实例,里面有个etcdserver
- 创建一个etcdserver对象然后赋值给上面
- 创建了各种异步协程、通道、注册各种模块 (KVServer、拦截器)到gRPC
- 启动

1.2.2 流程
- Etcdserver接收get请求,被拦截器给拦截
- 执行Key.go中Range,判断是否是线性读(默认线性读)
- 跟Leader进行通信,判断ReadIndex是不是最新的
- 不是的话就等着Raft那个携程进行数据同步
- 拿到最新的ReadIndex,然后根据key去数据库拿数据

2 写流程
写只能Leader处理,Follower只能读
⚫ 如何保证数据不丢失——集群来保证
⚫ 写命令如何不重复执行——幂等性来保证
架构:

流程1:etcd客户端发起一个put请求
流程2:etcd服务端中的拦截器接收请求,先做配额限制
流程3:如果没有没有超过配额,进入KV模块
流程4:走Raft模块保证各节点的数据一致
流程5:各节点将Raft日志进行持久化,防止节点在数据存到 boltdb之前挂掉导致数据丢失
流程6:将要持久化到boltdb的数据发送给Apply
流程7:数据基于MVCC进行持久化
2.1 客户端
与读流程差不多,不重复讲述
2.2 服务端
2.2.1 Quota(配额)模块
就是etcd boltdb中能存的最大字节,默认是2GB, 如果超过配额会报NO SPACE告警,调大配额,还需要执行etcdctl alarm disarm取消告警。⚫ 修改配额:启动服务端的时候加参数:–quota-backendbytes=20(20表示字节)
.jpg)
2.2.2 KV模块

2.2.3 整体源码

3 数据一致性—强一致性
Raft协议:http://thesecretlivesofdata.com/raft/
➢ Leader,集群领导者, 唯一性,拥有同步日志的特权,需定时广播心跳给 Follower节点,以维持领导者身份
➢ Candidate,竞选者,可以发起Leader选举
➢ Follower,跟随者, 同步从Leader收到的日志,etcd启动的时候默认为此状态
数据同步过程:

安全性:
➢ 选举规则
⚫ 检查候选者的最后一条日志中的任期号,若小于自己则拒绝投票。如果任期号相同,日志却比自己短,也拒绝为其投票。
⚫ 每个节点在同一个任期内只能为一个节点投票
➢ 日志复制规则
⚫ Leader完全特性:如果某个日志条目在某个任期号中已经被提交,那么这个条目必然出现在更大 任期号的所有Leader中
⚫ 只附加原则:Leader只能追加日志条目,不能删除已持久化的日志条目
⚫ 日志匹配原则:Leader在发送追加日志RPC消息时,会把新的日志条目紧接着之前的条目的索引 位置和任期号包含在里面。Follower节点会检查相同索引位置的任期号是否与Leader一致,一致 才能追加
4 总结
➢ Etcd客户端读写流程
⚫ 解析参数并选择命令 ⚫ 进行负载均衡重试等操作 ⚫ 发起gRPC请求
➢ Etcd服务端读流程
⚫ 前置开启服务以及各种携程,并注册拦截器,最终调KVServer模块 ⚫ 发起线性读保证安全
➢ Etcd服务端写流程
⚫ 前置开启服务以及各种携程,并注册拦截器,调Quota进行配额限制、然后调KVServer进行限流、 校验、限制大小等操作 ⚫ 走Raft模块,保证各节点数据同步 ⚫ 收到各节点同步日志成功通知则走Apply模块,进行幂等性判断 ⚫ 走MVCC模块保存不同版本的KV