去中心化計算的未來:通過 RPC 從微服務過渡到 WASM

Michael_Yuan發表於2019-12-23

這篇文章將展示如何使用 Wasm 和 RPC 在 web 上執行與語言無關的通用程式碼。

本文作者: Second State 的研究員、開源核心開發 Tim McCallum

從瀏覽器內的角度來看,Wasm 最近的開發工作,理所當然地受到了廣泛好評。在上一篇文章《只需5分鐘,教你如何編寫並執行一個 Rust + WebAssembly 程式》,我們對 Rust 到 Wasm 的編譯以及簡單的瀏覽器內 Wasm 執行的案例做了演示。

在另外一篇文章《區塊鏈、硬體與面向服務的架構,WASM 即將迎來大爆發?》,裡面有絕佳的瀏覽器內的 WASM 應用程式示例,並輔以了對WebAssembly(Wasm)的詳細解釋。

但正如我們前文所說,Wasm 也正在從客戶端走向服務端。

瀏覽器之外

Wasm 不僅僅是瀏覽器的位元組碼。 Wasm 有著前所未有的強大的可移植性、高效率和靈活性。因此,我們現在可以做到,以多種不同語言編寫瀏覽器內 Wasm 應用程式,發展到在所有裝置上分發 Wasm 獨立功能單元,在這一點上取得飛躍。

Wasm 執行環境可以包括最少的 shell、移動裝置、桌上型電腦和物聯網裝置。Wasm 可能會推動從微晶片乃至整個資料中心,這所有一切的發展(Webassembly.org,2019)。

file

為什麼跨越瀏覽器很重要?

當連線到現代 Web 服務時,我們並非僅僅與一臺機器進行互動,而是持續和後臺可能數千臺機器進行互動( Arpaci-Dusseau 和Arpaci-Dusseau,2018 )。

網站越複雜,運營成本就越高。散佈在分散式系統上的微服務需要盡最大可能做到簡單、高效和可靠。對於 Facebook、Google 這種大公司來說,這些特性意味著可以節省大量能耗,進而節省成本,促成積極成果。

除了這些能輕易做到的,我們還應該積極試驗,以找到方法來改善 Wasm 終端使用者/消費者體驗。 eBay 就是一個很好的例子。

利用 Wasm,eBay 最近很好地完善了其移動條形碼掃描器的實現,恰到好處地為客戶提供了最優服務( Tech.ebayinc.com,2019)。

為什麼選 Wasm?

首先我們需要了解下“抽象化”。

雖然作業系統抽象化對於構建分散式系統來說是一個糟糕的選擇,但程式語言抽象化卻更具意義。 (阿帕奇-杜索和阿帕奇-杜索,2018)。Wasm 作為從一開始就使用形式語義設計的第一種主流程式語言,進一步地提供了對現代硬體的抽象化的支援(Rossberg等,2018)。

Wasm 允許在最大量的原始碼語言中編寫和共享每個單個功能的邏輯。Wasm符合我們熟知的最佳軟體原則和慣例(DRY 和 KISS),並提供了必要時在所有裝置之間轉換可執行程式碼的方法。

為什麼要進行遠端過程呼叫(Remote Procedure Call)?

從程式間通訊(IPC)角度來看,最主要的抽象化是基於遠端程式呼叫(Remote Procedure Call)的概念,簡稱 RPC。(Arpaci-Dusseau和Arpaci-Dusseau,2018)。

要實現這種分散式機器之間普遍存在的互操作性,需要具備允許任何應用程式(以任何語言編寫)直接從任何其他分散式機器呼叫服務的功能,就好像它只是呼叫自己的本地物件一樣。 這正是遠端過程呼叫 (RPC) 技術所實現的。

本文的目標是使用 Wasm 和 RPC 在 web 上執行與語言無關的通用程式碼。

在下一節中,會講解如何:

  1. 編寫自定義的 Rust 程式碼並編譯為 Wasm
  2. 設定 RPC 伺服器
  3. 在 RPC 伺服器上定義自定義服務
  4. 安裝 Wasm 虛擬機器(WAVM)
  5. 通過 HTTP Post(即Curl,Python等)遠端執行自定義 WebAssembly(Wasm)程式碼file

1.編寫自定義的 Rust 程式碼並編譯為 Wasm

