Windmill:最快的自托管开源工作流引擎
我们对 Windmill 进行了基准测试,认为它是 Airflow、Prefect 甚至 Temporal 中最快的自托管通用工作流引擎。对于 Airflow,有速度快了 10 倍!
工作流引擎编排工作人员的有向无环图 (DAG) 中定义的作业,同时尊重依赖性。
主要优点包括资源分配、并行性、可观察性和持久性。高效的工作流引擎可以快速调度工作人员的作业,并且工作人员可以快速拉取和运行作业。
由于使用 Postgresql 进行状态管理和原始 SQL 语句之间的转换,避免了最终的一致性问题,Windmill 被强调为非常快。工作被流水线化以提高效率。
网友评论:
- Windmill 因其快速的性能、对 Discord 的积极开发人员支持以及按需触发工作流程和脚本的能力而受到赞誉。
- 虽然速度很重要,但更重要的因素是工作流引擎是否可以支持同时运行的多个作业,并且每个作业的性能可预测。
- 使用 Postgres 作为后端受到质疑,建议探索 MongoDB 等基于文档的数据库,以避免数据库迁移问题。
- 由于 Windmill 的脚本功能以及与 VS Code 等工具的集成,Windmill 被认为特别适合开发人员,但非开发人员也成功将其用于内部工具。
Temporal
从某种程度上来说,Temporal不是一个工作流引擎,而是一个专门的持久执行引擎。
Temporal 实际上并不管理工作,而只管理任务队列。因此,即使在编写了临时工作流程之后,您仍然需要单独管理您的工作人员。
Windmill 还支持反应性(又名等待事件),并且也可以作为持久执行引擎。
Temporal 的功能令人惊叹,如果 Windmill 和 Temporal 之间存在重叠,那么显然在某些用例中您应该使用 Temporal 而不是 Windmill(作为 Uber 规模的微服务异步模式的骨干)
另一方面,将任意作业发送到内部集群超出了 Temporal 的范围,因为您仍然需要事先痛苦地部署“工作程序”。
我们将 Spark 或 Dagster 等分析/ETL 引擎排除在外,因为它们本身不是工作流引擎,即使它们是构建在工作流引擎之上的。
工作流引擎与作业
作业队列是工作流引擎的核心,构成任何后台作业处理的关键。已经有很多出色的队列实现,以托管服务 (SQS)、分布式可扩展服务 (Kafka) 和软件(例如:带有 rmsq 的 Redis)或库 (Orban) 的形式。它们大多足以自行使用,并且许多开发人员会通过围绕作业队列构建自己的逻辑来完全避免使用工作流引擎,从而获得满足。这类似于编写您自己的专用工作流引擎。
什么是工作流引擎,什么是“包罗万象”的工作流
首先是一些定义,工作流是由代表作业规范的节点组成的有向无环图 DAG。工作流引擎是一种分布式系统,它能接收工作流,并协调工作流在工作者身上完成,同时尊重每个作业的所有依赖约束。工作流的种类繁多,许多软件都有特定领域的工作流引擎和工作流规范(如果你是一名软件工程师,你可能已经在不知不觉中编写了一个)。
在这里,我们感兴趣的是至少能用一种主要编程语言(Python/Typescript/Go/Bash)运行任意代码的工作流。这些工作流最通用,但也最复杂,最难优化。每个节点都是一段代码,它从其他步骤(或流程输入)获取参数和数据作为输入,执行一些副作用(http 请求、计算、写入磁盘/s3),然后返回一些数据供其他步骤使用。
工作流程引擎的主要5个优势:
- 资源分配:可充分利用集群,将每项作业分配给拥有不同资源(CPU、内存、GPU)的不同工作员,并保证工作员的全部资源都能为作业所用。
- 并行性:并行性:当工作流的约束条件允许某些步骤并行运行(分支、for-loop)时,工作流引擎可以将这些步骤分派给多个物理上独立的工作者,而不仅仅是线程。
- 可观察性:每个作业都有一个唯一的 ID,可以单独观察:可以检查输入、日志、输出和状态
- 耐用性:机器死机、副作用因意外原因失效。工作流需要在接近意外事件发生时可重新启动。实现这一点的方法之一是惰性:单个操作与多个相同操作的效果相同。如果有疑问,可以重放整个流程而不产生任何后果。这通常通过日志文件和 sdk 来实现,当操作附加的唯一 ID 是日志的一部分时,sdk 会跳过副作用。另一种方法是对流程状态进行事务快照,存储每次操作后的状态。要恢复时,只需重新加载最后的状态,然后从那里开始执行。Windmill 采用的是后者,并假定在用户领域需要时可以实现幂等性。
- 反应性:暂停流程,直到根据 webhook 或批准等事件再次恢复流程。
此外,一个包罗万象的工作流引擎应能动态注册新的可用工作人员,并能轻松部署新的工作流,将不同的工作分配给不同的工作人员,以及监控工作人员和系统本身的健康状况。
工作流引擎之上的开发者平台应能处理权限问题,使不同权限级别的用户可以运行不同的工作流,并使这些工作流能根据调用者的角色访问不同的资源。
为什么 Windmill 速度很快?
在工作流引擎中,"效率 "一词取决于:
-
计算转换的效率,根据上次完成的工作安排新工作的效率,以及工人本身提取已安排工作并运行它们的效率
-
在步骤之间传递数据的效率
-
工作程序提取作业、开始执行代码并提交结果和新状态的效率
Windmill 的速度极快,因为它在这三个方面都采用了简单的设计,处处优化,最大限度地利用了 Postgresql 和 Rust。
系统设计和队列
Windmill 提供了一个二进制文件(由 Rust 编译而成),既可以作为 api 服务器运行,也可以作为 Worker 运行。Worker 和服务器都连接到 Postgresql,但彼此互不连接。服务器只公开 api 和前端。队列是在 Postgresql 本身中实现的。作业可以通过调用 API 从外部触发,API 会将新作业推送到队列中。
作业存储在 Postgresql 的两个表中:
-
queue(当作业未完成时,甚至在运行时)
-
completed_job
作业在启动时不会从队列中移除,但其字段 running 会被设置为 true。队列是通过传统的 UPDATE SKIP LOCKED 来实现的。
UPDATE queue
SET running = true
, started_at = coalesce(started_at, now())
, last_ping = now()
, suspend_until = null
WHERE id = (
SELECT id
FROM queue
WHERE running = false AND scheduled_for <= now() AND tag = ANY($1)
ORDER BY priority DESC NULLS LAST, scheduled_for, created_at
FOR UPDATE SKIP LOCKED
LIMIT 1
)
RETURNING *
只要每个字段都有适当的索引,Postgresql 就能做到最快的速度。这种方法的一些变种是将作业从队列中删除,而不是更新标记,或者设置一个时间,在这个时间段内,其他作业无法被拉动。在引擎盖下的原理都是一样的。
推送流程作业时,其输入、指向不可变流程定义的指针和初始流程状态(见下)都会被推送到作业行中(这是一个很大的行!)。
然后,一个 Worker 会选择流程作业,读取流程定义和流程状态,并意识到它需要推送队列中的第一步,仅此而已。这就是流程的初始转换。
如果作业是流程的一个步骤(该流程作业是一个单独的作业,并将父作业设置为该流程作业),则工作者会一次拉一个作业,将其运行至完成,并推进状态。服务器本身不进行协调,每个流程的转换都由工人自己完成。
状态
工作流引擎用有限状态机(FSM)来表示作业。常用的 4 种主要状态是
-
等待先决条件(收到 webhook 等事件,或完成所有相关作业)
-
前提条件已满足,等待工作者拉取作业并执行它
-
运行
-
完成(成功或失败)
其他状态通常是对上述 4 种状态的细化
在 Windmill 中,整个流程本身就是一个有限状态机,流程的规格和流程状态都是易于读取的结构。
流程状态完全由步进计数器和流程模块状态数组定义。有些流程模块状态更为复杂,例如与 for 循环和分支相对应的状态。需要注意的一个有趣方面是,分支或 for 循环迭代等子流程都是各自定义明确的作业,它们的指针指向触发这些作业的父流程(parent_flow)。
因此,分支和 for-loop 是一种特殊的流程状态,其中包含一个 ID 数组,指向所有已启动的子流程。当按顺序执行 for 循环/分支时,过渡包括查看流程状态,如果还有迭代,则开始下一次迭代;如果没有迭代,则完成该步骤。
这种设计的方便之处在于,每个状态转换都只是一个事务性的 Postgresql 语句。Postgresql 是 ACID,我们可以充分利用这些特性。在工作流引擎中,达到最终一致性是最难、最慢的事情。我们可以通过以下方式跳过困难的部分
-
使用实现了 MVCC 和行级锁的 Postgresql。
-
在工作结束时,由工作者自己完成转换
流状态是以 JSONB 的形式实现的,因此状态转换是以原始 sql 编写的,它直接更改队列中与流作业相对应的行中的流状态。这既正确又极其高效。这部分并没有真正受益于 Rust,可以用任何语言实现,甚至可以直接用 PL/SQL 实现。
最棘手的两个过渡
-
嵌套流程的最后一步(分支的分支)
-
并行分支/迭代
详细介绍
- 在嵌套流程的最后一步,工作程序会更新父流程的状态,但会意识到该流程现在也已完成,因此会简单地递归到该流程的父流程,并进行流程转换。
- 并行步骤:在启动该步骤时,所有子流程都会排队,而不是一次运行一个流程。然后,任何完成每个分支子流程最后一步的工作者都会原子式地增加一个计数器。将计数器增加到与迭代次数一样长的工作者知道,由于整个步骤已经完成,因此它需要对整个步骤进行转换。
此外,对已完成任务的处理是在后台 tokio 任务中完成的,该任务在通道中接收已完成的任务,从而在一定程度上实现了流水线作业。工人无需等待数据库完全确认作业,就能接收另一个可用作业。
Windmill 的转换速度非常快,因为它们是作为原始 Postgresql 语句实现的,而且在执行作业和更新数据库之间有管道连接。
数据传递
在 Windmill 中,有 3 种主要的数据传递方式:
-
一个步骤的每个输入都可以是一个 javascript 表达式,可以引用任何步骤的输出
typecript、python、go、bash 的每个脚本都有其主要签名(由前端的 WASM 程序解析),可以预先计算给定步骤所需的不同输入。对于每个输入,我们都可以定义一个静态输入或一个 javascript 表达式,它可以引用任何步骤的结果,例如:results.d.foo,其中 d 是步骤的 id。复杂的 javascript 表达式将由嵌入式 v8 使用 Deno 运行时进行评估。按表达式计算大约需要 8 毫秒。
鉴于每个步骤结果本身就是一个作业,其中包含 json 格式的结果,因此只需一条 sql 语句就能检索到所需的结果。节点 id 到作业 id 的映射保存在流程作业状态中。
此外,大多数表达式都很琐碎,可以直接转换为原始 jsonb 语句:
SELECT result #> $3 as result FROM completed_job WHERE id = $1 AND workspace_id = $2"
其中 $3 为
json*path.map(|x| x.split(".").map(|x| x.to_string()).collect::<Vec<*>>())
- 共享临时文件夹中的数据 可以将流程配置为完全在同一个 Worker 上执行。在这种情况下,每个作业的临时文件夹内都会共享一个文件夹并用同义词链接(作业在临时文件夹中启动,执行结束后会删除该文件夹)
- 使用 s3 集成在 s3 中传递数据(该部分的具体更新将在第 5 天介绍)
工作者worker效率
在正常模式下,工作者一次拉取一个作业,识别作业使用的语言(python、typecript、go、bash、snowflake、Postgresql、mysql、mssql、bigquery),然后生成相应的运行时,然后运行作业。
与基于容器的工作流引擎相比,Worker 可以裸运行作业,而无需运行容器,从而提高了性能。不过,出于沙箱的目的,工作程序本身可以在容器内运行,并在 nsjail 沙箱中运行每个作业。
对于查询语言来说,除了建立连接外,没有冷启动。bash 没有冷启动,而 go 脚本在 AOT 中编译,然后在本地缓存二进制文件,以避免冷启动。
支持执行任意的 python 代码很难,因为我们必须支持任何带锁文件的导入。由于 Worker 不会在容器中运行 python 代码,因此必须动态处理依赖关系。为此,我们开发了一种高效的分布式缓存系统,它可以动态 pip 安装特定的一对(软件包、版本),并在运行中创建一个动态虚拟环境来执行代码。在运行代码之前,我们会对导入进行有效分析。
类似地,在 typescript 中,运行时要么是 deno,要么是 bun,这样它们就能从全局缓存中获益,永远不必重复安装相同的依赖关系。
然而,快速处理依赖关系还不足以达到最高速度,还需要冷启动生成一个 python(约 60 毫秒)或 deno/bun (约 30 毫秒)进程。在事件流情况下,逻辑本身大约需要 1 毫秒,因此冷启动是运行脚本开销的 30 倍。
幸运的是,我们最近为脚本实现了专用 Worker,它在 Worker 开始时催生一次 python 进程,然后在 while 循环中执行脚本逻辑,在 stdin 中接收作业输入,并将输出返回到 sdout。
我们将这种方法扩展为流程专用 Worker,本质上是相同的。一开始,这些 Worker 会为 Python 或 Typescript 实现的每个步骤生成一个相应的专用进程。这就彻底消除了冷启动,使 Windmill 流能够处理偶数流用例,每个 Worker 每秒可处理多达 1000 个步骤。
流的专用工作者会拉取与流相关的任何作业,然后将其路由到适当的专用进程。仍然一次只执行一个作业,但在预热流程中执行。
结论
Windmill 的速度非常快,因为它依赖于 Postgresql 和 Rust,并采用了简单的设计,能够优化每个部分,无论大小。这与用 Zig 实现的 Bun 快速的原因有点类似,都是尽可能地进行了优化。
Windmill是一个开源且可自托管的无服务器运行时和平台,将代码的强大功能与低代码的速度相结合。我们将您的脚本转换为内部应用程序和可组合的流程步骤,以自动化重复的工作流程。
https://www.jdon.com/70126.html
相关文章:
Windmill:最快的自托管开源工作流引擎
我们对 Windmill 进行了基准测试,认为它是 Airflow、Prefect 甚至 Temporal 中最快的自托管通用工作流引擎。对于 Airflow,有速度快了 10 倍! 工作流引擎编排工作人员的有向无环图 (DAG) 中定义的作业,同时尊重依赖性。 主要优点…...
线性代数 - 几何原理
目录 序言向量的定义线性组合、张成空间与向量基线性变换和矩阵线性复合变换与矩阵乘法三维空间的线性变换行列式矩阵的秩和逆矩阵维度变换点乘叉乘基变换特征值和特征向量抽象向量空间 序言 欢迎阅读这篇关于线性代数的文章。在这里,我们将从一个全新的角度去探索线…...
火电厂电气部分设计
摘要 本文首先根据任务书上所给系统与线路及所有负荷的参数,分析负荷发展趋势。从负荷增长方面阐明了建站的必要性,然后通过对拟建变电站的概括以及出线方向来考虑,并通过对负荷资料的分析,安全,经济及可靠性方面考虑…...
界面组件DevExpress Reporting v23.1 - Web报表设计器功能升级
DevExpress Reporting是.NET Framework下功能完善的报表平台,它附带了易于使用的Visual Studio报表设计器和丰富的报表控件集,包括数据透视表、图表,因此您可以构建无与伦比、信息清晰的报表 界面组件DevExpress Reporting v23.1已经发布一段…...
小程序Canvas 2D问题解决,如安卓drawImage不执行、动态高度设置、高度1365(或4096)限制等
我的最新版小程序想在绘制时使用自定义字体,需要将旧版canvas升级到2d新版,发现了许多问题,下面记录一下并提供解决思路,仅供参考,欢迎提供新思路。 一、开发工具和安卓上drawImage不执行,绘制出来是空白&…...
人工智能对网络安全的影响越来越大
如果问当前IT行业最热门的话题是什么,很少有人会回答除了人工智能(AI)之外的任何话题。 在不到 12 个月的时间里,人工智能已经从一项只有 IT 专业人员才能理解的技术发展成为从小学生到作家、程序员和艺术家的每个人都使用的工具…...
JavaEE(SpringMVC)期末复习
文章目录 JavaEE期末复习一、单选题: JavaEE期末复习 一、单选题: 1.Spring的核⼼技术是( A )? A依赖注入 B.JdbcTmplate C.声明式事务 D.资源访问 Spring的核心技术包括依赖注入(Dependency Injection&am…...
微服务保护 Sentinel
1.初识Sentinel 文章目录 1.初识Sentinel1.1.雪崩问题及解决方案1.1.1.雪崩问题1.1.2.超时处理1.1.3.仓壁模式1.1.4.断路器1.1.5.限流1.1.6.总结 1.2.服务保护技术对比1.3.Sentinel介绍和安装1.3.1.初识Sentinel1.3.2.安装Sentinel 1.4.微服务整合Sentinel 2.流量控制2.1.簇点链…...
【无标题】文本超过一行隐藏,鼠标经过显示提示框
创建一个组件专门用来出来文字的 <template><div class"tooltip-wrap"><el-tooltipref"tlp":content"text"effect"dark":disabled"!tooltipFlag":placement"placement"popper-class"tooltip…...
成为独立开发者有多难
首先自我介绍:我是一名前端开发工程师,7年的前端开发经验。CSDN 九段刀客_js,vue,ReactNative-CSDN博客,80多万的访问量,1万多的粉丝。 相信80%的程序员的终极梦想都是成为一名独立开发者,不用找工作有自己的产品可以有睡后收入。…...
C++ 正则表达式使用
C 11 以后有了正则表达式,对于处理字符串还是很方便的.由于我也再学习.所以下面的内容有可能描述的不准确,这些都是我自己代码中使用的,或者demo测试的. 首先使用正则表达式先要添加头文件 #include <regex> 然后编写自己的正则表达式: 例如我想匹配字符串中表示数字…...
VSCode任务tasks.json中的问题匹配器problemMatcher的问题匹配模式ProblemPattern详解
☞ ░ 前往老猿Python博客 ░ https://blog.csdn.net/LaoYuanPython 一、简介 在 VS Code 中,tasks.json 文件中的 problemMatcher 字段用于定义如何解析任务输出中的问题(错误、警告等)。 problemMatcher有三种配置方式,具体可…...
CSS 实现文本框签名
<div class"textarea-prepend"><textarea rows"6" placeholder"请输入消息内容"></textarea></div>.textarea-prepend {position: relative;}.textarea-prepend textarea {width: 300px;}.textarea-prepend::before {ba…...
Spring 定时任务如何到达某一指定时间点后,触发任务机制
在Spring框架中,可以使用Spring Task来实现定时任务。以下是使用Spring Task触发定时任务的步骤: 添加依赖:首先,在你的项目中添加Spring Task的依赖。如果使用Maven管理项目,可以在pom.xml文件中添加以下依赖项&#…...
PDF Reader Pro 3.0.1.0(pdf阅读器)
PDF Reader Pro是一款功能强大的PDF阅读、注释、填写表单&签名、转换、OCR、合并拆分PDF页面、编辑PDF等软件。 它支持多种颜色的高亮、下划线,可以按需选择,没有空白处可以进行注释,这时候便签是你最佳的选择,不点开时自动隐…...
【rust:tauri-app踩坑记录】dangerousRemoteDomainIpcAccess 不适用于IP地址,临时解决方案
找到一个临时解决方案: 修改依赖包的源代码 找到 C:\Users%USER_HOME%.cargo\registry\src\index.crates.io-6f17d22bba15001f\tauri-1.4.1\src\scope\ipc.rs 修改 函数 remote_access_for 将 155 行中的 matches_domain 删除掉,去掉校验 if matches_w…...
[Docker]八.Docker 容器跨主机通讯
一.跨主机通讯原理 在主机192.168.31.140上的docker0(172.17.0.0/16)中有一个容器mycentos( 172.17.0.2/16), 在主机192.168.31.81上的docker0(172.17.0.0/16)中有一个容器mycentos( 172.17.0.2/16),然后在主机192.168.31.140上ping主机192.168.31.81,发现ping不通要实现两个主…...
面试cast:reinterpret_cast/const_cast/static_cast/dynamic_cast
目录 1. cast 2. reinterpret_cast 3. const_cast 3.1 加上const的情况 3.2 去掉const的情况 4. static_cast 4.1 基本类型之间的转换 4.2 void指针转换为任意基本类型的指针 4.3 子类和父类之间的转换 5. dynamic_cast 5.1 RTTI(Run-time Type Identification) 1.…...
致远M3 反序列化RCE漏洞复现(XVE-2023-24878)
0x01 产品简介 M3移动办公是致远互联打造的一站式智能工作平台,提供全方位的企业移动业务管理,致力于构建以人为中心的智能化移动应用场景,促进人员工作积极性和创造力,提升企业效率和效能,是为企业量身定制的移动智慧…...
Ubuntu安装CUDA驱动
Ubuntu安装CUDA驱动 前言官网安装确认安装版本安装CUDA Toolkit 前言 CUDA驱动一般指CUDA Toolkit,可通过Nvidia官网下载安装。本文介绍安装方法。 官网 CUDA Toolkit 最新版:CUDA Toolkit Downloads | NVIDIA Developer CUDA Toolkit 最新版文档&…...
YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...
智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...
【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案
目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后,迭代器会失效,因为顺序迭代器在内存中是连续存储的,元素删除后,后续元素会前移。 但一些场景中,我们又需要在执行删除操作…...
Spring AI Chat Memory 实战指南:Local 与 JDBC 存储集成
一个面向 Java 开发者的 Sring-Ai 示例工程项目,该项目是一个 Spring AI 快速入门的样例工程项目,旨在通过一些小的案例展示 Spring AI 框架的核心功能和使用方法。 项目采用模块化设计,每个模块都专注于特定的功能领域,便于学习和…...
SQL Server 触发器调用存储过程实现发送 HTTP 请求
文章目录 需求分析解决第 1 步:前置条件,启用 OLE 自动化方式 1:使用 SQL 实现启用 OLE 自动化方式 2:Sql Server 2005启动OLE自动化方式 3:Sql Server 2008启动OLE自动化第 2 步:创建存储过程第 3 步:创建触发器扩展 - 如何调试?第 1 步:登录 SQL Server 2008第 2 步…...
Docker拉取MySQL后数据库连接失败的解决方案
在使用Docker部署MySQL时,拉取并启动容器后,有时可能会遇到数据库连接失败的问题。这种问题可能由多种原因导致,包括配置错误、网络设置问题、权限问题等。本文将分析可能的原因,并提供解决方案。 一、确认MySQL容器的运行状态 …...
从物理机到云原生:全面解析计算虚拟化技术的演进与应用
前言:我的虚拟化技术探索之旅 我最早接触"虚拟机"的概念是从Java开始的——JVM(Java Virtual Machine)让"一次编写,到处运行"成为可能。这个软件层面的虚拟化让我着迷,但直到后来接触VMware和Doc…...
如何在Windows本机安装Python并确保与Python.NET兼容
✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…...
《信号与系统》第 6 章 信号与系统的时域和频域特性
目录 6.0 引言 6.1 傅里叶变换的模和相位表示 6.2 线性时不变系统频率响应的模和相位表示 6.2.1 线性与非线性相位 6.2.2 群时延 6.2.3 对数模和相位图 6.3 理想频率选择性滤波器的时域特性 6.4 非理想滤波器的时域和频域特性讨论 6.5 一阶与二阶连续时间系统 6.5.1 …...
数据结构第5章:树和二叉树完全指南(自整理详细图文笔记)
名人说:莫道桑榆晚,为霞尚满天。——刘禹锡(刘梦得,诗豪) 原创笔记:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 上一篇:《数据结构第4章 数组和广义表》…...
