1 WebAssembly 介绍
WebAssembly(Wasm)是一种通用字节码技术,它可以将其他编程语言(如 Go、Rust、C/C++ 等)的程序代码编译为可在浏览器环境直接执行的字节码程序。
WebAssembly 的初衷之一是解决 JavaScript 的性能问题,让 Web 应用程序能够达到与本地原生应用程序类似的性能。作为底层 VM 的通用、开放、高效的抽象,许多编程语言,例如C、C++ 和 Rust,都可以将现有应用程序编译成 Wasm 的目标代码,以便它们在浏览器中运行。这将应用程序开发技术与运行时技术解耦,并大大提高了代码的可重用性。
2019 年 3 月,Mozilla 推出了 WebAssembly 系统接口(Wasi),以标准化 WebAssembly 应用程序与系统资源之间的交互抽象,例如文件系统访问、内存管理和网络连接,该接口类似于 POSIX 等标准 API。Wasi 规范的出现极大地扩展了 WebAssembly 的应用场景,使得 Wasm 不仅限于在浏览器中运行,而且可以在服务器端得到应用。同时,平台开发者可以针对特定的操作系统和运行环境提供 Wasi 接口的不同实现,允许跨平台的 WebAssembly 应用程序运行在不同的设备和操作系统上。
2 WebAssembly 会取代容器吗?
Docker 的创始人 Solomon Hykes 是这样评价 WASI 的:
如果 WASM+WASI 在 2008 年就存在,我们就不需要创建 Docker 了。这就是它的重要性。服务器上的 WebAssembly 是计算的未来。
Solomon Hykes 后续还发布了一条推文,表示 WebAssembly 将与容器一起工作,而不是取代它们。WebAssembly 可以成为一种容器类型,类似于 Linux 容器或 Windows 容器。它将成为标准的跨平台应用程序分发和运行时环境。
3 WebAssembly 的优势
WebAssembly 相较于传统的容器有着许多显著的优势:
- 体积更小:WebAssembly 应用程序比容器小,以下是两个简单的用于输出文档的应用程序,都是使用标准工具构建的,从下图可以看出,Wasm 应用程序比容器化应用程序小了近 10 倍。
- 速度更快:WebAssembly 应用程序的启动速度可以比容器快 1000 倍,你可以在不到一毫秒的时间内执行应用程序的第一条指令,有 时甚至可以达到微秒级。这将使构建可伸缩的应用程序变得更加容易,当请求达到峰值时,应用程序可以快速伸缩,当请求下降到零且没有流量时,应用程序不会浪费 CPU 或内存。
- 更加安全:WebAssembly 在沙箱环境中运行,具有强大的安全性。它提供了一系列安全特性,如内存隔离、类型检查和资源限制,以防止恶意代码执行和访问敏感信息。
- 可移植性更好:容器的架构限制了它们的可移植性。例如,针对 linux/amd64 构建的容器无法在 linux/arm64 上运行,也无法在 windows/amd64 或 windows/arm64 上运行。这意味着组织需要为同一个应用程序创建和维护多个镜像,以适应不同的操作系统和 CPU 架构。而 WebAssembly 通过创建一个在可以任何地方运行的单一 Wasm 模块来解决这个问题。只需构建一次 wasm32/wasi 的应用程序,任何主机上的 Wasm 运行时都可以执行它。这意味着 WebAssembly 实现了一次构建,到处运行的承诺,不再需要为不同的操作系统和 CPU 架构构建和维护多个镜像。
关于 WebAssembly 和容器详细的对比,可以查看这个表格: WebAssembly vs Linux Container [1]。
4 使用 Rust 开发 Wasm 应用
是否可以将应用程序编译为 Wasm 在很大程度上取决于所使用的编程语言。Rust、C、C++ 等语言对 Wasm 有很好的支持。从 Go 1.21 版本开始,Go 官方也初步支持了 Wasi,之前需要使用第三方工具如 tinygo 进行编译。由于 Rust 对 Wasm 的一流支 持以及无需 GC、零运行时开销的特点,使其成为了开发 Wasm 应用的理想选择。因此,本文选用 Rust 来开发 Wasm 应用程序。
4.1 安装 Rust
执行以下命令安装 rustup,并通过 rustup 安装 Rust 的最新稳定版本,rustup 是用于管理 Rust 版本和工具链的命令行工具。
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
4.2 为 Rust 添加 wasm32-wasi 编译目标
前面提到过,Wasi(WebAssembly System Interface)是用于 WebAssembly 的系统级接口,旨在实现 WebAssembly 在不同环境中与宿主系统交互。它提供标准化的方式,使得 WebAssembly 可以进行文件 I/O、网络操作和系统调用等系统级功能访问。
rustc 本身是一个跨平台的编译器,其编译的目标有很多,具体可以通过 rustup target list
命令来查看。wasm32-wasi 是 Rust 的编译目标之一,用于将 Rust 代码编译为符合 Wasi 标准的 Wasm 模块。通过将 Rust 代码编译为 wasm32-wasi 目标,可以将 Rust 的功能和安全性引入到 WebAssembly 环境中,同时利用 wasm32-wasi 提供的标准化系统接口实现与宿主系统的交互。
执行以下命令,为 Rust 编译器添加 wasm32-wasi 目标。
rustup target add wasm32-wasi
4.3 编写 Rust 程序
首先执行以下命令构建一个新的 Rust 项目。
cargo new http-server
编辑 Cargo.toml 添加如下依赖。这里我们使用 wrap_wasi 来开发一个简单的 HTTP Server, warp_wasi 构建在 Warp 框架之上,Warp 是一个轻量级的 Web 服务器框架,用于构建高性能的异步 Web 应用程序。
原生的 Warp 框架编写的代码无法直接编译成 Wasm 模块。因此我们可以使用 warp_wasi,通过它我们可以在 Rust 中利用 Wasi 接口来开发 Web 应用程序。
[dependencies]
tokio_wasi = { version = "1", features = ["rt", "macros", "net", "time", "io-util"]}
warp_wasi = "0.3"
编写一个简单的 HTTP Server,在 8080 端口暴露服务,当接收到请求时返回 Hello, World!。
use warp::Filter;
#[tokio::main(flavor = "current_thread")]
async fn main() {
let hello = warp::get()
.and(warp::path::end())
.map(|| "Hello, World!");
warp::serve(hello).run(([0, 0, 0, 0], 8080)).await;
}
执行以下命令,将程序编译为 Wasm 模块。
cargo build --target wasm32-wasi --release
4.4 安装 WasmEdge
编译完成的 Wasm 模块需要使用相应的 Wasm 运行时来运行。常见的 Wasm 运行时包括 WasmEdge、Wasmtime 和 Wasmer 等。
在这里,我们选择使用 WasmEdge,它是一个轻量、高性能且可扩展的 WebAssembly Runtime。执行以下命令安装 WasmEdge。
curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash
运行以下命令以使已安装的二进制文件在当前会话中可用。
source $HOME/.wasmedge/env
4.5 运行 Wasm 模块
使用 wasmedge 来运行前面编译好的 Wasm 模块。
wasmedge target/wasm32-wasi/release/http-server.wasm
在本地通过 curl 命令访问该服务。
curl http://localhost:8080
Hello, World!