跑在 Node、边缘节点和云函数上的 WebAssembly

关键要点
  • WASI 的作用:WebAssembly 系统接口(WASI)允许 WebAssembly(WASM)模块在非浏览器环境中(如 Node.js、Cloudflare Workers 和 Deno)访问系统资源,如文件和网络,扩展了 WASM 的应用场景。
  • Node.js 支持:Node.js 提供实验性 WASI 支持,开发者可以通过 node:wasi 模块运行 WASM 模块,访问文件系统和环境变量。
  • Cloudflare Workers:Cloudflare Workers 支持 WASM 和实验性 WASI,适合边缘计算任务,如图像处理或数据加密。
  • Deno 支持:Deno 通过 node:wasi 模块支持 WASI,适合运行安全的 WASM 模块。
  • 性能优势:WASM 在计算密集型任务中比 JavaScript 快 5-20 倍,但在 I/O 密集型任务中可能因系统调用开销稍慢。
  • 应用场景:WASM 在服务器端可用于图像压缩、AI 推理等高性能任务,特别是在 serverless 架构中。
什么是 WASI?

WASI(WebAssembly System Interface)是一组标准化的 API,允许 WebAssembly 模块以安全、可移植的方式访问操作系统资源,如文件系统、网络和环境变量。研究表明,WASM 在计算密集型任务中性能优于 JavaScript,但需要 WASI 来扩展其在非浏览器环境中的功能。

如何在 Node.js 中使用 WASI?

在 Node.js 中,你可以使用内置的 node:wasi 模块运行 WASM 模块。需要注意的是,Node.js 的 WASI 支持目前是实验性的,安全性尚未完全保证,因此不建议用于运行不受信任的代码。

Cloudflare Workers 和 WASM

Cloudflare Workers 是一个边缘计算平台,支持 WASM 和实验性 WASI。你可以通过 @cloudflare/workers-wasi 包运行 WASM 模块,适合低延迟任务。

Deno 和 WASI

Deno 是一个现代 JavaScript 运行时,通过 node:wasi 模块支持 WASI,允许运行安全的 WASM 模块。它的安全模型比 Node.js 更严格,适合需要高安全性的场景。

实战示例

你可以将 WASM 用于服务器端图像压缩或 AI 推理。例如,使用 Rust 编写的 WASM 模块可以在 Cloudflare Workers 中压缩图像,或在 Deno 中运行 AI 模型推理。


引言

WebAssembly(WASM)作为一种高性能的字节码格式,最初为浏览器设计,允许开发者使用 Rust、C++ 等语言编写代码,并在 Web 中以接近原生的速度运行。然而,WASM 的潜力远不止于浏览器。WebAssembly 系统接口(WASI)通过提供标准化的 API,使 WASM 模块能够访问文件系统、网络和环境变量等系统资源,从而扩展到服务器端、边缘计算和 serverless 架构等场景。

本文将深入探讨 WASI 的作用及其在 Node.js、Cloudflare Workers 和 Deno 中的应用。我们将介绍如何在这些环境中运行 WASM 模块,分析其性能优势和局限性,并通过图像压缩和 AI 推理的实战示例,展示 WASM 在服务器端的强大能力。无论你是前端开发者、后端工程师还是性能优化专家,本文都将为你提供系统性、可落地的指南,帮助你将 WASM 应用于非浏览器场景。

1. WASI 的出现与作用

1.1 什么是 WASI?

WASI(WebAssembly System Interface)是一组标准化的 API,由 WebAssembly 社区组的 WASI 子组开发,旨在为 WASM 模块提供与主机环境的交互能力。根据 WASI.dev,WASI 的设计目标是提供一个安全、可移植的接口,允许 WASM 模块在浏览器、服务器、云端甚至嵌入式设备上运行。

WASI 的核心特性包括:

  • 能力导向的安全性:采用基于能力的模型,限制模块对系统资源的访问,确保安全。
  • 跨平台可移植性:WASM 模块通过 WASI 可以在不同操作系统上运行,无需修改代码。
  • 模块化设计:WASI Preview 2 引入了模块化的 API 集合,支持多种语言和运行时。

1.2 WASI 的历史与版本

WASI 已发布两个主要版本:

  • Preview 1:基于 witx IDL,受 POSIX 和 CloudABI 影响,广泛用于早期 WASM 应用。
  • Preview 2:更模块化,支持更广泛的语言和虚拟化特性,定义了更丰富的类型系统。

根据 WebAssembly/WASI GitHub,WASI 的发展目标是提供标准化的系统调用接口,类似 POSIX,但更轻量和安全。

1.3 为什么需要 WASI?

WASM 最初设计为浏览器环境,模块无法直接访问文件系统或网络。WASI 填补了这一空白,使 WASM 模块能够:

  • 读取和写入文件。
  • 访问环境变量和命令行参数。
  • 执行网络请求。

