etcd读写请求的执行过程

etcd读请求如何执行

首先,etcdctl 会对命令中的参数进行解析。在解析完请求中的参数后,etcdctl 会创建一个 clientv3 库对象通过gRPC API来访问 etcd server。对应流程一。

然后通过负载均衡算法选择一个etcd server节点,然后调用 etcd server 的 KVServer 模块的 Range RPC 方法,把请求发送给 etcd server。在 etcd 3.4 中,clientv3 库采用的负载均衡算法为 Round-robin,对应流程二。

KVServer 收到 client 的 Range RPC 请求后,首先会根据 ServiceName 和 RPC Method 将请求转发到对应的 handler 实现,handler 首先会将metrics、日志、请求行为检查等一系列拦截器串联后执行。之后就进入核心的读流程,对应架构图中的流程三和四,这里首先介绍下串行读和线性读。

串行读

etcd 的串行 (Serializable) 读是直接读状态机数据返回、无需通过 Raft 协议与集群进行交互的模式。具有低延时、高吞吐量的特点,适合对数据一致性要求不高的场景。

线性读

etcd 默认读模式是线性读,因为它需要经过 Raft 协议模块反应的是集群共识,因此在延时和吞吐量上相比串行读略差一点,适用于对数据一致性要求高的场景。即一个值更新成功,随后任何通过线性读的 client 都能及时访问到。

线性读之 ReadIndex

串行读时之所以能读到旧数据,主要原因是当 client 发起一个写请求, Leader 收到写请求后会将此请求持久化到 WAL 日志,并广播给各个Follower节点,若一半以上节点持久化成功则该请求对应的日志条目被标识为已提交,Follower的etcdserver 模块再异步的从 Raft 模块获取已提交的日志条目,应用到状态机 (boltdb 等) 。

ReadIndex在 etcd 3.1 中引入的,当收到一个线性读请求时,Follower节点首先会向Raft 模块(即Leader)发送ReadIndex请求,此时Leader 会先向 Follower 节点发送心跳确认,一半以上节点确认 Leader 身份后才能将已提交日志索引 (committed index) 封装成 ReadState 结构体通过 channel 层层返回给线性读模块。线性读模块等待本节点状态机的已应用日志索引 (applied index) 大于等于 Leader 的已提交日志索引,就通知 KVServer 模块,可以与状态机中的 MVCC 模块进行交互读取数据了。

MVCC

MVCC多版本并发控制模块是为了解决提到 etcd v2 不支持保存 key 的历史版本、不支持多 key 事务等问题而产生的。它核心由内存树形索引模块 (treeIndex) 、嵌入式的 KV 持久化存储库 boltdb 、buffer组成。

其中boltdb是基于 B+ tree 实现的 key-value 键值库,支持事务,提供 Get/Put 等简易 API 给 etcd 操作。boltdb 的 key 是全局递增的版本号 (revision),value 是用户 key、value 等字段组合成的结构体,然后通过 treeIndex 模块来保存用户 key 和版本号的映射关系

treeIndex 模块是基于 Google 开源的内存版 btree 库实现的。

buffer则是在获取到版本号信息后,并不是所有请求都一定要从 boltdb 获取数据。etcd 出于数据一致性、性能等考虑,在访问 boltdb 前,首先会从一个内存读事务 buffer 中,二分查找你要访问 key 是否在 buffer 里面,若命中则直接返回。

img

img

etcd写请求如何执行

首先 client 端通过负载均衡算法选择一个 etcd 节点,发起 gRPC 调用。然后 etcd 节点收到请求后经过 gRPC 拦截器、Quota 模块后,进入 KVServer 模块,KVServer 模块向 Raft 模块提交一个提案,提案内容为“ put 一个 key 为 hello,value 为 world 的数据”。随后此提案通过 RaftHTTP 网络模块转发、经过集群多数节点持久化后,状态会变成已提交,etcdserver 从 Raft 模块获取已提交的日志条目,传递给 Apply 模块,Apply 模块通过 MVCC 模块执行提案内容,更新状态机。

Quota 模块

client 端发起 gRPC 调用到 etcd 节点,和读请求不一样的是,写请求需要经过Quota模块。当 etcd server 收到 put/txn 等写请求的时候,会首先检查下当前 etcd db 大小加上你请求的 key-value 大小之和是否超过了配额(quota-backend-bytes)。如果超过了配额,它会产生一个 NO SPACE的告警,并通过 Raft 日志同步给其它节点,告知 db 无空间了,并将告警持久化存储到 db 中。

