22 年我在找一个开源的可以直接部署的 Serverless(FaaS) 方案。短时间尝试了 OpenFaaS 和 Knative,但这两个分别都有某些地方并不符合当时的需求

接着发现了 WasmEdge,这是一个利用 wasm/wasi 的运行时,支持被嵌入式执行

当我看到它第一眼,加上原本我对 WebAssembly 的理解,我直接在站起来单方面宣布:

WebAssembly is FUTURE

WebAssembly?

WebAssembly 它是一种低级的类汇编语言,具有紧凑的二进制格式,在 wasm 虚拟机以接近原生的性能运行。原本是设计给在浏览器上运行,利用各种原生语言统一接口,让高密集和其他领域原生积累可以在浏览器上运行

关联阅读:

WebAssembly 的设计看起来并不止是浏览器,明显很适合运行无状态,无中心的云函数,非常适合「用完就丢」的一次性场景

而当时的云函数因为独特的场景需求,大部分 Serverless 平台必定优先支持 NodeJSGolang,其他语言就得看情况

但如果是 WebAssembly,情况就不是这么个情况了

首先它可以被集成运行,wasi 提供了让 wasm 操作宿主的可能性,最直觉的例子就是网络调用

接着,WebAssembly 本质是为了让原本各有所长的原生语言生态可以低成本在浏览器设计的沙箱中运行,这意味着只要可以静态编译成 WebAssembly 的语言都可以加入到这个行列

WasmEdge?

WasmEdge 运行时为其包含的 WebAssembly 字节码程序提供了良好定义的执行沙盒环境。该运行时提供了对操作系统资源(例如文件系统、套接字、环境变量、进程)和内存空间的隔离和保护

WebAssembly 领域,rust 是目前最佳选择(私心:还有一个MoonBit),而「Serverless 第一公民」的 NodeJS 并不在这个列表中

但是 WasmEdge 提供了 QuickJS,得以继续「继续Serverless」

所以,可以试试玩玩:采用 NodeJS 容器和 wasm runtime 分别 hello world

Walkthrough

因为 WasmEdge 给 QuickJS 提供的插件并没有完全覆盖 NodeJS 提供的所有类库,但是 http 还是可以使用。(毕竟我试过目前最不复杂的 koa 都寄了)

这次同样用 ssr 分别做一次例子,但这次用 React 18(因为某些原因,我对 SolidJS 失去了耐心)

Standard Docker Image

随便用 vite 开一个项目,然后基础镜像用 node 20,打包运行。好像已经没什么好讲的了

唯一的区别就是,在一般情况下,在 demo 环节都会使用 Express 做服务端程序,但是这里问题很大,只能改成 NodeJS 提供的最 low level 的 http

createServer((req, res) => {
  res.setHeader('Content-type', 'text/html');

  res.on('error', (e) => {
    console.log('res error', e)
  })

  const html = renderToString(
    <DataProvider data={data}>
      <App assets={assets} />
    </DataProvider>
  )

  res.end(html)
}).listen(4396, () => {
  console.log('listen 4396...')
})

Runtime - WasmEdge

由于运行环境是 QuickJS

本质上其运行的方式就是把 QuickJS打包成 wasm,接着用这个 wasm 「解释」JavaScript

在之前有一篇文章已经提到过 QuickJS 的一些注意点,这次也不例外,包括 noExternal

关联阅读:

所以这一次也必须把要服务端渲染一开始要在服务端运行的 js 打成单文件,然后直接交给 WasmEdge QuickJS Runtime 执行

并且在 Docker Image 环节也有所不同 —— 其他打法是依赖一个镜像打包,本质还是运行在某个系统之上,而 WebAssembly 是依托于 contained shim 运行的某一个 runtime (WasmEdge, Wasmtime, Wasmer)

FROM buildbase AS build
COPY server.js .
RUN wget https://github.com/second-state/wasmedge-quickjs/releases/download/v0.5.0-alpha/wasmedge_quickjs.wasm
RUN wget https://github.com/second-state/wasmedge-quickjs/releases/download/v0.5.0-alpha/modules.zip
RUN unzip modules.zip
# This line builds the AOT Wasm binary
RUN /root/.wasmedge/bin/wasmedgec wasmedge_quickjs.wasm wasmedge_quickjs.wasm

# 从这里开始「打包」wasm image,无论什么语言编译的 wasm 都差不多
FROM scratch
ENTRYPOINT [ "wasmedge_quickjs.wasm", "server.js" ]
COPY --link --from=build /src/wasmedge_quickjs.wasm /wasmedge_quickjs.wasm
COPY --link --from=build /src/server.js /server.js

Above All

首先最直观的感觉就是大小,Node 这个镜像大小已经不想讲了,而 wasm 版本,在 docker desktop 查看,其运行的时候「镜像」大小在 10MB 左右,并且其运行占用花费地非常少

但是有一个明显的问题,我目前还没有排查是 docker desktop 的性能还是这本就是 wasi 的问题 —— 它运行效率很慢。使用传统 linux 镜像的版本运行时非常迅速,像直接本地运行,stream 模式也非常快,而 wasi 版本采用 stream 模式时都会卡加载,并且 css 文件永远没能正常下载

但我个人意见,其意义非常大 —— 它可以以非常小成本,利用 WebAssembly 统一运行的特性,运行某些程序在边缘网络(Edge)上。例如 ffmpeg 可以被编成 wasm,那么就意味着可以利用边缘资源甚至几个节点的残余资源用来解码或者转码。而更大的意义可能是可以用来运行专项的小模型

就像 WasmEdge 其实举最多的例子并不是什么小服务 FaaS,而是模型推理,并官方提供 tensorflow 等推理框架插件集成

这波 AI 的推背感感觉是来真的。我认为接下来 Multi-Agent 的形态将会跟微服务或者「核心云 - 边缘网」差不多的结构:使用参数较少,在 CPU 运行优秀的,针对「特定领域特调」的大模型采用 wasi 运行在边缘计算用于降低延迟提供服务(其实就像现在会运行在边缘的 AIoT 一样),然后采用一个或几个通用大模型用于决策或者强化学习

事实上 python 的执行相对于某几个能贴地飞行的语言来说其能力胯成马,但如果是这样的决策组合可能会是未来的形态,因为相对的,成本会低一些