如何通过网络远程执行WebAssembly虚拟机

508 阅读9分钟

file

作者:Timothy, 开源软件开发者,就职于 Second State

原文链接:medium.com/wasm/strivi…

编者按:本文 demo 了终端用户以及机器用户如何在只使用 HTTP 请求的情况下,通过 web 从 Wasm 函数中找到答案。 对于更喜欢冒险的读者,本文还 demo了如何在相同的基础结构上编写和部署 Wasm 可执行文件。

以下为正文:

背景

之前的文章,我们谈到,虽然 Wasm 在客户端确实很受欢迎,但 Wasm 最近也成为了服务器端技术和服务的有力竞争者。

基于这个想法,《去中心化计算的未来:通过 RPC 从微服务过渡到 WASM》一文提出:未来,分布式计算微服务将会由传统的微服务向 Wasm 基础设施过渡。 这其中的原因有很多。 其中之一就是,通过 Wasm ,在大多数源代码语言和本地硬件之间可以编写和共享单独的 discrete 函数的逻辑。

因此,不可避免地,这意味着 Wasm 将适用于大多数应用程序,即使 Wasm 在后端,也可以有效地执行一组管理良好的 discrete 函数来为每个应用程序服务。

当然,这种乌托邦式的分布式计算模式需要一些基础,我们可以在这样的基础上建立自己的定制业务和企业软件。

SSVM 概览

虽然我们的目标看似遥不可及,但实际上是可以实现的。在服务端的 WebAssembly 领域,出现了一些令人无比激动的,能够为我们“铺路”的基础设施。

Second State 最近构建了一套软件,使部署和执行服务器端的 Wasm 变得非常方便。

下面的图表显示了构成这个系统的一些组件,即:

  • SSVMRPC —— 使用 Rust 编写的远程过程调用 (RPC) 实现,可以方便地与 SecondState 的无状态(Stateless)虚拟机 SSVM 进行代码部署和代码执行交互

  • SSVMContainer ーー处在网络和 SSVM 传入请求之间的 Rust 应用程序。 此应用程序处理 Wasm 应用程序的部署并管理服务的执行(即,Wasm 应用程序内部的可调用函数)。因为 SSVM 执行无状态执行,它还管理应用程序状态。

  • SSVM ( github.com/second-stat… )ーー 高性能、硬件优化、无状态、基于堆栈的 Wasm 虚拟机。 SSVM 可以执行任意二进制文件 ,同时对于 AI区块链特定的应用也是高度优化的。

file

Second State 研发的 SSVM 有一个核心的优势,就是使用者不需要知道它的内部工作机制。事实上,要使用 SSVM 执行服务器端的 Wasm,您只需要发出一个简单的 HTTP 请求。

下文将演示“调用应用程序的函数” ,但在此之前,让我们先花几分钟的时间进行一次快速的技术深入讨论。

技术解析

本节将展示如何在 SSVM 上创建和部署自己的 Wasm 可执行文件。我们将在 Ubuntu 操作系统上使用 Rust,创建和部署 Demo。

安装Rust

sudo apt-get update
sudo apt-get -y upgrade
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env

创建新的应用程序

cd ~
cargo new --lib add
cd add

设置 Wasm 特定的系统配置

将以下内容添加到 Cargo.toml 文件

[lib]
name = "add_lib"
path = "src/lib.rs"
crate-type =["cdylib"]

编写源代码

打开新文件 src/lib.rs 并加上以下代码

#[no_mangle]
pub extern fn add_two_numbers(_x: i32, _y: i32) -> i32{
_x + _y
}

Wasm 系统配置

rustup target add wasm32-wasi
rustup override set nightly

编译到Wasm

cargo build --release --target=wasm32-wasi

上面的集合将在 target/wasm32-wasi/release/add_lib.wasm 创建新的Wasm文件。这个文件我们将部署在 SecondState 的 Wasm 基础设施 SSVM 上。

在部署这个应用程序时,我们将遵循这个特定的 HTTP POST 规范

快速了解已编译的 Wasm 文件

WAT

