背景
云原生正在成为新的基础设施
云原生技术有利于各组织在公有云、私有云和混合云等新型动态环境中,构建和运行可弹性扩展的应用。云原生向下封装资源,将复杂性下沉到基础设施层,加速了应用与基础设施资源之间的解耦,向上支撑应用,让开发者更关注业务价值,解放了开发者的生产力。业务与企业运维能够依托于云原生所提供的基础设施,实现智能升级价值,充分释放云计算红利,云原生技术正在成为全新的生产力工具。
什么是serverless
无服务器架构应用仅在需要时启动。有事件触发应用代码运行时,公共云提供商才会为这一代码分配资源。该代码执行结束后,用户便不再付费。除了成本与效率上的优势外,进一步释放了云计算的能力,将运维、安全、可用性、可伸缩性等需求下沉到基础设施实现。无服务器也能将开发人员从运维相关的琐碎日常任务中解放出来。
程序员的工作一定是越来越简化的,对于业务开发人员来说,可以专注于业务代码开发,摆脱诸如服务器置备和管理应用程序等复杂的工作。
为什么选择WebAsssembly
WebAsssembly是一种安全、可移植的低层次二进制代码。它有一种紧凑的二进制格式,能够以接近原生性能的速度运行。作为应用层级的后端语言,它具有以下优点:
WebAssembly对比docker冷启动延迟可以降低90%以上,对比于js通常具有两倍的运行速度优势。其冷启动方面的延迟是它非常显著的一个优势。与faas和serverless的理念非常贴合。从下图WebAssembly与docker的一个对比上,可以看到它在冷启动延迟和速度方面均具有独特的优势。
istio(envoy)与WebAssembly结合
dapr与WebAsssembly结合
最初了解WebAsssembly还是通过易立的这篇WebAssembly + Dapr = 下一代云原生运行时?
作为dapr的社区成员,本身对dapr也非常了解,加上公司内部也在使用dapr的某些功能。我对dapr+WebAsssembly模式的前景还是非常看好的。
proxy-wasm
简介
前面说到,istio使用WebAsssembly动态扩展其数据面功能。只要遵循其标准的WebAsssembly程序都可以在运行时被动态加载运行。那么这个标准是什么标准呢?这样就有了proxy-wasm,
适配
dapr-wasm
简介
second state 发布了一个dapr-wasm 的项目,这个项目是结合dapr和wasmedge提供的一个图片识别的例子。跟项目的简介说的一样,这是一个模板项目,用于演示如何在使用 dapr作为sidecar 运行 WebAssembly 的微服务。
我大致看了一下go的源码,虽然代码量很少,但是为了这个demo的运行,背后还是有一些工作的。首先是wasm正常运行所需要的ABI,包括图片相关的和Tensorflow相关的导入函数。这些方法是在go的WasmEdge-go(实际逻辑代码在WasmEdge-image、WasmEdge-tensorflow中)项目中封装的,通过差异化编译方式在正常编译是不附带这些方法。
在rust代码侧使用了两个额外的ABI sdkwasm-bindgen 和适配TensorFlow的wasmedge_tensorflow_interface
在这个项目中可以看到一个wasm项目的几个基础点。制定交互ABI、host实现、目标语言sdk实现、项目引用sdk编写业务逻辑编译为wasm。在本项目中,应用与dapr进行结合,首先dapr作为应用的运行时负责应用与外部的交互(在此项目中体现较少),host加载wasm整体作为一个应用对外提供服务。
搭建及运行
跟随项目说明进行预装软件的安装以及构建及运行项目。
这里有几点额外说明一下。首先在安装环境和预装软件的时候,使用国内的服务器会非常慢所以可能需要一定的科学上网的手段,或者直接看下安装脚本将必要的文件/软件本地下载并上传到服务器(由于下载的东西比较多,所以不太推荐根据脚本手动下载并上传)。或者使用国外的服务器下载过程就非常丝滑。
在运行的时候推荐的命令是阻塞式的,如果你想只开一个命令行窗口把所有必要的软件运行起来,你可以将其后台运行(并将控制台输出重定向到文件)。
make run-api-go & ## Run the image-api-go
make run-api-rs & ## Run the image-api-rs
make run-web & ## Run the Web port service
如果你对某个库有代码修改,想要重新运行,你可以通过以下方式:
dapr list
APP ID HTTP PORT GRPC PORT APP PORT COMMAND AGE CREATED PID
image-api-rs 3502 46317 9004 ./target/release/... 20h 2021-10-12 13:31.33 2327077
image-api-go 3501 42157 9003 ./image-api-go 20h 2021-10-12 13:32.25 2327404
go-web-port 3500 46121 8080 ./web-port 20h 2021-10-12 13:32.44 2327618
以重新运行前端为例,其app id为go-web-port,将其先stop然后重新编译运行即可
dapr stop go-web-port
make build-web
make run-web
效果
运行时会启动3个daprsidecar和3个项目的进程分别为web、go和rust项目。前端项目运行在8080端口通过dapr访问go项目后端api,对图片进行识别。
压测
主要目的是想通过压测查看是否存在内存泄露现象,所以对于压测的次数和时间没有精确要求控制。而且请求会附带图片参数,通过ab和wrk构造压测请求较为麻烦。所以偷懒一下,直接在web-port子项中改造下发送请求的地方,每次点击额外发送100条请求。通过top来查看内存占用情况。由于可能会重启后端服务其pid会变,linux下可以通过运行top -p $(ps -ef | grep ./image-api-go | grep -v grep | grep -v dapr | awk '{print $2}')
发送前记录
top - 10:25:41 up 24 days, 18:14, 2 users, load average: 2.46, 1.23, 0.68
Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.8 us, 0.8 sy, 0.0 ni, 98.3 id, 0.0 wa, 0.0 hi, 0.1 si, 0.0 st
MiB Mem : 7692.5 total, 178.5 free, 1577.7 used, 5936.3 buff/cache
MiB Swap: 0.0 total, 0.0 free, 0.0 used. 5839.4 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
2327420 root 20 0 2993084 694812 72148 S 0.0 8.8 5:27.18 image-api-go
101次请求运行完成后记录:
top - 10:26:02 up 24 days, 18:15, 2 users, load average: 4.31, 1.73, 0.85
Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie
%Cpu(s): 1.0 us, 0.8 sy, 0.0 ni, 98.1 id, 0.0 wa, 0.0 hi, 0.1 si, 0.0 st
MiB Mem : 7692.5 total, 174.3 free, 1581.7 used, 5936.4 buff/cache
MiB Swap: 0.0 total, 0.0 free, 0.0 used. 5835.4 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
2327420 root 20 0 2993084 697244 72148 S 0.0 8.9 6:01.23 image-api-go
增长2432K。每次内存增长并不稳定,最小大概200k,大部分在1-2M左右。(等待所有请求完成后记录的内存大小,运行时的内存峰值在七百多M)
此问题已经给相关项目提了issue,正在排查解决中。
写在最后
wasm已经展现出来了其强大的生命力。即使是处于一个早期阶段,对后端和多语言的各项支持还不是很完善,但是因为其美好的前景,在云原生领域被广泛的接纳。已经有越来越多的webassembly相关项目进入到了cncf中。
从上面的案例可以看出,webassembly作为后端程序被使用时,整体程序架构类似于下图:
其核心是ABI interface的制定,在不同的细分领域可能会有不同的ABI标准。比如在语言层面交互时可能会是WASI,在代理中间件场景就是proxy-wasm,与dapr运行时交互的业务wasm代码可能会在未来发展出dapr-wasm abi。
在制定完成ABI之后用于编译成wasm的语言,比如c、rust、go等就需要按照abi标准去实现和调用外部函数。这部分胶水代码对于同一个语言来说都是一模一样的,而且在大部分场景下完整的实现完成都会有一定的复杂性,所以自然而然的就会产生不同语言自己的clientSDK。
对于wasm的host程序来说也是一样的,需要实现ABI中定义的导入函数,并将对wasm内的函数调用封装成自身语言的函数,对外提供。这样程序只需要集成ABI host Impl代码,即可像调用本地函数一样调用wasm内实现的函数。
因此我觉得在未来,wasm在各个领域的争夺必有一项是关于自身领域ABI标准规范的话语权的争夺。当然也有可能WASI博取百家之长成为一套完备的ABI规范。