做者: Michael Yuan,WasmEdge Maintainer前端
在上一篇文章《目前大火的 Jamstack 究竟是什麼?》一文中,咱們介紹了 Jamstack 的基本概念,如今就讓咱們來看一下若是使用 Rust 和 WebAssembly 構建高性能的 Jamtack 應用。git
Vercel 是開發和託管 Jamstack 應用程序的領先平臺。與傳統 Web 應用程序在 runtime 從服務器動態生成 UI 不一樣,Jamstack 應用程序由靜態 UI( HTML 和 JavaScript )和一組經過 JavaScript 支持動態 UI 元素的 serverless 函數組成。github
Jamstack 的方式有不少好處。 這其中最重要的好處之一是其強大的性能。因爲 UI 再也不從中心服務器的 runtime 生成,所以服務器上的負載要少得多,咱們能夠經過邊緣網絡(例如 CDN)部署 UI。web
可是,邊緣 CDN 只解決了分發靜態 UI 文件的問題。後端的 serverless 函數可能仍然很慢。事實上,目前流行的 serverless 平臺存在衆所周知的性能問題,例如冷啓動緩慢,對於交互式應用程序尤爲如此。在這方面, WebAssembly 大有可爲。docker
使用 WasmEdge,一個 CNCF 託管的雲原生的 WebAssembly runtime , 開發者能夠編寫高性能 serverless 函數,部署在公共雲或邊緣計算節點上。本文中,咱們將探索如何使用 Rust 編寫的 WasmEdge 函數來驅動 Vercel 應用程序後端。後端
爲何在 Vercel Serverless 使用 WebAssembly ?
Vercel 平臺已經有了很是易於使用的 serverless框架 ,能夠部署 Vercel 中託管的函數。正如上面討論的,使用 WebAssembly 和 WasmEdge 是爲了進一步提升性能。用 C/C++、Rust 和 Swift 寫的高性能函數能夠輕鬆編譯成 WebAssembly。這些 WebAssembly 函數比 serverless 函數中經常使用的 JavaScript 或 Python 快得多。api
那麼問題來了,若是原始性能是惟一的目標,爲何不直接將這些函數編譯爲機器本地可執行文件呢?這是由於 WebAssembly 「容器」仍然提供許多有價值的服務。數組
首先,WebAssembly 在 runtime 層級隔離了函數。代碼中的錯誤或內存安全問題不會傳播到 WebAssembly runtime以外。隨着軟件供應鏈日漸變得越發複雜,將代碼在容器中運行,以防止別人未經受權經過依賴庫訪問您的數據,這點很是重要。安全
其次,WebAssembly 字節碼是可移植的。開發者只需構建一次,無需擔憂將來底層 Vercel serverless 容器(操做系統和硬件)的改變或更新。它還容許開發者在類似的託管環境中重複使用相同的 WebAssembly 函數,如騰訊 Serverless Functions 的公有云中,或者在像 YoMo 這樣的數據流框架中。服務器
最後, WasmEdge Tensorflow API 提供了最符合 Rust 規範的、執行 Tensorflow 模型的方式。WasmEdge 安裝了 Tensorflow 依賴庫的正確組合,併爲開發者提供了統一的 API。
概念和解釋說了不少,趁熱打鐵,讓咱們看看示例應用程序!
準備工做
因爲咱們的 demo WebAssembly 函數是用 Rust 編寫的,所以您須要安裝有 Rust 編譯器。確保按以下方式安裝 wasm32-wasi
編譯器目標,以生成 WebAssembly 字節碼。
$ rustup target add wasm32-wasi
Demo 應用程序的前端是用 Next.js 編寫的,並部署在 Vercel 上。咱們假設您已經具有使用 Vercel 的基本知識。
示例 1: 圖片處理
咱們的第一個 demo 應用程序是讓用戶上傳圖片,而後調用 serverless 函數將其變成黑白圖片。在開始以前,你能夠試一下這個部署在 Vercel 上的 demo。
首先 fork demo 應用程序的 GitHub repo 。在 Vercel 上部署應用程序,只需從 Vercel for GitHub 頁面點GitHub repo 導入。
此 GitHub repo 的內容是一個 Vercel 平臺的標準 Next.js 應用程序。 其後端 serverless 函數在 api/functions/image_grayscale
文件夾中。src/main.rs
文件包含 Rust 程序的源代碼。 Rust 程序從 STDIN
讀取圖片數據,而後將黑白圖片輸出到 STDOUT
。
use hex; use std::io::{self, Read}; use image::{ImageOutputFormat, ImageFormat}; fn main() { let mut buf = Vec::new(); io::stdin().read_to_end(&mut buf).unwrap(); let image_format_detected: ImageFormat = image::guess_format(&buf).unwrap(); let img = image::load_from_memory(&buf).unwrap(); let filtered = img.grayscale(); let mut buf = vec![]; match image_format_detected { ImageFormat::Gif => { filtered.write_to(&mut buf, ImageOutputFormat::Gif).unwrap(); }, _ => { filtered.write_to(&mut buf, ImageOutputFormat::Png).unwrap(); }, }; io::stdout().write_all(&buf).unwrap(); io::stdout().flush().unwrap(); }
使用 Rust 的 cargo
工具構建 Rust 程序爲 WebAssembly 字節碼或者原生代碼。
$ cd api/functions/image-grayscale/ $ cargo build --release --target wasm32-wasi
將 build artifacts 複製到 api
文件夾。
$ cp target/wasm32-wasi/release/grayscale.wasm ../../
Vercel 在設置 serverless 環境時會運行
api/pre.sh
。這時會安裝 WasmEdge runtime,而後將 WebAssembly 字節碼程序編譯爲一個本地的so
庫,從而更快地執行。
api/hello.js
文件符合 Vercel 的 serverless 規範。 它加載 WasmEdge runtime,在 WasmEdge 中啓動已編譯的 WebAssembly 程序,並經過 STDIN 傳遞上傳的圖像數據。注意這裏 api/hello.js
運行由 api/pre.sh
生成的已編譯的 grayscale.so
文件從而獲得更好的性能。
const fs = require('fs'); const { spawn } = require('child_process'); const path = require('path'); module.exports = (req, res) => { const wasmedge = spawn( path.join(__dirname, 'WasmEdge-0.8.1-Linux/bin/wasmedge'), [path.join(__dirname, 'grayscale.so')]); let d = []; wasmedge.stdout.on('data', (data) => { d.push(data); }); wasmedge.on('close', (code) => { let buf = Buffer.concat(d); res.setHeader('Content-Type', req.headers['image-type']); res.send(buf); }); wasmedge.stdin.write(req.body); wasmedge.stdin.end(''); }
這樣就完成了。 接下來將 repo 部署到 Vercel ,就能夠獲得一個 Jamstack 應用程序。該應用程序具備高性能的基於 Rust 和 WebAssembly 的 serverless 後端。
示例 2: AI 推理
第二個 demo 應用程序是讓用戶上傳圖像,而後調用 serverless 函數來識別圖片中的主要物體。
它與上一個示例在同一個 GitHub repo ,可是在 tensorflow
分支。 注意:將此 GitHub repo 導入到 Vercel 網站上時,Vercel 將爲每一個分支建立一個預覽 URL。tensorflow
分支將會有本身的部署 URL 。
用於圖像分類的後端 serverless 函數位於 tensorflow
分支中的 api/functions/image-classification
文件夾中。 src/main.rs
文件包含 Rust 程序的源代碼。Rust 程序從 STDIN
讀取圖像數據,而後將文本輸出輸出到 STDOUT
。 它用 WasmEdge Tensorflow API 來運行 AI 推理。
pub fn main() { // Step 1: Load the TFLite model let model_data: &[u8] = include_bytes!("models/mobilenet_v1_1.0_224/mobilenet_v1_1.0_224_quant.tflite"); let labels = include_str!("models/mobilenet_v1_1.0_224/labels_mobilenet_quant_v1_224.txt"); // Step 2: Read image from STDIN let mut buf = Vec::new(); io::stdin().read_to_end(&mut buf).unwrap(); // Step 3: Resize the input image for the tensorflow model let flat_img = wasmedge_tensorflow_interface::load_jpg_image_to_rgb8(&buf, 224, 224); // Step 4: AI inference let mut session = wasmedge_tensorflow_interface::Session::new(&model_data, wasmedge_tensorflow_interface::ModelType::TensorFlowLite); session.add_input("input", &flat_img, &[1, 224, 224, 3]) .run(); let res_vec: Vec<u8> = session.get_output("MobilenetV1/Predictions/Reshape_1"); // Step 5: Find the food label that responds to the highest probability in res_vec // ... ... let mut label_lines = labels.lines(); for _i in 0..max_index { label_lines.next(); } // Step 6: Generate the output text let class_name = label_lines.next().unwrap().to_string(); if max_value > 50 { println!("It {} a <a href='https://www.google.com/search?q={}'>{}</a> in the picture", confidence.to_string(), class_name, class_name); } else { println!("It does not appears to be any food item in the picture."); } }
使用 cargo
工具將 Rust 程序構建爲 WebAssembly 字節碼或原生代碼。
$ cd api/functions/image-grayscale/ $ cargo build --release --target wasm32-wasi
將 build artifacts 複製到 api
文件夾
$ cp target/wasm32-wasi/release/classify.wasm ../../
一樣,api/pre.sh
腳本在此應用程序中安裝 WasmEdge runtime 及其 Tensorflow 依賴項。 它還在部署時將 classify.wasm
字節碼程序編譯爲 classify.so
原生共享庫。
api/hello.js
文件符合 Vercel serverless 規範。 它加載 WasmEdge runtime,在 WasmEdge 中啓動已編譯的 WebAssembly 程序,並經過 STDIN
傳遞上傳的圖像數據。注意 api/hello.js
運行由 api/pre.sh
產生的編譯過的 classify.so
文件,以達到更好的性能。
const fs = require('fs'); const { spawn } = require('child_process'); const path = require('path'); module.exports = (req, res) => { const wasmedge = spawn( path.join(__dirname, 'wasmedge-tensorflow-lite'), [path.join(__dirname, 'classify.so')], {env: {'LD_LIBRARY_PATH': __dirname}} ); let d = []; wasmedge.stdout.on('data', (data) => { d.push(data); }); wasmedge.on('close', (code) => { res.setHeader('Content-Type', `text/plain`); res.send(d.join('')); }); wasmedge.stdin.write(req.body); wasmedge.stdin.end(''); }
如今能夠將 forked 的repo 部署到 vercel, 就會獲得一個識別物體的 Jamstack 應用。
只需更改模板裏的 Rust 函數,你就能夠部署本身的高性能 Jamstack 應用了!
展望
從 Vercel 當前的 serverless 容器運行 WasmEdge 是一種向 Vercel 應用程序添加高性能函數的簡單方法。
若是你用 WasmEdge 開發了有趣的 Vercel 函數或者應用,能夠添加微信 h0923xw,就能夠領取一份小禮品。
展望將來,更好的方法是將 WasmEdge 自己做爲容器使用,而不是今天這樣用 Docker 和 Node.js 來啓動 WasmEdge。 這樣,咱們能夠以更高效率運行 serverless 函數。 WasmEdge 已經與 Docker 工具兼容。 若是有興趣加入 WasmEdge 和 CNCF 進行這項激動人心的工做,歡迎加入咱們的 channel。