工控智汇

工控智汇

去中心化计算的未来:通过 RPC 从微服务过渡到 WASM

admin 71 179

从浏览器内的角度来看,Wasm最近的开发工作,理所当然地受到了广泛好评。在上一篇文章,我们对Rust到Wasm的编译以及简单的浏览器内Wasm执行的案例做了演示。

在另外一篇文章《区块链、硬件与面向服务的架构,WASM即将迎来大爆发?》,里面有绝佳的浏览器内的WASM应用程序示例,并辅以了对WebAssembly(Wasm)的详细解释。

浏览器之外

Wasm不仅仅是浏览器的字节码。Wasm有着前所未有的强大的可移植性、高效率和灵活性。因此,我们现在可以做到,以多种不同语言编写浏览器内Wasm应用程序,发展到在所有设备上分发Wasm独立功能单元,在这一点上取得飞跃。

为什么跨越浏览器很重要?

网站越复杂,运营成本就越高。散布在分布式系统上的微服务需要尽最大可能做到简单、高效和可靠。对于Facebook、Google这种大公司来说,这些特性意味着可以节省大量能耗,进而节省成本,促成积极成果。

除了这些能轻易做到的,我们还应该积极试验,以找到方法来改善Wasm最终用户/消费者体验。eBay就是一个很好的例子。

为什么选Wasm?

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

Wasm允许在最大量的源代码语言中编写和共享每个单个功能的逻辑。Wasm符合我们熟知的最佳软件原则和惯例(DRY和KISS),并提供了必要时在所有设备之间转换可执行代码的方法。

为什么要进行远程过程调用(RemoteProcedureCall)?

从进程间通信(IPC)角度来看,最主要的抽象化是基于远程程序调用(RemoteProcedureCall)的概念,简称RPC。(Arpaci-Dusseau和Arpaci-Dusseau,2018)。

要实现这种分布式机器之间普遍存在的互操作性,需要具备允许任何应用程序(以任何语言编写)直接从任何其他分布式机器调用服务的功能,就好像它只是调用自己的本地对象一样。这正是远程过程调用(RPC)技术所实现的。

本文的目标是使用Wasm和RPC在web上执行与语言无关的通用代码。

在下一节中,会讲解如何:

编写自定义的Rust代码并编译为Wasm

设置RPC服务器

在RPC服务器上定义自定义服务

安装Wasm虚拟机(WAVM)

通过HTTPPost(即Curl,Python等)远程执行自定义WebAssembly(Wasm)代码

1.编写自定义的Rust代码并编译为Wasm

安装Rust

curl--proto'=https'--()|shsource$HOME/.cargo/env

创建新的Rust项目

cd~cargonew--libadd_numbers_via_wavmcdadd_numbers_via_wavm

编辑文件;添加lib部分,同时也添加依赖项,如下所示

[lib]name="adding_lib"path="src/"crate-type=["cdylib"][depencies]serde_json="1.0"

在命令行中添加必要的Wasm软件和配置

rustuptargetaddwasm32-wasirustupoverridesetnightly

创建一个名为~/.cargo/config的新文件。并将以下构建文本放入这个新创建的配置文件中。

[build]target="wasm32-wasi"

编写一个我们可以调用的有不同的功能的定制的Rust程序。在下面的例子中,函数“double”和“triple”会分别取一个整数并分别乘以2和3。

useserde_json::json;pubexternfnprint(answer:i32){letthe_answer=json!({"Result":answer});println!("{}",the__string());}[no_mangle]pubexternfntriple(x:i32){letz=x*3;print(z);}

可以使用以下命令编译上面的代码

cargobuild--release
2.设置RPC服务器

这里给大家推荐一个简洁的C++RPC服务器,叫做rpcsrv(链接:)。我们要使用这个C++RPC服务器来接受HTTPPOST并通过C++将它们转换为系统调用。

sudoapt-getupdatesudoapt-~gitclone

使用以下命令开启RPC服务

sudo./rpcsrvd--listen-port8080
3.在RPC服务器上定义自定义服务

在我们进一步讨论之前,我想简单地讨论一下JSON的使用。我简要地探索了一个关于单值的绝妙概念。Univalue是一个高性能的RAIIC++JSON库和通用值对象类。我之后会找时间针对这个做彻底的研究。

方便起见,我结合使用了UniValue和rapidjson。同样,我也需要更多的时间来研究,来找到数据交换和互操作性的最佳方法,我们之后再进行讨论。

下面的代码用于安装rapidjson。

cd~

在安装rapidjson之后,我修改了原始C++API文件(链接:),以便在rpcsrvcodebase中包含rapidjson功能。

include"rapidjson/"includeiostreamusingnamespacerapidjson;

