騰訊云云函數(shù)(SCF)已經(jīng)支持十多種編程語(yǔ)言和運(yùn)行時(shí)框架。騰訊云最近發(fā)布的SCF custom runtime(自定義運(yùn)行時(shí))更進(jìn)一步——SCF現(xiàn)在可以支持用任何編程語(yǔ)言編寫的函數(shù)。
本文將介紹如何在云函數(shù)SCF中運(yùn)行用Rust編寫的WebAssembly函數(shù)。
我們先介紹一些基本概念,然后回顧一個(gè)完整但簡(jiǎn)單的hello world示例,部署您的第一個(gè)WebAssembly無(wú)服務(wù)器函數(shù)。最后,我們將用一個(gè)機(jī)器學(xué)習(xí)即服務(wù)(MLaaS)示例來做一些有用的事情。該示例接受數(shù)據(jù)并以SVG格式返回?cái)M合模型和可視化。
這是本教程結(jié)束時(shí)你將創(chuàng)建的最終應(yīng)用(https://www.secondstate.io/demo/2020-tencentcloud.html)。它完全是「無(wú)服務(wù)器」的,只有使用時(shí)會(huì)產(chǎn)生成本。
HTML和JavaScript UI可以托管在任何計(jì)算機(jī)上,包括筆記本電腦上。在騰訊云Serverless上的后端函數(shù)執(zhí)行機(jī)器學(xué)習(xí)和SVG繪圖。
為什么選擇WebAssembly和Rust
傳統(tǒng)的無(wú)服務(wù)器函數(shù)基于重量級(jí)的框架。開發(fā)者必須在特定的應(yīng)用框架中編寫函數(shù),比如Node.js中的JavaScript或Python Boto。
騰訊云SCF Custom Runtime打破了這種模式,允許開發(fā)者用任何語(yǔ)言編寫無(wú)服務(wù)器函數(shù)。
為了演示這個(gè)優(yōu)勢(shì),本文提供了基于Bash腳本的函數(shù)、基于Deno的TypeScript函數(shù)和基于Rust的本機(jī)二進(jìn)制函數(shù)的示例。這使我們能夠在騰訊云上創(chuàng)建和部署基于web組件的無(wú)服務(wù)器函數(shù)。
為什么要這么做?以下是一些原因:
·WebAssembly是為性能而設(shè)計(jì)的。WebAssembly函數(shù)可以比用JavaScript或者Python快10倍。
·WebAssembly函數(shù)是可移植的。雖然可以在SCF Custom runtime上運(yùn)行本地二進(jìn)制文件,但必須將這些二進(jìn)制文件編譯到Custom runtime的確切操作系統(tǒng)環(huán)境中。目前在X86 CPU上使用的是CentOS 7.6,之后可能會(huì)更改。正如我們將要看到的,WebAssembly函數(shù)是可移植的,并且非常容易部署和管理。
·WebAssembly函數(shù)是安全的。眾所周知,即使使用Docker,本地二進(jìn)制應(yīng)用程序也可能會(huì)破壞容器。由于你的應(yīng)用程序可能依賴于許多第三方庫(kù),因此你的依賴項(xiàng)中存在危險(xiǎn)代碼的風(fēng)險(xiǎn)真實(shí)存在。WebAssembly有著基于能力的安全模型,為你的代碼提供更好的運(yùn)行時(shí)保護(hù)。
·雖然WebAssembly兼容各種編程語(yǔ)言,但Rust、AssemblyScript(TypeScript)、C/C++和Go是寫WebAssembly函數(shù)的最佳語(yǔ)言,尤其是Rust。Rust是一種流行且越來越受矚目的編程語(yǔ)言,社區(qū)非?;钴S。Rust讓我們能夠?qū)懸粋€(gè)高效但內(nèi)存安全的函數(shù)。
最后,在騰訊云上編寫和部署WebAssembly函數(shù)實(shí)際上非常簡(jiǎn)單,在一個(gè)小時(shí)內(nèi)就可以完成。
前期準(zhǔn)備
首先,注冊(cè)一個(gè)騰訊云賬戶。對(duì)大多數(shù)開發(fā)和個(gè)人項(xiàng)目來說,開發(fā)工作都可以在免費(fèi)額度內(nèi)進(jìn)行。
確保你已經(jīng)在地開發(fā)計(jì)算機(jī)或Docker容器上安裝了Rust和ssvmup工具鏈。ssvmup工具鏈將Rust程序編譯并優(yōu)化為WebAssembly字節(jié)碼。只需使用以下簡(jiǎn)單命令即可安裝。你也可以參考這個(gè)指南。
$curl--proto'=https'--tlsv1.2-sSf https://sh.rustup.rs|sh
$source$HOME/.cargo/env
......
$curl https://raw.githubusercontent.com/second-state/ssvmup/master/installer/init.sh-sSf|sh
WebAssembly函數(shù)是在Second State虛擬機(jī)SSVM里執(zhí)行的。SSVM是專為服務(wù)端的用例和應(yīng)用優(yōu)化的高性能WebAssembly虛擬機(jī)。
Hello,world
要在騰訊云上部署Rust和WebAssembly函數(shù),我們建議clone或者fork GitHub上的模板repo,并把這個(gè)模板作為你自己應(yīng)用的基礎(chǔ)。(模板鏈接:https://github.com/second-state/ssvm-tencent-starter)
在main.rs上的Rust函數(shù)是我們將部署到SCF的無(wú)服務(wù)函數(shù)。正如你能從源代碼看到的那樣,它能從STDIN讀取函數(shù)的輸入,然后用println!macro把結(jié)果發(fā)送到STDOUT。
use std::io::{self, Read};
use serde::Deserialize;
fn main() {
let mut buffer = String::new();
io::stdin().read_to_string(&mut buffer).expect("Error reading from STDIN");
let obj: FaasInput = serde_json::from_str(&buffer).unwrap();
let key1 = &(obj.key1);
let key2 = &(obj.key2);
println!("Hello! {}, {}", key1, key2);
}
#[derive(Deserialize, Debug)]
struct FaasInput {
key1: String,
key2: String
}
Rust main()函數(shù)使用serde庫(kù)來從STDIN解析一個(gè)JSON字符串。
JSON看起來像下面這樣。我們之所以用這種方式編寫函數(shù),是因?yàn)镾CF使用內(nèi)置的hello world JSON模板來測(cè)試部署好的函數(shù)。
{
"key1":"test value 1",
"key2":"test value 2"
}
函數(shù)提取key1和key2值并輸出下面的hello消息到STDOUT。
Hello!test value 1,test value 2
但是,這個(gè)函數(shù)的web請(qǐng)求是如何被轉(zhuǎn)換成STDIN的?如何將STDOUT中的函數(shù)響應(yīng)轉(zhuǎn)換為HTTP響應(yīng)?
這是通過我們模板中的SCF custom runtime基礎(chǔ)設(shè)施和引導(dǎo)(bootstrap)程序完成的。
正如你所看到的那樣,引導(dǎo)程序只是一個(gè)bash shell程序,它不斷地輪詢?cè)坪瘮?shù)SCF以查找傳入的請(qǐng)求。它將傳入的請(qǐng)求轉(zhuǎn)換為STDIN,并通過SSVM調(diào)用WebAssembly函數(shù)。然后,它接受STDOUT輸出,并將其作為函數(shù)的響應(yīng)發(fā)給SCF。
如果你使用我們的模板,就不需要修改引導(dǎo)程序。
現(xiàn)在,可以使用ssvmup將Rust函數(shù)構(gòu)建到WebAssembly字節(jié)碼中,然后將zip文件打包,從而在騰訊云SCF Custom Runtime上進(jìn)行部署。
$ssvmup build
......
$cp pkg/hello_bg.wasm cloud/
$cd cloud
$zip hello.zip*
按照這個(gè)說明和截圖來部署并測(cè)試上面hello.zip文件?,F(xiàn)在已經(jīng)成功地部署了一個(gè)WebAssembly無(wú)服務(wù)器函數(shù)!
接下來,讓我們用Rust函數(shù)創(chuàng)建一個(gè)有價(jià)值的web服務(wù)。
機(jī)器學(xué)習(xí)即服務(wù)
這個(gè)例子中,我們選擇了一個(gè)計(jì)算密集型的機(jī)器學(xué)習(xí)任務(wù)來演示Rust WebAssembly函數(shù)的性能。
無(wú)服務(wù)器函數(shù)采用以逗號(hào)分隔的數(shù)字輸入字符串,這些數(shù)字表示二維平面上的一組點(diǎn)。輸入的數(shù)據(jù)格式是x1,y1,x2,y2,...
該函數(shù)分析數(shù)據(jù)并計(jì)算兩個(gè)特征向量,指示數(shù)據(jù)中最大方差的方向。特征向量為數(shù)據(jù)科學(xué)家提供了關(guān)于驅(qū)動(dòng)數(shù)據(jù)變化的潛在因素的線索。這就是所謂的主成分分析。
我們的函數(shù)創(chuàng)建一個(gè)SVG圖,并且在這個(gè)圖上繪制輸入的數(shù)據(jù)點(diǎn)以及上面計(jì)算得到的特征向量。該函數(shù)最后以XML文本的形式返回這個(gè)SVG圖。
要開始這個(gè)例子,你可以clone或者fork這個(gè)repo(https://github.com/second-state/wasm-learning/tree/master/tencentcloud)。該項(xiàng)目在tencentcloud/ssvm/pca文件夾中?;蛘吣憧梢詮?fù)制Cargo.toml和src/*的內(nèi)容到上文的hello world模板。如果你選擇后者,確保你修改了Cargo.toml,將其指向Rust機(jī)器學(xué)習(xí)庫(kù)的正確源代碼文件夾。
本教程中不會(huì)深入探討PCA或SVG生成的Rust源代碼的細(xì)節(jié),因?yàn)樗鼈兩婕按罅康挠?jì)算代碼。
遵照與hello world示例中相同的步驟。使用ssvmup構(gòu)建一個(gè)pca.zip包,并將其部署到騰訊云SCF custom runtime上。
接下來,我們將部署好的函數(shù)與web API網(wǎng)關(guān)關(guān)聯(lián)起來,以便可以從web HTTP或HTTPS請(qǐng)求調(diào)用它。在SCF的web控制臺(tái)的觸發(fā)管理選項(xiàng)卡中執(zhí)行此操作。這里可以查看教程和截圖(https://github.com/second-state/wasm-learning/tree/master/tencentcloud/ssvm/pca#create-a-web-service)
API控制臺(tái)將HTTP請(qǐng)求轉(zhuǎn)換為無(wú)服務(wù)器函數(shù)的JSON輸入。例如,這里有一個(gè)對(duì)API網(wǎng)關(guān)URL的HTTP POST請(qǐng)求。我們將來自iris.csv文件的以逗號(hào)分隔的數(shù)據(jù)點(diǎn)放在POST主體中。
$curl-d iris.csv-X POST https://service-m9pxktbc-1302315972.hk.apigw.tencentcs.com/release/PCASVG
API網(wǎng)關(guān)將以下JSON傳到Rust函數(shù)的STDIN。POST body現(xiàn)在是JSON中的body屬性。
{
"body": "3.5,0.2,3,0.2,...",
"headerParameters": {},
"headers": {
"accept": "/",
"content-length": "11",
"content-type": "application/x-www-form-urlencoded",
"host": "service-aj0plx8u-1302315972.hk.apigw.tencentcs.com",
"user-agent": "curl/7.54.0",
"x-anonymous-consumer": "true",
"x-api-requestid": "e3123014742e7dd79f0652968bf1f62e",
"x-b3-traceid": "e3123014742e7dd79f0652968bf1f62e",
"x-qualifier": "$DEFAULT"
},
"httpMethod": "POST",
"path": "/my_hk",
"pathParameters": {},
"queryString": {},
"queryStringParameters": {},
"requestContext": {
"httpMethod": "ANY",
"identity": {},
"path": "/my_hk",
"serviceId": "service-aj0plx8u",
"sourceIp": "136.49.211.114",
"stage": "release"
}
}
Rust函數(shù)解析主體中的數(shù)據(jù),執(zhí)行PCA,并生成SVG圖形。它將SVG內(nèi)容打印到STDOUT,后者由API網(wǎng)關(guān)獲取并作為HTTP響應(yīng)發(fā)送回來。
要在AJAX請(qǐng)求中使用此API網(wǎng)關(guān)URL,還必須配置騰訊云網(wǎng)關(guān)以接受CORS web請(qǐng)求。查看指南,了解如何做到這一點(diǎn)。
下面的HTML JavaScript例子展示了如何在網(wǎng)頁(yè)中使用這個(gè)無(wú)服務(wù)器函數(shù)。
它通過ID csv_data從textarea字段獲取CSV數(shù)據(jù),向無(wú)服務(wù)器函數(shù)發(fā)出AJAX HTTP POST請(qǐng)求,然后把返回值(一個(gè)SVG圖形)放入ID為svg_img的HTML元素中。點(diǎn)擊這里查看demo(https://www.secondstate.io/demo/2020-tencentcloud.html)。
$.ajax({
method: "POST",
url: "https://service-m9pxktbc-1302315972.hk.apigw.tencentcs.com/release/PCASVG",
data: $('#csv_data').val(),
dataType: "text"
}).done(function(data) {
$('#svg_img').html(data);
})
運(yùn)行中的Web應(yīng)用
接下來
騰訊的SCF Custom runtime是一個(gè)非常強(qiáng)大的無(wú)服務(wù)運(yùn)行環(huán)境。它為你想要編寫的任何應(yīng)用程序函數(shù)提供了一個(gè)通用的Linux環(huán)境,并提供了標(biāo)準(zhǔn)的web接口來與函數(shù)的輸入和輸出進(jìn)行交互。這絕對(duì)值得一試。
正如本文所討論的,我們相信Rust和WebAssembly為無(wú)服務(wù)器函數(shù)提供了一個(gè)高性能、安全、可移植、面向未來的堆棧。Rust、WebAssembly與SCF costum runtime代表了未來!
作者簡(jiǎn)介:Michael Yuan博士是5本軟件工程書籍的作者。最新著作《Building Blockchain Apps》由Addison-Wesley于2019年12月出版。Michael還擔(dān)任Second State的CEO,Second State是一家致力于將WebAssembly和Rust技術(shù)引入云計(jì)算,AI與區(qū)塊鏈的公司。