如果想查看新创建的 Wasm 应用程序的文本表示(称为“ WebAssembly Text format”或简称“ WAT”) ,安装非常有用的 WABT工具包就可以。

只需运行以下命令,就可以将 Wasm 转换为 Wat。

./wasm2wat ~/add/target/wasm32-wasi/release/add_lib.wasm -o ~/add/target/wasm32-wasi/release/add_lib.wat

文本展示(WAT) 将大概如下:

(module
(type (;0;) (func (param i32 i32) (result i32)))
(func $add_two_numbers (type 0) (param i32 i32) (result i32)
local.get 1
local.get 0
i32.add)
(table (;0;) 1 1 funcref)
(memory (;0;) 16)
(global (;0;) (mut i32) (i32.const 1048576))
(global (;1;) i32 (i32.const 1048576))
(global (;2;) i32 (i32.const 1048576))
(export "memory" (memory 0))
(export "__data_end" (global 1))
(export "__heap_base" (global 2))
(export "add_two_numbers" (func $add_two_numbers)))

Wasm

您将注意到原始的 add_lib.wasm 无法随意查看, 因为它是一个可执行的二进制文件。 此外,在没有任何优化的情况下,默认由 Rust 编译生成 Wasm 文件大约为1800000字节。 就Wasm而言,这是很大的。

我们将使用 xxd 命令将 Wasm 文件转换为十六进制(用于 HTTP POST 的 JSON 数据)。 但是,我建议在转换之前,缩小原来的 Wasm 文件。

xxd -p target/wasm32-wasi/release/add_lib.wasm | tr -d $'\n'

缩小 Wasm 可执行文件的一个非常简单的方法是再次使用超赞的 wabt toolkit * 。 除了在这里,我们还将以另一种方式转换回来,即,将刚刚创建的 wat 文件转换回 wasm,如下所示。

./wat2wasm ~/add/target/wasm32-wasi/release/add_lib.wat -o ~/add/target/wasm32-wasi/release/add_lib.wasm

现在可以安全地执行上面的 xxd 命令了。

这些 wabt 转换的总体结果是, Wasm 可执行文件的大小从之前的 1800000字节变为 4000字节。 新的 Wasm 可执行文件的十六进制表示形式(在 xxd 命令之后)现在看起来像这样(仅供参考)。

0061736d0100000001070160027f7f017f030201000405017001010105030100100619037f01418080c0000b7f00418080c0000b7f00418080c0000b073704066d656d6f727902000a5f5f646174615f656e6403010b5f5f686561705f6261736503020f6164645f74776f5f6e756d6265727300000a09010700200120006a0b

复制粘贴很容易了,对吗?

部署应用

Wasm 文件的这个十六进制转储需要一个小调整。 在部署应用程序之前,我们需要在 Wasm 十六进制字符串的开头添加一个 0x

下面是我们如何通过 Curl 部署上面的 Wasm 应用程序的示例。

Curl

注意我们手动添加的 0x,在字节码的开头!

curl --header "Content-Type: application/json" \
--request POST \
--data '{
"request": {
"application": {
"storage": "file_system",
"bytecode": "0x0061736d0100000001070160027f7f017f030201000405017001010105030100100619037f01418080c0000b7f00418080c0000b7f00418080c0000b073704066d656d6f727902000a5f5f646174615f656e6403010b5f5f686561705f6261736503020f6164645f74776f5f6e756d6265727300000a09010700200120006a0b","name": "Add"}}}' \
http://13.54.168.1:8080/deploy_wasm_application

Postman — GUI HTTP 客户端

file

如果使用 GUI HTTP 客户机发出 POST 请求,那么下面是您传入的等效 JSON。

同样,请注意我们在字节码开始时手动添加的 0x

{
 "request": {
  "application": {
   "storage": "file_system",
   "bytecode": "0x0061736d0100000001070160027f7f017f030201000405017001010105030100100619037f01418080c0000b7f00418080c0000b7f00418080c0000b073704066d656d6f727902000a5f5f646174615f656e6403010b5f5f686561705f6261736503020f6164645f74776f5f6e756d6265727300000a09010700200120006a0b",
   "name": "Add"
  }
 }
}