在这个阶段,我们可以继续在C++代码中使用rapidjson功能。下面是一个示例,演示如何修改修改原始echo函数(链接:)

////RPC"echo"//staticUniValuemyapi_1_echo(constUniValuejreq,constUniValueparams){//Receiveandparseincomingparametersstrings=();constchar*theJson=_str();Documentd;(theJson);//AssignparameterstoC++variablesandprinttoconsoleValuetheService=d["ServiceName"];ValuetheType=d["Type"];ValuetheFunctionName=d["Execution"]["FunctionName"];ValuetheArgument=d["Execution"]["Argument"];coutl;cout"ReceivedanewrequesttoexecuteaserviceonWasmVirtualMachine"l;_str()l;coutl;cout"ServiceNameis:"()l;cout"ServiceTypeis:"()l;cout"Wasmfunctionis:"()l;cout"Wasmfunctionargument:"()l;//ConstructandexecutethecalltoWasmVirtualMachinestringcommandString="wavmrun--abi=wasi--function=";commandString+=();commandString+="";commandString+="~/add_numbers_via_wavm/target/wasm32wasi/release/adding_";commandString+="";commandString+=();cout"\n";cout"Executingcommand"l;stringtheWasmResults=execute_function_and_return_output(commandString);//Printtheresulttoconsolecout"Resultsareasfollows:"l;couttheWasmResultsl;UniValueresult(theWasmResults);cout"Finished."l;//ReturntheresultsbacktothecalleroftheRPCreturnjrpcOk(jreq,result);}
4.安装Wasm虚拟机(WAVM)

WAVM使用LLVM将WebAssembly代码编译成机器代码,其性能接近原生性能。

下面是安装WAVM的说明

sudoapt-getinstallgccsudoapt-getinstallclangwget
5.通过HTTPPost(即Curl、Python等)远程执行自定义WebAssembly(Wasm)代码

执行可以由任何能够生成HTTPPOST的机制执行。例如,从Postman这样的GUI到Linuxcurl命令,当然还有像Python和Javal这样的解释和编译代码库。

下面是在linux命令行中使用Curl的调用代码示例

Curl-传入一段有效的JSON代码

tpmccallum$curl--header"Content-Type:application/json"--requestPOST--data'{"jsonrpc":"2.0","method":"echo","params":{"ServiceName":"Doublethedigits","Type":"Execution","Execution":{"FunctionName":"double","Argument":"10"}},"id":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代码之上)。

ReceivedanewrequesttoexecuteaserviceonWasmVirtualMachine{"ServiceName":"Doublethedigits","Type":"Execution","Execution":{"FunctionName":"double","Argument":"10"}}ServiceNameis:DoublethedigitsServiceTypeis:ExecutionWasmfunctionis:doubleWasmfunctionargument:10ExecutingcommandResultsareasfollows:{"Result":20}Finished.

Python-传入一段有效的JSON代码

系统设置

sudoapt-getinstallpython-pippipinstalljson-rpcpipinstallrequests

我们将Python传入一段有效的JSON代码,描述我们需要哪种服务。在这个例子中,我们希望将数字10翻一倍,即调用“FunctionName”:“double”和“Argument”:“10”。

importrequestsimportjsonurl=""payload={"jsonrpc":"2.0","method":"echo","params":{"ServiceName":"Doublethedigits","Type":"Execution","Execution":{"FunctionName":"double","Argument":"10"}},"id":1}response=(url,json=payload).json()

现在我们可以看到,响应返回执行Wasm的结果,即“Result”:20。

printresponse{u'jsonrpc':u'2.0',u'result':u'{"Result":20},u'id':1}

我们调用另一个服务(即“FunctionName”:“triple”,“Argument”:“10”)再次尝试这个方法

url=""payload={"jsonrpc":"2.0","method":"echo","params":{"ServiceName":"Triplethedigits","Type":"Execution","Execution":{"FunctionName":"triple","Argument":"10"}},"id":1}response=(url,json=payload).json()

同样,我们可以看到这个响应是所选服务的正确结果。

printresponse{u'jsonrpc':u'2.0',u'result':u'{"Result":30}',u'id':1}
参考文献

Arpaci-Dusseau,和Arpaci-Dusseau,,2018,《操作系统:三个简单的部分》,Arpaci-DusseauBooksLLC.

Rossberg,A.,Titzer,B.,Haas,A.,Schuff,D.,Gohman,D.,Wagner,L.,Zakai,A.,Bastien,J.以及Holman,M.(2018),《使用WebAssembly加速网络发展》,ACM通讯,107-115页.

(2019),eBay上的WebAssembly:一个真实世界的案例,[在线资源]可访问:[2019年11月20日访问].