安裝 Rust

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs (https://sh.rustup.rs/) | shsource $HOME/.cargo/env複製程式碼

建立新的 Rust 專案

cd ~
cargo new --lib add_numbers_via_wavm
cd add_numbers_via_wavm複製程式碼

編輯 Cargo.toml 檔案; 新增 lib 部分 ,同時也新增依賴項,如下所示

[lib]
name = "adding_lib"
path = "src/adding.rs"
crate-type =["cdylib"][dependencies]
serde_json = "1.0"複製程式碼

在命令列中新增必要的 Wasm 軟體和配置

rustup target add wasm32-wasi
rustup override set nightly複製程式碼

建立一個名為 ~/.cargo/config 的新檔案。並將以下構建文字放入這個新建立的配置檔案中。

[build]
target = "wasm32-wasi"複製程式碼

編寫一個我們可以呼叫的有不同的功能的定製的 Rust 程式。在下面的例子中,函式“ double”和“ triple” 會分別取一個整數並分別乘以 2 和 3。

use serde_json::json;
pub extern fn print(answer: i32){
let the_answer = json!({
"Result": answer});
println!("{}", the_answer.to_string());
}
#[no_mangle]
pub extern fn double(x: i32){
let z = x * 2;
print(z);
}
#[no_mangle]
pub extern fn triple(x: i32){
let z = x * 3;
print(z);
}複製程式碼

可以使用以下命令編譯上面的程式碼

cargo build --release複製程式碼

2. 設定 RPC 伺服器

這裡給大家推薦一個簡潔的 C++ RPC 伺服器,叫做 [rpcsrv] (https://github.com/jgarzik/rpcsrv)。 我們要使用這個 C++ RPC 伺服器來接受 HTTP POST 並通過 C++ 將它們轉換為系統呼叫。

sudo apt-get update
sudo apt-get -y upgrade
sudo apt-get install autoconf
sudo apt install libevent-2.1-6
sudo apt-get install libevent-dev
sudo apt-get install libtool
cd ~
git clone https://github.com/jgarzik/rpcsrv.git
cd ~/rpcsrv
git submodule update --init
./autogen.sh
./configure
make
sudo make install複製程式碼

使用以下命令開啟 RPC 服務

sudo ./rpcsrvd --listen-port 8080複製程式碼

3. 在 RPC 伺服器上定義自定義服務

在我們進一步討論之前,我想簡單地討論一下 JSON 的使用。 我簡要地探索了一個關於單值的絕妙概念。 Univalue 是一個高效能的 RAII C++ JSON 庫和通用值物件類。我之後會找時間針對這個做徹底的研究。

方便起見,我結合使用了 UniValue 和 rapidjson。 同樣,我也需要更多的時間來研究,來找到資料交換和互操作性的最佳方法,我們之後再進行討論。

下面的程式碼用於安裝 rapidjson

cd ~
https://github.com/Tencent/rapidjson.git
cd ~/rapidjson
git submodule update --init
mkdir build
cd ~/rapidjson/build/
cmake ..
sudo cp -rp ~/rapidjson/include/rapidjson /usr/include/複製程式碼

在安裝 rapidjson 之後,我修改了原始 C++ API 檔案,以便在 rpcsrvcodebase 中包含 rapidjson 功能。

#include "rapidjson/document.h"
#include "rapidjson/writer.h"
#include "rapidjson/stringbuffer.h"
#include <iostream>
using namespace rapidjson;複製程式碼

在這個階段,我們可以繼續在 C++ 程式碼中使用 rapidjson 功能。 下面是一個示例,演示如何修改原始 echo 函式

//
// RPC "echo"
//
static UniValue myapi_1_echo(const UniValue & jreq,
  const UniValue & params) {
  // Receive and parse incoming parameters
  string s = params.write();
  const char * theJson = s.c_str();
  Document d;
  d.Parse(theJson);
  // Assign parameters to C++ variables and print to console
  Value & theService = d["Service Name"];
  Value & theType = d["Type"];
  Value & theFunctionName = d["Execution"]["FunctionName"];
  Value & theArgument = d["Execution"]["Argument"];
  cout << endl;
  cout << "Received a new request to execute a service on Wasm Virtual
  Machine..." << endl;
  cout << s.c_str() << endl;
  cout << endl;
  cout << "Service Name is: " << theService.GetString() << endl;
  cout << "Service Type is: " << theType.GetString() << endl;
  cout << "Wasm function is: " << theFunctionName.GetString() << endl;
  cout << "Wasm function argument: " << theArgument.GetString() <<
    endl;
  
  // Construct and execute the call to Wasm Virtual Machine
  string commandString = "wavm run --abi=wasi --function=";
  commandString += theFunctionName.GetString();
  commandString += " ";
  commandString += "~/add_numbers_via_wavm/target/wasm32
  wasi / release / adding_lib.wasm ";
  commandString += " ";
  commandString += theArgument.GetString();
  cout << "\n";
  cout << "Executing command ... " << endl;
  string theWasmResults =
    execute_function_and_return_output(commandString);
  
  // Print the result to console
  cout << "Results are as follows ...: " << endl;
  cout << theWasmResults << endl;
  UniValue result(theWasmResults);
  cout << "Finished." << endl;
  // Return the results back to the caller of the RPC
  return jrpcOk(jreq, result);
}複製程式碼

4. 安裝Wasm 虛擬機器(WAVM)

WAVM 使用 LLVM將 WebAssembly 程式碼編譯成機器程式碼,其效能接近原生效能。

下面是安裝 WAVM的說明

sudo apt-get install gcc
sudo apt-get install clang 
wget https://github.com/WAVM/WAVM/releases/download/nightly%2F2019-11-04/wavm-0.0.0-prerelease-linux.deb
sudo apt install ./wavm-0.0.0-prerelease-linux.deb複製程式碼

5. 通過 HTTP Post (即 Curl、 Python 等)遠端執行自定義 WebAssembly (Wasm) 程式碼

執行可以由任何能夠生成 HTTP POST 的機制執行。 例如,從 Postman這樣的 GUI 到 Linux curl 命令,當然還有像 PythonJava 這樣的解釋和編譯程式碼庫。

下面是在 linux 命令列中使用 Curl 的呼叫程式碼示例

Curl - 傳入一段有效的JSON程式碼

tpmccallum$ curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0","method":"echo","params":{"Service Name": "Double the digits","Type": "Execution","Execution": {"FunctionName": "double","Argument": "10"}}, "id":1}' [http://123.456.78.9:8080/rpc/1](http://localhost:8080/rpc/1)複製程式碼

當檢視這個呼叫程式碼時,請記住 Rust 程式( Wasm 最早緣起於 Rust) 有兩個函式: “ double”和“ triple”。 增加的 RPC 層意味著這些原始函式現在被定義為兩個單獨的服務。

正如上面所看到的,我們不僅要指定想呼叫的服務,還要指定所需的單個引數。 當這個 POST 在 web 上執行時,RPC 伺服器直接呼叫 WAVM,然後返回一個 JSON 結果物件給呼叫程式碼。

返回有效的 JSON

{
 "jsonrpc": "2.0",
 "result": {
  "Result": 20
 },
 "id": 1
}複製程式碼

返回物件是完全可配置的,這只是一個返回計算結果的簡單示例。

RPC 伺服器輸出

RPC 伺服器輸出是可選的,這裡只是為了演示而建立的。 這裡演示了 RPC 伺服器可以來回傳遞 JSON。其他格式也有機會內建到 RPC 層(位於 Rust 和 Wasm 程式碼之上)。

Received a new request to execute a service on Wasm Virtual Machine... {"Service Name":"Double the digits","Type":"Execution","Execution":{"FunctionName":"double","Argument":"10"}}Service Name is: Double the digits
Service Type is: Execution
Wasm function is: double
Wasm function argument: 10Executing command ...Results are as follows ...:
{"Result":20}Finished.複製程式碼

Python - 傳入一段有效的JSON程式碼

系統設定

sudo apt-get install python-pip
pip install json-rpc
pip install requests複製程式碼

我們將 Python 傳入一段有效的JSON程式碼,描述我們需要哪種服務。 在這個例子中,我們希望將數字10翻一倍,即呼叫“ FunctionName” : “ double”和“ Argument” : “10”。

>>>import requests
>>>import json

>>>url = "http://123.456.78.9:8080/rpc/1"
>>>payload = {
    "jsonrpc":"2.0","method":"echo","params":{"Service Name":"Double the digits","Type": "Execution","Execution": {"FunctionName": "double","Argument": "10"}}, "id":1
}
>>>response = requests.post(url, json=payload).json()複製程式碼

現在我們可以看到,響應返回執行 Wasm 的結果,即“ Result” : 20。

>>> print response{u'jsonrpc': u'2.0', u'result': u'{"Result":20}, u'id': 1}複製程式碼

我們可以通過呼叫另一個服務(即“ FunctionName” : “ triple” ,“ Argument” : “10”)再次嘗試這個方法

>>>url = "http://123.456.78.9:8080/rpc/1"
>>>payload = {
    "jsonrpc":"2.0","method":"echo","params":{"Service Name":"Triple the digits","Type": "Execution","Execution": {"FunctionName": "triple","Argument": "10"}}, "id":1
}
>>>response = requests.post(url, json=payload).json()複製程式碼

同樣,我們可以看到這個響應是所選服務的正確結果。

>>> print response
{u'jsonrpc': u'2.0', u'result': u'{"Result":30}', u'id': 1}複製程式碼

本文通過 RPC 演示瞭如何使用 Wasm 。我是一名熱情的開源區塊鏈軟體研究人員,也是 SecondState 公司(在達拉斯、奧斯汀、北京和臺北設有辦公室)的核心開發 https://www.SecondState.io/ 。 如果你想了解更多關於 Wasm 和其他可以提升業務的技術,請通過電子郵件GitHub 與我們聯絡。

參考文獻

  1. Arpaci-Dusseau, R.H. 和 Arpaci-Dusseau, A.C.,2018, 《作業系統:三個簡單的部分》, Arpaci-Dusseau Books LLC.
  2. Rossberg, A., Titzer, B., Haas, A., Schuff, D., Gohman, D., Wagner, L., Zakai, A., Bastien, J. 以及 Holman, M. (2018), 《使用 WebAssembly 加速網路發展》, ACM通訊,107-115頁.
  3. Tech.ebayinc.com. (2019), eBay 上的 WebAssembly : 一個真實世界的案例, [線上資源] 可訪問: https://tech.ebayinc.com/engineering/webassembly-at-ebay-a-real-world-use-case/ [2019年11月20日訪問].


相關文章