Xline 源码解读(四)—— CURP 状态机引擎

news2024/10/6 3:20:51

在上一篇源码解读的文章(Xline 源码解读(三) —— CURP Server 的实现)中,我们简单阐述了Xline 的 Curp Server 是如何实现的。接下来,就让我们话接上回,继续深入地来了解 Curp Server 中的一些核心的数据结构,特别是 conflict_checked_channel 和 command worker,它们相互协作,共同推动着 CURP Server 内部状态机状态转换。

01、为什么我们需要冲突检测队列?

冲突检测队列本质是一个多生产者多消费者的 channel,其核心职责是维护动态变化的冲突关系,保证在同一时间内,所有的 receiver 都不会接收到冲突的命令。receiver 接收到的命令的顺序(后面称之为冲突序),满足以下两点:

  1. 如果 cmd A 与 cmd B 存在冲突,并且 A 先于 B 被压入队列当中,则 A 会先从队列中弹出。而 B 只有当 A 执行完毕后,才能从队列中弹出。
  2. 如果 cmd A 与 cmd B 不存在冲突,且 A 先于 B 被压入队列,则按照 FIFO 顺序,即 A 会先从队列中弹出,B 后弹出。

单看文字的话可能有些抽象,不过没有关系,让我们来看一个简单的例子:假设我们现在有 A、B、C 三个 command,进入冲突检测队列的顺序依次为 A,B,C。其中 A 与 B 冲突,而 C 与 A 和 B 均不产生冲突,则冲突检测队列的初始状态如下:

当 A、B、C 都进入到队列当中后,3 个不同的 cmd_worker 分别从 channel 获取 command,由于 cmd_worker_1 会先接收到 A。B 则会因为与 A 产生冲突而被缓存在队列当中。C 由于没有任何命令与其冲突,因此可以被 cmd_worker_2 接收。而 B 只有在 A 被执行并且 drop 之后,才会从队列当中弹出。如下图所示: 

可能有些读者会产生疑问:既然 spec_pool 的职责就是判断 fast path 中是否存在冲突,对应了 CURP paper 中的 witness,那为什么还要引入一个冲突检测队列呢?主要原因有二:

  1. 任何命令,不管走的是 fast path 还是 slow path, 最终都必须要在 leader 上得到执行。因此,leader 必须在这些混合的命令中,找到一个合理的执行顺序,来保证命令的执行不会打破 CURP 对命令执行顺序的假设。而 spec_pool 中只保存了可以在 fast path 被执行的命令,并不涉及 slow path 中的命令
  2. 在达成共识的过程当中,我们必须先判断命令是否存在于 spec_pool 当中。这是一个同步的操作,如果将复杂冲突序的计算工作放到 spec_pool 当中,那会很容易形成 bottleneck。因此,我们将判断是否有冲突和计算出冲突序这两个职责进行拆分,将前者放到同步的 spec_pool 当中,而将后者放到异步的冲突检测队列当中执行。

02、冲突检测队列是如何工作的?

要理解冲突检测队列的工作原理,离不开下面两个问题的探讨:

  1. 我们应当如何对冲突关系进行建模?
  2. 面对会动态变化的冲突关系,我们如何快速地找到所有没有冲突的命令?

针对第一个问题,我们可以将所有的命令都看成是一张有向无环图中的顶点,而将冲突关系视为图中顶点之间的有向边。假设命令 A 和命令 B 之间存在冲突(命令的抵达顺序为先 A 后 B),我们便可将冲突关系看成是 A 和 B 之间的一条弧<A, B>,其中弧头总是指向后进来的顶点(这一顺序性就保证了图中不会出现环形冲突)。

一旦我们将冲突关系定义为一个有向无环的非连通图中的一条边,那么执行某个命令时所需要计算的冲突顺序问题,就转换成了一个求有向无环图中,该命令所在的连通分量的拓扑排序问题。针对每个命令,successors(也就是后继) 保存了哪些 cmd 与当前 cmd 冲突,successors 的长度就是该 顶点的出度。而 predecessor_cnt (前驱数)则代表了这个 cmd 与之前的多少个 cmd 存在冲突,也就是该顶点元素的入度。

同样回到我们前面所提到的 A、B、C 的例子当中,当我们使用 DAG 来描述命令的冲突关系,其情况如下图所示。

当 cmd_worker 从 channel 当中接收命令时,channel 只需要遍历这个有向无环图中的每一个连通分量,并找到第一个入度为 0 的顶点即可。只有当 command 执行完毕后,channel 才会更新 B 的 predecessor_cnt,以解决 A 与 B 之间的冲突关系。

03、状态机引擎的架构

正如我们在文章开头所说的那样,冲突检测队列和 command worker 共同组成了 Curp Server 的状态机引擎。冲突检测队列向 command worker 提供无冲突的命令,而 command worker 则负责执行这些命令,并根据结果推动更新冲突检测队列当中的冲突关系。

