跳到主要内容
Deno 2.4 现已发布,带来了 deno bundle、字节/文本导入、稳定的 OTel 等更多功能
了解更多
Deno helps Windmill.dev serve their enterprise clients.

Deno 中的不可变脚本如何助力 Windmill.dev (YC S22) 构建生产级运维

  • Ruben Fiszel

本篇客座博客由 Ruben Fiszel 撰写,他是 Windmill.dev 的创始人兼首席执行官。

Windmill.dev 是一个开源开发者平台,公司可以在其中从脚本构建内部工作流和 UI。超过 300 家公司,包括 PhotoRoom 等企业客户,将 Windmill 作为其生产基础设施的核心部分,用于各种操作,例如 ETL 管道、整合内部和外部 API 等。

由于公司依赖 Windmill 来处理由脚本组成的任务关键型工作流,因此我们的技术必须具备高性能、高安全性和灵活性

  • 脚本的最小冷启动时间
  • 安全地运行不受信任的任意代码
  • 共享和组合脚本的简单方法

Windmill 可以运行 Python、Typescript、Bash 和 Go 代码。对于 TypeScript,我们选择 Deno 是因为它具备高性能、高安全性和创建独立脚本的能力

脚本作为一级原生类型

脚本是构建工作流和 UI 的最小“单元”,因为它们具有灵活性和可控性。Windmill 工作流由各种脚本组成。

A sample Windmill workflow composed of immutable Deno scripts

一个简单的单分支流程示例:当 HackerNews 消息包含提及内容时发布到 Slack,并进行情感分析。

上述示例表明,工作流不仅由现成脚本组成,还使用不同语言的脚本,这些脚本具有各种输入和资源。

由于脚本用于各种工作流,因此它们的共享性和一致性非常重要。此外,Windmill 是社区驱动的:脚本在 Hub 上共享,所有经批准的脚本都集成到产品中。

基于这些要求,脚本必须可靠、安全且高性能。只有 Deno 能将这一切集于一身。

Deno 使脚本不可变

Windmill 要求每个脚本公开一个主函数,并在同一文件中声明其依赖项。主函数的参数被解析,以推断触发此类脚本的有效负载对应的 JSON Schema。这种模型与 Deno 完美契合。

Deno 的依赖项要求可以在使用它们的同一文件中声明。此外,导入语句可以指定所使用的精确版本,即使是 npm 导入也不例外

import mysql from "npm:mysql2@^2.3.3/promise";
import * as wmill from "https://deno.land/x/windmill@v1.101.1/mod.ts";

当指定依赖项版本时,我们可以保证脚本执行的可重现性,无论环境如何。最重要的是,共享 Deno 脚本就像共享一个文件一样简单——无需单独的依赖项清单,例如 `package.json`。

脚本创建或更新后,其新版本将与一个不可变且永久的哈希值相关联,然后连同相关元数据一起存储在 Postgres 中。

借助 Deno,Windmill 上的脚本是不可变的,并且是一级原生类型——一个可共享、可组合的构建块,供整个社区使用。

使用 Deno 安全地运行任意代码

Windmill 在多租户环境中运行用户生成的代码,因此必须隔离每个脚本的执行(对于自托管安装,为方便起见可以禁用这些安全措施)。Deno 的选择性权限模型使我们能够精细地控制每次执行的访问权限。

对于 Windmill 中其他语言的脚本,沙盒机制通过 NSJail 实现,这会带来很大的性能开销,并且使用和配置复杂。Deno,由于其默认安全的特性,在多租户环境中具有性能优势

Deno 的最小冷启动

借助 Deno,我们实现了 15 毫秒的冷启动时间,这得益于它与 V8 的集成以及依赖项的不可变缓存

缓存的依赖项还会通过 S3 支持的同步系统在集群的工作节点之间传播,因此任何引用缓存依赖项的 Deno 脚本都不需要下载它,从而获得性能优势。

执行 Deno 脚本的冷启动时间约为 15 毫秒,这意味着大多数轻量级脚本的端到端运行时间为 30 毫秒。

未来展望

我们很高兴在 Windmill 的生产环境中使用了 Deno——它简化了开发,增加了一层安全性,并确保了我们企业客户的最佳性能。

未来,我们计划通过更深入地集成 Deno 并在进程内运行它,以达到无服务器 / AWS Lambda 的性能。

不要错过任何更新 — 在 Twitter 上关注我们