因为etcd v3 是个 MVCC 数据库,保存了 key 的历史版本,当你未配置压缩策略(compact)的时候,随着数据不断写入,db 大小会不断增大,导致超限。压缩模块支持按多种方式回收旧版本,比如保留最近一段时间内的历史版本。不过它仅仅是将旧版本占用的空间打个空闲(Free)标记,后续新的数据写入的时候可复用这块空间而无需申请新的空间。

如果你需要回收空间减少 db 大小,得使用碎片整理(defrag), 它会遍历旧的 db 文件数据并写入到一个新的 db 文件。但是它对服务性能有较大影响,不建议你在生产集群频繁使用。

KVServer 模块

通过配额检查后,请求就从 API 层转发到了 KVServer 模块的 put 方法,我们知道 etcd 是基于 Raft 算法实现节点间数据复制的,因此它需要将 put 写请求内容打包成一个提案消息,提交给 Raft 模块。不过 KVServer 模块在提交提案前,还有如下的一系列检查和限速。

Preflight Check:为了保证集群稳定性,避免雪崩,任何提交到 Raft 模块的请求,都会做一些简单的限速判断。如果 Raft 模块已提交的日志索引(committed index)比已应用到状态机的日志索引(applied index)超过了 5000,那么它就返回一个"etcdserver: too many requests"错误给 client。

鉴权:尝试去获取请求中的鉴权信息,若使用了密码鉴权、请求中携带了 token,如果 token 无效,则返回"auth: invalid auth token"错误给 client。

包大小:会检查你写入的包大小是否超过默认的 1.5MB, 如果超过了会返回"etcdserver: request is too large"错误给给 client。

Propose

通过一系列检查之后,会生成一个唯一的 ID并将请求关联到一个对应的消息通知 channel,然后向 Raft 模块发起(Propose)一个提案(Proposal),即流程四。向 Raft 模块发起提案后,KVServer 模块会等待此 put 请求,等待写入结果通过消息通知 channel 返回或者超时。etcd 提交提案的默认超时时间是 7 秒(5 秒磁盘 IO 延时 +2*1 秒竞选超时时间),如果一个提案请求超时未返回结果,则可能会出现你熟悉的 etcdserver: request timed out 错误。

WAL 模块

Raft 模块收到提案后,如果当前节点是 Follower,它会转发给 Leader,只有 Leader 才能处理写请求。Leader 收到提案后,通过 Raft 模块输出 待转发给 Follower 节点的消息 和 待持久化的日志条目 给etcdserver,同时该etcdserver作为 Leader,它会将 put 提案消息广播给集群各个节点,此时leader及Follower节点需要需要把集群 Leader 任期号、投票信息、已提交索引、提案内容持久化到一个 WAL(Write Ahead Log)日志文件中,每个提案在被提交前都会各个节点被持久化到 WAL 日志文件中,以保证集群的一致性、可恢复性(crash后重启恢复),即流程五。

WAL 模块如何持久化 Raft 日志条目

首先将 Raft 日志条目内容(含任期号、已提交索引、提案内容)序列化后保存到 WAL 记录的 Data 字段, 然后计算 Data 的 CRC 值,设置 Type 为 Entry Type, 以上信息就组成了一个完整的 WAL 记录。最后计算 WAL 记录的长度,然后调用 fsync 持久化到磁盘,完成将Raft日志条目保存到持久化存储中。

一半以上节点持久化此Raft日志条目(WAL预写日志)后, Raft 模块就会通过 channel 告知 etcdserver 模块,put 提案已经被集群多数节点确认,提案状态为已提交,可以继续向Apply模块提交提案内容。

于是进入流程六,etcdserver 模块从 channel 取出提案内容,添加到先进先出(FIFO)调度队列,随后通过 Apply 模块按入队顺序,异步、依次执行提案内容。

Apply 模块

Apply 模块在执行提案内容前,会首先判断当前提案是否已经执行过了,如果执行过了则直接返回,若未执行同时无 db 配额满告警,则进入到 MVCC 模块,开始与持久化存储模块打交道,Apply模块执行 put 提案内容则对应流程七。

在考虑故障场景,若 put 请求提案在执行流程七的时候 etcd 突然 crash 了, 重启恢复的时候,etcd 是如何找回异常提案,再次执行呢。