从结构上来看,Curp Server 状态机引擎是由三对 channel 和一个 filter 构成,其中这三对 channel 分别为:(send_tx,filter_rx)、(filter_tx, recv_rx) 以及 (done_tx, done_rx)具体的结构可以参考下图。

其数据流方向为:send_tx -> filter_rx -> filter -> filter_tx -> recv_rx -> done_tx -> done_rx

其中,send_tx 为 RawCurp 对象所拥有,负责在 propose(对应了 curp 的 fast path)、以及应用日志 apply(对应了 curp 的 slow path) 时向冲突检测队列投递对应的 CEEvent。冲突检测队列则会在计算出冲突顺序后,将 CEEvent 转换成为 Task 并通过 (filter_tx, recv_rx) 投递到 command worker 当中执行。command worker 在执行完Task 后将结果通过 (done_tx, done_rx) 反馈到冲突检测队列中,并更新队列中依赖图中的顶点信息。

04、状态是如何转换的

要理解一个状态机的工作原理,我们需要理解以下两个问题:

  1. 状态机提供了哪些事件以及哪些状态
  2. 哪些事件会导致状态发生转移 让我们先来看看 Curp Server 的状态机引擎都提供了哪些事件。
/// Event for command executor
enum CEEvent<C> {
    /// The cmd is ready for speculative execution
    SpecExeReady(Arc<LogEntry<C>>),
    /// The cmd is ready for after sync
    ASReady(Arc<LogEntry<C>>),
    /// omit some code...
}

/// CE task
struct Task<C: Command> {
    /// Corresponding vertex id
    vid: u64,
    /// Task type
    inner: TaskType<C>,
}

/// Task Type
enum TaskType<C: Command> {
    /// Execute a cmd
    SpecExe(Arc<LogEntry<C>>, Option<C::Error>),
    /// After sync a cmd
    AS(Arc<LogEntry<C>>, C::PR),
    /// omit some code...
}

从前面的描述中我们可以看出,Curp Server 的状态机引擎主要的事件可以分为两类,一类是 CEEvent,它描述了命令本身的信息,包括了命令的来源,其中 SpecExeReady 表明这个命令是来自于 fast path, 而 ASReadey 则表明这个命令来自于 slow path。而另一类则是 Task,它描述命令在依赖图中的顶点 id,以及当前命令所需要执行的操作。

Curp Server 的状态机引擎也定义了如下的状态:

/// Execute state of a cmd
enum ExeState {
    /// Is ready to execute
    ExecuteReady,
    /// Executing
    Executing,
    /// Has been executed, and the result
    Executed(bool),
}

/// After sync state of a cmd
enum AsState<C: Command> {
    /// Not Synced yet
    NotSynced(Option<C::PR>),
    /// Is ready to do after sync
    AfterSyncReady(Option<C::PR>),
    /// Is doing after syncing
    AfterSyncing,
    /// Has been after synced
    AfterSynced,
}

