Protocol Extension Base On Wasm——协议扩展篇
目前 Mesh 本身就有非常多的协议支持的诉求,原来的方式是直接用 Golang 写协议解析的代码,现在通过 Wasm 可以让 MOSN 更好地以更加灵活以及可扩展的方式去支持协议开发。本文主要介绍基于 Wasm 实现开放协议扩展流程和原理,更好的帮助开发者理解和更容易接入 Mesh。
背景
在云原生趋势下,对厂商和客户扩展语言应该是包容的,不应该将用户的技术栈绑定到 Sidecar 语言中,我们关注到 Wasm 能解决这个问题,它提供沙箱隔离机制,允许多语言编写代码打包成 Wasm文件,然后嵌入到 Sidecar 中执行。
理想情况下,MOSN 保证核心组件稳定,厂商私有代码不应该合并到 MOSN 中,应该具备按需扩展能力即可,MOSN 具备灵活的扩展能力,以及对多语言友好、稳定和安全性高等能力。本文重点会以 Go 语言为例,讲解如何基于 Wasm 对 MOSN 的协议进行扩展。
协议拓展
在具体讲解扩展前,简单介绍一下使用 Wasm 扩展的优缺点。
隔离性 : Wasm 扩展将运行在资源受限的沙箱中,扩展代码的漏洞及崩溃都无法传导到沙箱之外,沙箱所使用的 CPU、内存资源等受宿主机(MOSN)控制。 安全性 : Wasm 扩展只能通过一组有限的、明确定义的 ABI 与 MOSN 进行通信,MOSN 对该 ABI 具有完全的控制权,这使得 Wasm 扩展只能使用 MOSN 允许的能力、访问受许可的资源。 敏捷性 : Wasm 扩展框架允许在不重启 MOSN 的前提下,动态加载、更新、卸载 Wasm 扩展插件。 灵活性 : 可以使用多种语言编写 Wasm 扩展,例如: Go、C++、Rust 等,甚至直接复用社区扩展插件。
增加性能开销:目前沙箱和宿主机内存隔离,插件和 MOSN 数据交换需要通过 ABI 和内存 Copy,会增加性能开销。 成熟度相对不足:目前 Wasm runtime 还需要进行生产验证,目前基于 C 的 Wasm 实现相对比较成熟。
1. 模块装载流程
2. 请求/响应流程
请求/响应到达 NETWORK/IO 层。 通过协议去解码 Buffer 数据流,创建上下文。 生成 Stream,封装帧以及连接信息。 经过 Proxy 层进行路由转发, 编码请求/响应。
3. 编解码流程
数据报文委托给扩展协议 Wasm Protocol 解码。 沙箱内扩展协议解码被调用,返回 Command。 Command 在转发前,委托给 Wasm Protocol 编码。 沙箱内开发者扩展编码被调用,返回 Buffer。
当 IO 数据流到达时,Connection 会分发(dispatch)Buffer, 会创建 downstream 的上下文 Context。 在调用 Wasm Protocol 的解码之前,会调用沙箱插件 OnContextCreate 方法创建插件上下文(简称 Wasm Context), Wasm Context 对象会保存在 Host 的上下文中,用于回调插件生命周期方法。 Host 调用插件解码方法,会通过abi规范方法传递 Buffer 字节和长度,同时也会把当前会话的 contextID 透传给插件。 沙箱内 SDK 会根据 contextID 查找已经创建的 Protocol Context (开发者提供的协议插件),调用协议解码并生成 Command。 在整个 Decode 的流程中,Host 和沙箱插件已经获得锁。沙箱插件会根据解出的 Command,生成 Host 侧能理解的 Command 结构,由 Wasm Protocol 生成 Request 或者 Response 类型 Command。 在 Host 转发请求到远端主机时,会再次调用 Wasm Protocol 进行一次编码, 这里会通过编码 ABI 接口,同时会把 contextID 透传给插件。 沙箱内 SDK 会根据 contextID 查找已经创建的 ProtocolContext (开发者提供的协议插件),会先用第 4 步生成的 Command 作为参数,传递给协议插件编码(encode)入参, 如果此时 Host 侧 Header 和 Content 有变更,在传递给协议插件之前,会更新 Command 的 Header 和 Content,保证 Host 侧的内容不会丢失。 当协议插件编码生成 Buffer 时,沙箱 SDK 会负责将编码数据 Copy 到 Host侧(通过 ABI 接口),然后通过 Connection 发送出去。 当收到响应时,针对 Response 的 Command,会创建新的 Context,步骤 1~8 会重新执行一遍。特殊的情况,在收到响应时,Host 清理资源时,会将请求的 Wasm Context 和响应的 Wasm Context 一并清除, 防止内存泄露。
沙箱 SDK 将编码数据 Copy 到 Host 侧,通过以下 ABI 接口对 Host 发起调用:
Qiuck Start
本小节主要演示快速跑通协议扩展流程,我们基于 Wasm 扩展机制实现 wasm-bolt 协议插件(基于原生 bolt 协议),跑通主流程比较简单,分为以下步骤:
提供插件代码,并打包成 bolt-go.wasm 文件。
启动 MOSN 并装载 bolt-go.wasm 插件。
启动 JAVA SOFABoot服务端和客户端程序。
1. 编写协议扩展
如果直接在本地编译,需要 tiny-go >= 0.17.0 版本, 可以在 examples 目录 bolt 中执行命令:
2. 启动 MOSN
如果是研发同学,可以根据 Step 2 拉取代码,直接通过 intellij idea 右键项目根目录 Debug(这样就不用手动去编译且不需要命令行启动 MOSN 了),在 Edit Configurations... 调试配置页签中修改包路径和程序入口参数:
3. 启动 SOFABoot
目前 SOFABoot 应用测试程序已经托管到 Github 上,可以通过以下命令获取:
启动 SOFABoot 服务端程序:
java -DMOSN_ENABLE=true -Drpc_tr_port=12199 -Dspring.profiles.active=dev -Drpc_register_registry_ignore=true -jar sofa-echo-server-web-1.0-SNAPSHOT-executable.jar
然后启动 SOFABoot 客户端程序:
java -DMOSN_ENABLE=true -Drpc_tr_port=12198 -Dspring.profiles.active=dev -Drpc_register_registry_ignore=true -jar sofa-echo-client-web-1.0-SNAPSHOT-executable.jar
当客户端启动成功后,会在终端输出以下信息(每隔 1 秒发起一次 Wasm 请求):
https://github.com/mosn/mosn/pull/1597?spm=ata.21736010.0.0.4e6513eeCnOrtd
- mosn api #31:
- wasm sdk-go:
https://github.com/zonghaishang/proxy-wasm-sdk-go?spm=ata.21736010.0.0.4e6513eeCnOrtd
附:
Wasm 启动配置文件:
https://github.com/mosn/mosn/blob/master/configs/mosn_rpc_config_wasm.json?spm=ata.21736010.0.0.4e6513eeQRr96Y&file=mosn_rpc_config_wasm.json
example 目录:
https://github.com/zonghaishang/proxy-wasm-sdk-go/tree/master/examples/bolt
延伸阅读