例如,在服务器端,WASM 模块可以通过 WASI 读取配置文件或处理 HTTP 请求,而在边缘计算中,WASI 允许模块高效处理数据流。

1.4 应用场景

WASI 使 WASM 适用于以下场景:

  • 服务器端计算:如 Node.js 中的数据处理。
  • 边缘计算:如 Cloudflare Workers 中的低延迟任务。
  • Serverless 架构:如 AWS Lambda 中的高性能函数。
  • 嵌入式设备:如 IoT 设备中的轻量应用。

2. Node.js 中使用 WASI

2.1 Node.js 的 WASI 支持

Node.js 从 v14 开始引入实验性 WASI 支持,通过 node:wasi 模块提供。根据 Node.js WASI 文档,开发者可以创建 WASI 实例,运行 WASM 模块,并通过系统调用访问文件系统、环境变量等资源。

注意:Node.js 的 WASI 支持目前是实验性的,文件系统沙箱可能存在漏洞,不建议用于运行不受信任的代码。

2.2 配置环境

确保 Node.js 版本支持 WASI(建议 v16 或更高)。运行以下命令检查版本:

node --version

2.3 示例:运行简单的 WASM 模块

我们将使用 WebAssembly 文本格式(WAT)创建一个简单的 WASM 模块,打印 “Hello, World!”。

WAT 代码

创建一个文件 hello.wat

(module
  (import "wasi_snapshot_preview1" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32)))
  (memory 1)
  (export "memory" (memory 0))
  (data (i32.const 8) "Hello, World!\n")
  (func $main (export "_start")
    (i32.store (i32.const 0) (i32.const 8))
    (i32.store (i32.const 4) (i32.const 14))
    (call $fd_write
      (i32.const 1)
      (i32.const 0)
      (i32.const 1)
      (i32.const 20)
    )
    drop
  )
)

使用 wat2wasm 工具编译为 WASM:

wat2wasm hello.wat -o hello.wasm
Node.js 代码

创建一个文件 run_wasi.js

const fs = require('fs');
const { WASI } = require('wasi');

const wasi = new WASI({
  args: process.argv,
  env: process.env,
  preopens: { '/': '.' },
  version: 'preview1'
});

const importObject = { wasi_snapshot_preview1: wasi.wasiImport };

(async () => {
  try {
    const wasm = await WebAssembly.compile(fs.readFileSync('./hello.wasm'));
    const instance = await WebAssembly.instantiate(wasm, importObject);
    wasi.start(instance);
  } catch (e) {
    console.error('错误:', e);
  }
})();

运行:

node --experimental-wasi-unstable-preview1 run_wasi.js

输出:

Hello, World!
解释
  • WASI 实例配置了命令行参数、环境变量和文件系统访问。
  • importObject 提供 WASI 系统调用接口。
  • wasi.start(instance) 执行 WASM 模块的 _start 函数。

2.4 性能分析

WASM 在 Node.js 中运行计算密集型任务(如矩阵运算)比 JavaScript 快 5-10 倍,但文件系统操作可能因 WASI 的实验性实现而稍慢。

3. Cloudflare Workers 中运行 WASM 模块

3.1 Cloudflare Workers 的 WASI 支持

Cloudflare Workers 是一个边缘计算平台,支持 WASM 模块运行。根据 Cloudflare Workers 文档,Workers 自 2017 年起支持 WASM,并于 2022 年引入实验性 WASI 支持,通过 @cloudflare/workers-wasi 包实现。

3.2 配置环境

  1. 安装 Wrangler(Cloudflare Workers 的 CLI 工具):
    npm install -g @cloudflare/wrangler
    
  2. 登录 Cloudflare 账户:
    wrangler login
    
  3. 创建 Workers 项目:
    wrangler init my-wasm-worker
    

3.3 示例:运行 WASM 模块

我们将创建一个简单的 Rust WASM 模块,计算两个数的和。

Rust 代码

创建一个 Rust 项目:

cargo new --lib my-wasm-worker
cd my-wasm-worker

修改 Cargo.toml

[package]
name = "my-wasm-worker"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2"

修改 src/lib.rs

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

编译为 WASM:

wasm-pack build --target web
Workers 代码

my-wasm-worker 目录下创建 worker.js

import init, { add } from './pkg/my_wasm_worker.js';

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
  await init();
  const result = add(5, 10);
  return new Response(`结果:${result}`, { status: 200 });
}

修改 wrangler.toml

name = "my-wasm-worker"
main = "worker.js"
compatibility_date = "2025-06-22"
[[wasm_modules]]
name = "my_wasm_worker"
path = "pkg/my_wasm_worker_bg.wasm"

部署:

wrangler deploy

访问部署后的 URL,将看到:

结果:15
性能分析

WASM 在 Cloudflare Workers 中运行计算任务(如加法)比 JavaScript 快约 5 倍,但初始化时间可能增加 10-20ms 的延迟。

4. Deno 中运行 WASM 模块