响应

{"response":{"application":{"name":"Add","uuid":"0xa9d57ac0f5046512"},"status":"success"}}

应用成功部署

当应用程序部署时,将返回一个唯一标识符,即 0xa9d57ac0f5046512 。 以后调用应用程序的函数时,需要记住/ 保存这个标识符。

以上部分是技术分析。接下来,让我们看看如何通过 HTTP 调用一个应用的函数。

调用一个应用的函数

调用应用程序的函数不止局限于用户。 这篇文章解释了如何通过 Curl 等调用 Wasm 函数,你可以更好地理解请求和响应细节。

事实上,大多数时候,这些函数都是由机器编程调用的。至少,它们将通过网页浏览器或手机应用程序构建,并通过最终用户的“点击”来执行。

现在让我们开始调用应用程序的函数。

将下面的 curl 命令复制并粘贴到终端中。也可以使用类似于 Postman 这样的图形用户界面,来执行这个 HTTP 请求。

命令行ー Curl 语法示例

不要被下面的 --data 弄得晕头转向, 它实际上是相当简单明了的。更多信息参见 HTTP POST 规范 。实际上,我们只是调用函数 add_two_numbers 将两个数字相加并传入两个数字 [“2” ,“2”],期望返回值为 “4”

curl --header "Content-Type: application/json" \
  --request POST \
  --data '{"request": {"application": {"storage": "file_system", "uuid": "0xa9d57ac0f5046512"},"function": {"name": "add_two_numbers", "arguments": ["2", "2"],"argument_types": ["i32", "i32"], "return_types": ["i32"]},"modules": ["rust"] }}' \
  http://13.54.168.1:8080/execute_wasm_function

GUI — Postman JSON 示例

{
 "request": {
  "application": {
   "storage": "file_system", 
   "uuid": "*0xa9d57ac0f5046512*"
  },
  "function": {
   "name": "*add_two_numbers*", 
   "arguments": ["*2*", "*2*"],
   "argument_types": ["i32", "i32"], 
         "return_types": ["i32"]
  },
  "modules": ["rust"] 
 }
}

响应

上述两种方法都将生成结果对象,如下所示。返回值为 "return_value":["4"],结果是正确的。

{
 "result": {
  "error_message": "",
  "gas": 0,
  "gas_used": 6,
  "return_value": [
   "4"
  ],
  "status": "Succeeded",
  "vm_snapshot": {
   "global": [
    [
     0,
     "0x0000000000100000"
    ],
    [
     1,
     "0x0000000000100000"
    ],
    [
     2,
     "0x0000000000100000"
    ]
   ]
  }
 },
 "service_name": "0xa9d57ac0f5046512_1578786333_add_two_numbers",
 "uuid": "0xa9d57ac0f5046512"
}

你可能会注意到,在返回数据中有一部分是 vm_snapshotvm_snapshot是什么呢?

虚拟机快照

虚拟机快照是由 Second State 的虚拟机(SSVM) 本身生成的数据。

最重要的是要记住 SSVM 本身是无状态的。 每次调用 SSVM 都会引起一个新的、干净的 SSVM 实例。

vm_snapshot 数据允许总体系统存储 SSVM 的最新已知状态。 通过存储这个 vm_snapshot 信息,我们可以确保 SSVM 能够从上次没完成的地方继续。 使用这种方法,可以在下一次执行期间恢复 SSVM 的最后已知状态。

我们在本文开头提到,终端用户并不需要了解系统的内部工作机制。 简单地说,如果最终用户重复调用一个函数,系统将代表它们处理所有的vm_snapshot (VM 状态)。

有状态的 Wasm 执行即服务

本文 demo 了一个简单的有状态 Wasm 执行环境。在这个环境中,无论是终端用户或机器都可以使用每个discrete Wasm 函数的逻辑进行交互。 仅通过网络就可以使用 HTTP POST 请求。

这个演示只使用了简单的应用程序函数行 add_two_numbers 添加两个数字,但当然,您可以按照需求自由编写任何逻辑。

如果您对本文中的技术有任何疑问或需要任何帮助,请 GitHubSecond State 联系。