其中,ExeState 代表命令的执行状态,而 AsState 则代表了命令是否执行完了 after_sync 阶段。Curp Server 通过组合 ExeState 和 AsState 两种状态来代表命令执行过程中的不同状态。不同的状态所代表的语义如下:

  • (ExecuteReady, NotSynced(None): 代表命令已经可以准备执行execute 阶段,并且该命令属于 fast path,无需等待 after_sync 结束即可向用户返回结果。此状态也是 fast path 路径下,状态机引擎的初始状态。
  • (ExecuteReady,AfterSyncRead(None)): 代表命令已经可以准备执行 execute 阶段,并且该命令属于 slow path,需要等到after_sync结束才能向用户返回结果。此状态也是 slow path 路径下,状态机引擎的初始状态。
  • (Executeing,NotSynced(Some(C::PR))): 代表命令执行 pre_execute 成功,并拿到了 pre_execute 的执行结果 Some(C::PR), 同时命令也进入了执行状态。
  • (Executeing,NotSynced(None)):代表命令 pre_execute 执行失败
  • (Executed(true),NotSynced(Some(C::PR))): 代表命令执行成功
  • (Executed(false),NotSynced(None)): 代表 fast path 中的命令执行失败
  • (Executed(true),AfterSyncRead(LogIndex, Some(C::PR))): 代表命令执行完毕,开始准备执行 after_sync
  • (Executed(true),AfterSyncing):代表命令执行完毕,正在执行 after_sync 阶段
  • (Executed(true),AfterSynced): 代表命令执行成功,且 after_sync 也执行成功
  • (Executeing,AfterSyncReady(Some(C::PR))): 代表命令正在执行,并准备执行 after_sync 阶段
  • (Executeing,AfterSyncReady(None)):代表命令的 pre_execute 阶段失败
  • (Executed(false),AfterSyncReady(None)): 代表 slow path 中的命令执行失败。

    各种状态之间的转换关系如下:

05、Summary

在 Xline 中,不论是来自于 fast path 的命令,还是来自 slow path 的命令,这些命令最终都会在 leader 节点上得到执行。在这个过程当中,仅凭 spec_pool 来判断命令是否冲突是显然不够的。因为 spec_pool 只能判断新的命令是否与正在 execute 但还没有 after_sync 的命令冲突,但它不能起到动态维护命令冲突关系的作用。为了解决冲突关系的动态维护,我们引入了冲突检测队列,并通过冲突检测队列和 command worker 共同构造 Curp Server 的状态机引擎。通过 command worker 的执行,使得来自不同路径(fast path 和 slow path)的命令能够根据自身的状态,进行状态转移,并向用户返回相应的结果。

06、往期回顾

Xline 源码解读(一) —— 初识 CURP 协议

Xline 源码解读 (二)—— Lease 的机制与实现

Xline 源码解读(三) —— CURP Server 的实现

Xline是一个用于元数据管理的分布式KV存储。Xline项目以Rust语言写就,欢迎大家参与我们的开源项目!

GitHub链接:

https://github.com/xline-kv/Xline

Xline官网:www.xline.cloud
Xline Discord: 

https://discord.gg/XyFXGpSfvb

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网络安全中的人工智能:优点、缺点、机遇和危险

2022 年秋天&#xff0c;人工智能在商业领域爆发&#xff0c;引起了轰动&#xff0c;不久之后&#xff0c;似乎每个人都发现了 ChatGPT 和 DALL-E 等生成式 AI 系统的新的创新用途。世界各地的企业开始呼吁将其集成到他们的产品中&#xff0c;并寻找使用它来提高组织效率的方法…

Spring Cloud Gateway 路由构建器的源码分析

Spring Cloud Gateway 路由构建器的源码分析 文章目录 1. 路由构建器的入口2. 创建路由规则3. 设置路由规则和属性4. 路由过滤器的设置5. 构建和获取路由规则&#xff1a;6. 实例化路由构建器&#xff1a;8. 路由构建器的源码分析8.1 RouteLocator接口8.2 RouteLocatorBuilder…

漏刻有时地理信息系统LOCKGIS小程序配置说明(web-view组件、服务器域名配置、复制链接和转发功能)

漏刻有时地理信息系统说明文档(LOCKGIS、php后台管理、三端一体PC-H5-微信小程序、百度地图jsAPI二次开发、标注弹窗导航)漏刻有时地理信息系统LOCKGIS小程序配置说明(web-view组件、服务器域名配置、复制链接和转发功能)漏刻有时地理信息系统LOCKGIS主程序配置说明(地图调起弹…

【面试经典150 | 栈】有效的括号

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;栈哈希表 其他语言cpython3 写在最后 Tag 【栈】 题目来源 20. 有效的括号 题目解读 括号有三种类型&#xff0c;分别是小括号、中括号和大括号&#xff0c;每种括号的左右两半括号必须一一对应才是有效的括号&#…

react 中获取多个input输入框中的值的 俩种写法

目录 1. 使用受控组件 2. 使用非受控组件 1. 使用受控组件 这是React中最常见的方法&#xff0c;每个输入框都与React组件的state相关联&#xff0c;并通过onChange事件来更新state。 代码示例&#xff1a; import React, { Component } from react;class MultipleInputExam…

机器人SLAM与自主导航

机器人技术的迅猛发展&#xff0c;促使机器人逐渐走进了人们的生活&#xff0c;服务型室内移动机器人更是获得了广泛的关注。但室内机器人的普及还存在许多亟待解决的问题&#xff0c;定位与导航就是其中的关键问题之一。在这类问题的研究中&#xff0c;需要把握三个重点&#…

安卓使用android studio跨进程通信之AIDL

我写这篇文章不想从最基础的介绍开始,我直接上步骤吧. 1.创建服务端 1.1:创建服务端项目:我的as版本比较高,页面就是这样的 1.2:创建AIDL文件,右键项目,选中aidl aidl名字可以自定义也可以默认 basicTypes是自带的,可以删掉,也可以不删,然后把你自己所需的接口写上去 1.3:创建…

5、Kafka集成 SpringBoot

SpringBoot 是一个在 JavaEE 开发中非常常用的组件。可以用于 Kafka 的生产者&#xff0c;也可以 用于 SpringBoot 的消费者。 1&#xff09;在 IDEA 中安装 lombok 插件 在 Plugins 下搜索 lombok 然后在线安装即可&#xff0c;安装后注意重启 2&#xff09;SpringBoot 环境准…

Vue 3.0中Treeshaking特性是什么?

一、是什么 Tree shaking 是一种通过清除多余代码方式来优化项目打包体积的技术&#xff0c;专业术语叫 Dead code elimination 简单来讲&#xff0c;就是在保持代码运行结果不变的前提下&#xff0c;去除无用的代码 如果把代码打包比作制作蛋糕&#xff0c;传统的方式是把鸡…

VTK8.0.0编译+QT5.9.2+VS2017

背景 VTK网上资料较多并且使用较多的版本可能是VTK8.2.0&#xff0c;但是由于之前先配置了QT 5.9.2 msvc2017 PCL1.8.1 VTK8.0.0环境&#xff0c;听说有人PCL1.8.1配置VTK8.2.0实测版本不兼容&#xff0c;需修改源码调试&#xff0c;比较麻烦&#xff0c;所以之前就使用的VT…

Unity 中3D数学基础-向量

本文主要全面讲解向量的数学运算已经对应的实际应用意义! 1.向量的认识 向量(矢量) 有1、2、3维! 向量既可以表示大小也可以表示方向,也可以表示一个空间坐标点!因此他虽然是一个数学数字表示,但是实际空间意义有这三种! 坐标点A(x,y,z) 表示与原点连线构成的方向…

前端常见的数据类型有哪些?

在前端开发中,常见的数据类型包括: 1:字符串(String):表示文本数据,用引号(单引号或双引号)括起来,例如:“Hello, World!”。 创建字符串:let str = ‘Hello, World!’;获取字符串长度:let length = str.length;字符串拼接:let newStr = str1 + str2;2:数字(N…

SystemVerilog(2)——数据类型

一、概述 和Verilog相比&#xff0c;SV提供了很多改进的数据结构。它们具有如下的优点&#xff1a; 双状态数据类型&#xff1a;更好的性能&#xff0c;更低的内存消耗队列、动态和关联数组&#xff1a;减少内存消耗&#xff0c;自带搜索和分类功能类和结构&#xff1a;支持抽…

微信小程序之首页-后台交互及WXS的使用

目录 前言 一. 前后台数据交互及封装request 1.准备后台 1.1 配置数据源 1.2 部分后台获取数据方法编写 2.准备前端 2.1封装Request 2.2 前端JS方法编写 2.3 前端页面展示index.wxml 二.WXS的使用 1.简介 2.WXS优化OA系统 2.1 使用及定义 2.2 导入要使用的项目 2.…

【代码随想录第45天】动态规划5

代码随想录第45天| 动态规划5 1049. 最后一块石头的重量 II494. 目标和474.一和零 1049. 最后一块石头的重量 II LeetCode题目&#xff1a;1049. 最后一块石头的重量 II 代码随想录&#xff1a;1049. 最后一块石头的重量 II 思路就是尽量把石头分成重量总和相等的两堆&#xff…

计算机操作系统-第十二天

目录 进程控制的基本概念 什么是进程控制 如何实现进程控制 如何实现原语的”原子性“ 与进程控制相关的原语 进程创建中的原语 进程终止中的原语 进程的阻塞和唤醒中的原语 进程的切换中的原语 知识滚雪球-程序是如何运行的&#xff1f;&#xff1a; 本节思维导图 进…

数据可视化与GraphQL:利用Apollo创建仪表盘

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 「推荐专栏」&#xff1a; ★java一站式服务 ★ ★ React从入门到精通★ ★前端炫酷代码分享 ★ ★ 从0到英雄&#xff0c;vue成神之路★ ★ uniapp-从构建到提升★ ★ 从0到英雄&#xff…

第二证券:风电概念强势拉升,威力传动“20cm”涨停,双一科技等大涨

风电概念20日盘中强势拉升&#xff0c;到发稿&#xff0c;威力传动“20cm”涨停&#xff0c;双一科技涨超17%&#xff0c;顺发恒业亦涨停&#xff0c;金雷股份、大金重工涨约7%&#xff0c;新强联、海力风电涨超5%。 音讯面上&#xff0c;9月以来江苏、广东海风项目加快推动&a…

前端数据可视化之【title、legend、tooltip、toolbox 】配置项

目录 &#x1f31f;Echarts配置项&#x1f31f;Echarts配置项之 title组件&#x1f31f;Echarts配置项之 legend组件&#x1f31f;Echarts配置项之 tooltip组件&#x1f31f;Echarts配置项之 toolbox组件&#x1f31f;写在最后 &#x1f31f;Echarts配置项 ECharts开源来自百度…

如何解决香港服务器使用的常见问题

​  站长们在选择香港服务器租用时会考虑到它的各种性能以及稳定性&#xff0c;这是必须的。但是使用过程中还有些问题也不容忽视&#xff0c;比如&#xff1a;带宽资源是否短缺&#xff0c;是否存在安全漏洞&#xff0c;连接是否正常等这些问题也要考虑到。 香港服务器使用中…