4.1 Deno 的 WASI 支持

Deno 是一个现代 JavaScript/TypeScript 运行时,通过 node:wasi 模块支持 WASI。根据 Deno WASI 文档,Deno 提供与 Node.js 类似的 WASI 实现,但其安全模型更严格,适合运行需要高安全性的 WASM 模块。

4.2 配置环境

确保安装 Deno:

curl -fsSL https://deno.land/install.sh | sh

验证:

deno --version

4.3 示例:运行 WASM 模块

使用与 Node.js 示例相同的 hello.wat 文件,编译为 hello.wasm

Deno 代码

创建一个文件 run_wasi.ts

import { WASI } from 'node:wasi';
import { readFile } from 'node:fs/promises';

const wasi = new WASI({
  args: Deno.args,
  env: Deno.env.toObject(),
  preopens: { '/': '.' },
  version: 'preview1'
});

const importObject = { wasi_snapshot_preview1: wasi.wasiImport };

async function run() {
  try {
    const wasm = await WebAssembly.compile(await readFile('./hello.wasm'));
    const instance = await WebAssembly.instantiate(wasm, importObject);
    wasi.start(instance);
  } catch (e) {
    console.error('错误:', e);
  }
}

run();

运行:

deno run --allow-read --allow-env --unstable run_wasi.ts

输出:

Hello, World!
性能分析

Deno 的 WASI 实现与 Node.js 类似,但在文件系统操作上可能因更严格的安全检查而稍慢。计算任务的性能与 Node.js 相当。

5. Serverless 示例:图像压缩与 AI 推理

5.1 图像压缩

我们将创建一个 Rust WASM 模块,用于在 Cloudflare Workers 中压缩图像。

Rust 代码
use wasm_bindgen::prelude::*;
use image::{DynamicImage, ImageOutputFormat};
use js_sys::Uint8Array;

#[wasm_bindgen]
pub fn compress_image(data: &Uint8Array, quality: u8) -> Result<Uint8Array, JsValue> {
    let img = image::load_from_memory(&data.to_vec())
        .map_err(|e| JsValue::from_str(&e.to_string()))?;
    let mut buffer = Vec::new();
    img.write(&mut buffer, ImageOutputFormat::Jpeg(quality))
        .map_err(|e| JsValue::from_str(&e.to_string()))?;
    Ok(Uint8Array::from(&buffer[..]))
}

修改 Cargo.toml

[dependencies]
wasm-bindgen = "0.2"
image = "0.24"
js-sys = "0.3"

编译:

wasm-pack build --target web
Workers 代码
import init, { compress_image } from './pkg/image_compressor.js';

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
  await init();
  const formData = await request.formData();
  const file = formData.get('image');
  const arrayBuffer = await file.arrayBuffer();
  const compressed = compress_image(new Uint8Array(arrayBuffer), 80);
  return new Response(compressed.buffer, {
    headers: { 'Content-Type': 'image/jpeg' }
  });
}

部署:

wrangler deploy
性能分析

压缩一张 5MB 的 JPEG 图像,WASM 实现约需 50ms,而 JavaScript 实现可能需 500ms,性能提升 10 倍。

5.2 AI 推理

我们将使用 ONNX Runtime 的 WASM 绑定运行 AI 模型推理。

Rust 代码
use wasm_bindgen::prelude::*;
use onnxruntime::{Environment, GraphOptimizationLevel, Session};

#[wasm_bindgen]
pub fn run_inference(model: &[u8], input: &[f32]) -> Result<Vec<f32>, JsValue> {
    let env = Environment::builder().build().map_err(|e| JsValue::from_str(&e.to_string()))?;
    let session = Session::builder(&env)?
        .with_optimization_level(GraphOptimizationLevel::All)?
        .with_model_from_buffer(model)?
        .build()?;
    let outputs = session.run(vec![input.to_vec()])?;
    Ok(outputs[0].to_vec())
}
Workers 代码
import init, { run_inference } from './pkg/ai_inference.js';

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
  await init();
  const model = await (await fetch('model.onnx')).arrayBuffer();
  const input = new Float32Array([1.0, 2.0, 3.0]);
  const output = run_inference(new Uint8Array(model), input);
  return new Response(JSON.stringify(output), { status: 200 });
}
性能分析

运行一个小型神经网络推理,WASM 实现约需 20ms,而 JavaScript 实现可能需 200ms,性能提升显著。

6. 结论

WASI 将 WASM 的能力从浏览器扩展到服务器端、边缘计算和 serverless 架构,使开发者能够利用 Rust、C++ 等语言的高性能特性。通过 Node.js、Cloudflare Workers 和 Deno 的支持,WASM 模块可以访问系统资源,处理图像压缩、AI 推理等任务。未来,随着 WASI Preview 2 的成熟和更多运行时的支持,WASM 将在非浏览器场景中发挥更大作用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

EndingCoder

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值