核心就是我们上面介绍的 WAL 日志,因为提交给 Apply 模块执行的提案已获得多数节点确认即持久化到WAL日志文件中,etcd 重启时会从 WAL 中解析出 Raft 日志条目内容,追加到 Raft 日志的存储中,并重放已提交的日志提案到 Apply 模块执行

然而这又引发了另外一个问题,如何确保幂等性,防止提案重复执行导致数据混乱。

Raft 日志条目中的索引(index)字段是全局单调递增的,每个日志条目索引对应一个提案, 如果一个命令执行后,我们在 db 里面也记录下当前已经执行过的日志条目索引,则可以解决幂等性问题。

但是如果执行命令的请求更新成功了,但是更新 index 的请求却失败了,一样会导致异常,因此我们还需要将两个操作作为原子性事务提交,才能实现幂等。

MVCC

Apply 模块判断此提案未执行后,就会调用 MVCC 模块来执行提案内容。MVCC 主要由三部分组成,一个是内存索引模块 treeIndex,是一个内存版 BTree ,保存用户 key 和版本号 (revision)的映射关系;另一个是 boltdb 模块,是基于 B+tree 实现的 key-value 嵌入式 db,通过提供桶(bucket)机制实现类似 MySQL 表的逻辑隔离,boltdb 的 key 是全局递增的版本号,value 是用户 key、value 等字段组合成的结构体;另一个是bucket buffer 用来保存 合并后异步定时批量提交 导致的暂未提交的事务数据。

img

版本号(revision)在 etcd 里面发挥着重大作用,etcd 启动的时候默认版本号是 1,随着你对 key 的增、删、改操作而全局单调递增,boltdb则使用版本号作为key。

boltdb 的 value具体包括了用户key 值,value 值、key 创建时的版本号(create_revision)、最后一次修改时的版本号(mod_revision)、key 自身修改的次数(version),租约信息。

虽然boltdb put 调用成功,但etcd 并未提交事务,数据只更新在 boltdb 所管理的内存数据结构中。事务提交的过程,包含 B+tree 的平衡、分裂,将 boltdb 的脏数据(dirty page)、元数据信息刷新到磁盘,因此事务提交的开销是昂贵的。如果我们每次更新都提交事务,etcd 写性能就会较差。

etcd 采用是合并后异步定时批量提交,etcd 通过合并多个写事务请求,异步定时的(默认每隔 100ms)将批量事务一次性提交从而刷新到磁盘, 从而大大提高吞吐量,仅pending 事务过多才会触发同步提交。但这样也存在事务未提交,读请求可能无法从 boltdb 获取到最新数据的问题。

为了解决这个问题,etcd 引入了一个 bucket buffer 来保存暂未提交的事务数据。在更新 boltdb 的时候,etcd 也会同步数据到 bucket buffer。因此 etcd 处理读请求的时候会优先从 bucket buffer 里面读取,其次再从 boltdb 读,通过 bucket buffer 实现读写性能提升,同时保证数据一致性。

img

要点记录

1、线性读 ReadIndex需要等待本节点状态机的已应用日志索引 (applied index) 大于等于 Leader 的已提交日志索引,才能继续读取数据。

2、etcd 提交提案的默认超时时间是 7 秒,如果一个提案请求超时未返回结果,则会出现的 etcdserver: request timed out 错误。

3、当一半以上节点持久化了WAL日志后, Raft 模块才会通过 channel 告知 etcdserver 模块,put 提案已经被集群多数节点确认,提案状态为已提交,可以继续提交给Apply模块执行。

etcd 重启时会从 WAL预写式日志中解析出 Raft 日志条目内容,并重放已提交的日志提案到 Apply 模块执行。

etcd 在启动的时候会将etcd db文件内容拷贝到etcd进程内存中,启动拷贝是需要磁盘IO,节点内存足够的请求下,后续处理读请求过程中就不会产生磁盘 I/IO 了。

4、MVCC 主要由三部分组成,一个是内存索引模块 treeIndex,实现是BTree,用于保存用户 key 和版本号 (revision)的映射关系;另一个是 boltdb 模块,是基于 B+tree 实现的 key-value 嵌入式 db,通过提供桶(bucket)机制实现类似 MySQL 表的逻辑隔离,boltdb 的 key 是全局递增的版本号,value 是用户 key、value 等字段组合成的结构体;第三个是bucket buffer 用来保存 合并后异步定时批量提交 导致的暂未提交的事务数据。

5、etcd采用合并后异步定时批量提交,etcd 通过合并多个写事务请求,异步定时的(默认每隔 100ms)将事务批量一次性提交,进而刷新到磁盘, 从而大大提高吞吐量。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/90433.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【计算机组成 课程笔记】2.1 设计自己的计算机

课程链接: 计算机组成_北京大学_中国大学MOOC(慕课) 2 - 1 - 201-设计自己的计算机(14‘24’‘)_哔哩哔哩_bilibili 什么是指令系统体系结构?这个问题其实非常简单,但要想解释清楚也没有那么容易。我们还是从一个小故事…

机器学习技术(六)——有监督学习算法之线性回归算法实操

机器学习技术(五)——有监督学习之线性回归算法实操 引言: 机器学习监督算法是一种基于已有标记数据的学习方法,通过对已知输入和输出数据的学习,建立一个模型来预测新的输入数据的输出。这种算法模仿人类的学习过程&a…

Pycharm保存自定义布局

1.启用特定窗口 在View->Tool Windows下可以启用特定窗口,窗口标签会出现在左边(图中红框处),下边或右边,可以拖动摆放位置 2.保存 在windows->layout下可以选择保存布局 1.图中第一个选项:选择或…

【Kafka】Kafka Stream简单使用

一、实时流式计算 1. 概念 一般流式计算会与批量计算相比较。在流式计算模型中,输入是持续的,可以认为在时间上是无界的,也就意味着,永远拿不到全量数据去做计算。同时,计算结果是持续输出的,也即计算结果…

计算机网络(速率、宽带、吞吐量、时延、发送时延)

速率: 最重要的一个性能指标。 指的是数据的传送速率,也称为数据率 (data rate) 或比特率 (bit rate)。 单位:bit/s,或 kbit/s、Mbit/s、 Gbit/s 等。 例如 4 1010 bit/s 的数据率就记为 40 Gbit/s。 速率往往是指额定速率或…

设计模式--代理模式(Proxy Pattern)

一、什么是代理模式(Proxy Pattern) 代理模式(Proxy Pattern)是一种结构型设计模式,它允许一个对象(代理)充当另一个对象(真实对象)的接口,以控制对该对象的…

video标签在安卓手机内置浏览器上的播放问题

道阻且长,行而不辍,未来可期 问题: 在手机内置浏览上,虽然没有给video标签设置controls,但在内置浏览器上,就是会显示。 而且,video一旦自动播放,video的层级就会提升到最前,想设置一个盒子覆盖…

lambda 表达式

C自学精简实践教程 目录(必读) 什么是lambda表达式? lambda表达式实际上就是一个类似函数的可以被调用的对象。 和函数一样可以使用圆括号调用。 只不过,这个函数可以定义在代码的任意位置。非常的灵活,自由。而函数不能定义在其他函数的…

【数据结构】多叉树转换为二叉树-c++代码实现-POJ 3437 Tree Grafting

文章目录 写这个题目的原因寻找提交网址题目解决思路AC代码成功AC 写这个题目的原因 1、今天在看王道考研数据结构的课(虽然我要保研,但是因为这些看保研面试的时候会问,所以看一下嘞orz),看到了这个多叉树转换为二叉…

【YOLOV5】YOLOV5添加SPPCSPC

当前YOLOV5版本为7.0 第一步 在models/common.py添加SPPCSPC class SPPCSPC(nn.Module):# CSP https://github.com/WongKinYiu/CrossStagePartialNetworksdef __init__(self, c1, c2, n1, shortcutFalse, g1, e0.5, k(5, 9, 13)):super(SPPCSPC, self).__init__()c_ int(2 *…

CS144(2023 Spring)Lab 0:networking warmup(环境搭建 webget bytestream)

文章目录 前言其他笔记相关链接 1. Set up GNU/Linux on your computer2. Networking by hand3. Writing a network program using an OS stream socket3.1 Linux配置3.2 C规范3.3 Writing webget3.3.1 实现3.3.2 测试 4. An in-memory reliable byte stream4.1 思路分析4.2 代…

以GitFlow分支模型为基准的Git版本分支管理流程

以GitFlow分支模型为基准的Git版本分支管理流程 文章目录 以GitFlow分支模型为基准的Git版本分支管理流程GitFlow分支模型中的主要概念GitFlow的分支管理流程图版本号说明借助插件Git Flow Integration Plus实现分支模型管理其他模型TBD模型阿里AoneFlow模型 GitFlow分支模型中…