点击上方“程序猿技术大咖”,关注并选择“设为星标”

回复“加群”获取入群讨论资格!

本文主要详细介绍如何基于 wasm go sdk 实现协议扩展以及相关细节,更好的帮助开发者支持更多协议场景。

创建插件工程

一、前置准备

  • 安装 go

    链接:https://golang.org/doc/install

  • 安装 tinygo

  • 链接:https://tinygo.org/getting-started/linux/

提示:如果已有 go 不需要重复安装,tinygo 用于编译成 wasm 插件。tinygo 也可以从 github 直接下载解压,把解压后的 bin 目录加入到 PATH 目录。

二、创建项目工程

在 $GOPATH/src 目录中创建工程, 假设项目工程名为 plugin-repo,用来包含多个插件:

# 1. 查看GOPATH路径
go env | grep GOPATH
# 2. 在GOPATH/src目录中创建
mkdir plugin-repo
cd plugin-repo
# 3. 执行项目初始化
go mod init
# 4. 创建协议插件目录名称,假设叫做
boltmkdir -p bolt/main

执行完成后,目录结构如下:

plugin-repo                // 插件仓库根目录
├── go.mod                // 项目依赖管理
└── bolt                    // 插件名称,开发者扩展代码放到这里└── main            // 注册插件逻辑,开发者编写注册插件逻辑└── build            // 插件编译后,自动生成

因为在开始编写插件时,需要依赖 wasm sdk,需要在插件根目录,执行以下命令,拉取依赖:

go get github.com/zonghaishang/proxy-wasm-sdk-go
go mod vendor

提示:完整实例程序已经包含在 github 仓库, 请参考 plugin-repo(https://github.com/zonghaishang/plugin-repo)。

三、 编写插件扩展

在开始编写插件前,我们先展示编写完成后的目录结构:

plugin-repo                // 插件仓库根目录
├── go.mod                // 项目依赖管理
├── Makefile            // 编译插件成wasm文件
└── bolt├── protocol.go├── command.go├── codec.go├── api.go├── main│   ├── main.go│   └── main_test.go├── build            // 插件编译后,自动生成└── bolt-go.wasm

在协议扩展场景,我们主要提供编解码(codec)、编解码对象(command)、协议层支持心跳/响应(protocol)和注册协议(main)。

1 、编解码实现

在处理请求和响应流程中,开发者需要实现 Codec 接口, 方法处理逻辑如下:

  • Decode:需要开发者将data中的字节数据解码成请求或者响应

  • Encode:需要开发者将请求或者响应编码成字节 buffer

type Codec interface {Decode(ctx context.Context, data Buffer) (Command, error)Encode(ctx context.Context, cmd Command) (Buffer, error)
}

注意:在 Decode 流程中,完成解码需要调用 data.Drain(frameLen), frameLen 代表完整请求或者报文总长度。

开发者在编写编解码时,建议采用协议名 +Codec 命名,比如 bolt 编解码,命名为 boltCodec。

目前提供了示例编解码实现,请参考 boltCodec(https://github.com/zonghaishang/plugin-repo/blob/master/bolt/codec.go)。

2、 编解码对象

编解码主要在二进制字节流和请求/响应对象互转,开发者在定义请求/响应对象,应该遵守 command 接口。目前 command 主要分 2 类,请求和响应。

  • 请求对象:主要包括请求(request-response)、请求(oneway)、心跳类型

  • 响应对象:主要包括响应请求结果对象

请求对象除了表达 request-response 模型、oneway 和心跳,也会承载超时等属性,与之对应响应会承载响应状态码。

目前请求和响应的接口契约如下:

type Request interface {Command// IsOneWay Check that the request does not care about the response    IsOneWay() bool    GetTimeout() uint32 // request timeout}type Response interface {CommandGetStatus() uint32 // response status}

不管请求还是响应,除了识别 command 类型,还承担请求头部和请求体 2 部分,头部是普通的 key-value 结构,data 部分应该是协议的 content 部分,而不是完整报文内容。

目前 command 的接口定义如下:

// Command base request or response command
type Command interface {// Header get the data exchange header, maybe return nil.   GetHeader() Header// GetData return the full message buffer, the protocol header is not included    GetData() Buffer// SetData update the full message buffer, the protocol header is not included    SetData(data Buffer)// IsHeartbeat check if the request is a heartbeat request    IsHeartbeat() bool    // CommandId get command id    CommandId() uint64    // SetCommandId update command id    // In upstream, because of connection multiplexing,    // the id of downstream needs to be replaced with id of upstream    // blog: https:mosn.io/blog/posts/multi-protocol-deep-dive/#%E5%8D%8F%E8%AE%AE%E6%89%A9%E5%B1%95%E6%A1%86%E6%9E%B6    SetCommandId(id uint64)
}

目前提供了示例编解码对象实现,请参考 command(https://github.com/zonghaishang/plugin-repo/blob/master/bolt/command.go)。

3、 协议层

因为心跳需要协议层理解,如果开发者扩展的协议支持心跳能力,应当提供扩展 KeepAlive 实现:

  • KeepAlive: 根据请求 id 生成一个心跳请求 command

  • ReplyKeepAlive: 根据收到的请求,返回一个心跳响应 command

type KeepAlive interface {KeepAlive(requestId uint64) RequestReplyKeepAlive(request Request) Response
}

注意:如果扩展协议不支持心跳或者不需要心跳,协议层 KeepAlive 方法返回 nil 即可

在 service mesh 场景中,因为增加了一跳,mesh 在转发过程中可能被控制面拦截,比如限流熔断,需要协议层构造并返回响应,因此开发者需要提供 Hijacker 接口实现:

  • Hijack: 根据请求和拦截状态码,返回一个响应 command

type Hijacker interface {// Hijack allows sidecar to hijack requests    Hijack(request Request, code uint32) Response
}

目前协议层接口采用组合方式,主要讲编解码独立拆分出去, protocol 接口定义:

type Protocol interface {Name() string    Codec() CodecKeepAliveHijackerOptions
}

接口中方法描述:

  • Name:返回协议名称

  • Codec:返回协议编解码对象

  • KeepAlive:协议心跳实现

  • Hijacker:处理控制面拦截逻辑

  • Options:协议层配置选项开发,一般协议组合默认配置 proxy.DefaultOptions

目前提供了示例协议实现,请参考 protocol(https://github.com/zonghaishang/plugin-repo/blob/master/bolt/protocol.go)。

4、 注册协议

在完成协议扩展后,需要将我们编写的插件进行注册,在 wasm 扩展中,我们一切是以 Context 为核心来转的,比如 host 侧触发解码,在沙箱内会调用开发者 protocol context 的回调来解码。

因此注册协议我们需要提供一个 ProtocolContext 接口实现,和 protocol 接口极其类似:

// L7 layer extension
type ProtocolContext interface {Name() string         // protocol nameCodec() Codec         // frame encode & decodeKeepAlive() KeepAlive // protocol keep aliveHijacker() Hijacker   // protocol hijackerOptions() Options     // protocol options
}

以 bolt 协议插件为例,我们提供 boltProtocolContext 实现:

// 1. 提供bolt插件protocolContext实现
type boltProtocolContext struct {proxy.DefaultRootContext             // notify on plugin start.proxy.DefaultProtocolContext     // 继承默认协议实现,比如使用默认Options()bolt      proxy.Protocol            // 插件真实协议实现contextID uint32
}// 2. 创建bolt单实例协议实例
var boltProtocol = bolt.NewBoltProtocol()func boltContext(rootContextID, contextID uint32) proxy.ProtocolContext {return &boltProtocolContext{bolt:      boltProtocol,contextID: contextID,}
}// 3. 注册boltContext协议钩子
func main() {proxy.SetNewProtocolContext(boltContext)
}// 4. 如果协议不支持心跳,这里允许返回nil
func (proto *boltProtocolContext) KeepAlive() proxy.KeepAlive {return proto.bolt
}// 5. 如果需要获取插件参数,可以override对应方法
func (proto *boltProtocolContext) OnPluginStart(conf proxy.ConfigMap) bool {proxy.Log.Infof("proxy_on_plugin_start from Go!")return true
}

目前提供了示例协议注册实现,请参考 main。

5、 调试&打包

开发者在编写完插件后,允许在本地 idea 直接开始调试测试,并且不依赖 MOSN 启动。目前推荐在协议开发完后,提供 main_test.go 实现,在里面写集成测试。

目前 wasm sdk 提供了模拟器实现(Emulator), 可以模拟完整的 MOSN 处理流程,并且可以回调开发者插件对应生命周期方法。基本用法:

    // 1. 注册对应context和配置,boltContext在同一个main包下已经实现opt := proxy.NewEmulatorOption().WithNewProtocolContext(boltContext).WithNewRootContext(rootContext).WithVMConfiguration(vmConfig)// 2. 创建一个sidecar模拟器host := proxy.NewHostEmulator(opt)// release lock and reset emulator statedefer host.Done()// 3. 调用host对应实现,比如启动沙箱host.StartVM()// 4. 调用启动插件host.StartPlugin()// 5. 模拟新请求到来,创建插件上下文ctxId := host.NewProtocolContext()// 6. 模拟host接收客户端请求,并解码cmd, err := host.Decode(...)// 7. 模拟host转发请求,并编码upstreamBuf, err := host.Encode(...)// 8. 模拟host处理完请求host.CompleteProtocolContext(ctxId)

如果要在 GoLand 中直接调试集成测试, 需要执行以下操作:

  • GoLand->Preferences...->Go->Build Tags & Vendoring->Custom tags填写proxytest

  • 调试窗口 Edit Configurations...-> 勾选 Use all custom build tags

目前提供了示例集成测试,请参考 main test(https://github.com/zonghaishang/plugin-repo/blob/master/bolt/main/main_test.go)。

目前打包插件,可以本地开发环境编译打包,也支持镜像方式编译插件,目前通用 makefile(https://github.com/zonghaishang/plugin-repo/blob/master/Makefile)已经提供,可以 copy 到插件项目根目录中使用。

基于 makefile,2 种打包命令分别如下(编译成功会在插件中创建 build 文件夹,并且输出 bolt-go.wasm):

# 1. 本地编译,bolt替换成开发者插件名
make name=bolt# 2. 基于镜像编译
make build-image name=bolt

目前提供了示例打包文件,请参考 bolt-go(https://github.com/zonghaishang/plugin-repo/tree/master/bolt/build)。

四、启动 MOSN

目前提供了一份用于 wasm 启动的配置文件 mosn_rpc_config_wasm.json(https://github.com/mosn/mosn/blob/master/configs/mosn_rpc_config_wasm.json),可以使用以下命令启动 MOSN:

./mosnd start -c /path/to/mosn_rpc_config_wasm.json

提示:

  • 目前提供的配置,会开启 2045 和 2046 端口,2045 接收客户端请求,通过 2046 转发给服务端

  • mosn_rpc_config_wasm 中已经配置了 bolt-go.wasm,在项目根目录 etc/wasm/目录中

  • 如果是自定义协议插件,配置 mosn_rpc_config_wasm.json 中有几点需要修改

    • vm_config.path 指向的 wasm 路径

    • wasm_global_plugins.plugin_name和codecs.config.from_wasm_plugin 要相同

    • codecs.config.from_wasm_plugin 和 extend_config.sub_protocol 要相同(一般协议有 2 个 listener 都要改)

其中, mosnd 可执行文件可以通过编译 MOSN 获取,执行以下命令:

# 下载mosn代码到本地GOPATH, 可以通过本地shell执行:go env | grep GOPATH 查看
# step 1:
mkdir -p $GOPATH/src/mosn.io
cd $GOPATH/src/mosn.io# step 2:
# clone mosn源码
git clone https://github.com/mosn/mosn.git# step 3:
# 本地编译
sudo make build-local tags=wasmer# 编译成功后,会在项目根目录下
build/bundles/v0.21.0/binary/mosnd

如果是研发同学,可以根据 step 2 拉取代码,直接通过 GoLand 右键项目根目录 Debug(这样就不用手动去编译以及不需要命令行启动 MOSN 了), 在 Edit Configurations... 调试配置页签中修改包路径和程序入口参数:

Package path: mosn.io/mosn/cmd/mosn/main
Program arguments: start -c /path/to/mosn_rpc_config_wasm.json

提示:

  • /path/to 需要替换成 MOSN 根目录中到 mosn_rpc_config_wasm.json 文件的完整路径

  • 如果要在 GoLand 中直接调试 MOSN(默认 wasm 模块没有编译), 需要执行以下操作:

    • GoLand->Preferences...->Go->Build Tags & Vendoring->Custom tags追加 wasmer

    • 调试窗口 Edit Configurations...-> 勾选 Use all custom build tags

五、启动应用服务

开发完成后,可以先启动 MOSN,然后启动应用的服务端和客户端,以 SOFABoot 应用为例展示。

目前 SOFABoot 应用测试程序已经托管到 github 上,可以通过以下命令获取:

git clone https://github.com/sofastack-guides/sofastack-mesh-demo.git
# checkout到wasm_benchmark分支
git checkout wasm_benchmarkcd sofastack-mesh-demo/sofa-samples-springboot2
# 本地打包sofaboot应用程序
mvn clean package
# 打包成功后,会在sofa-echo-server和sofa-echo-client下生成target目录,
# 其中分别包含服务端和客户端可执行程序,文件名分别为:
# sofa-echo-server-web-1.0-SNAPSHOT-executable.jar
# sofa-echo-client-web-1.0-SNAPSHOT-executable.jar

启动 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 请求):

>>>>>>>> [57,21,7ms]2021-03-16 20:57:05 echo result: Hello world!
>>>>>>>> [57,22,5ms]2021-03-16 20:57:06 echo result: Hello world!
>>>>>>>> [57,23,7ms]2021-03-16 20:57:07 echo result: Hello world!
>>>>>>>> [57,24,7ms]2021-03-16 20:57:08 echo result: Hello world!
>>>>>>>> [57,25,8ms]2021-03-16 20:57:09 echo result: Hello world!
>>>>>>>> [57,26,7ms]2021-03-16 20:57:10 echo result: Hello world!
>>>>>>>> [57,27,5ms]2021-03-16 20:57:11 echo result: Hello world!
>>>>>>>> [57,28,7ms]2021-03-16 20:57:12 echo result: Hello world!

当前扩展特性已经合并进开源社区,感兴趣同学可以查看实现原理:

  • wasm protocol #1579

    (https://github.com/mosn/mosn/pull/1597)

  • mosn api #31

    (https://github.com/mosn/api/pull/31)

  • wasm sdk-go

    (https://github.com/zonghaishang/proxy-wasm-sdk-go)


感谢您的阅读,也欢迎您发表关于这篇文章的任何建议,关注我,技术不迷茫!

  • Apache Dubbo 3.0.0 正式发布 - 全面拥抱云原生

  • 微服务该如何拆分?

  • MySQL性能优化(七):MySQL执行计划,真的很重要,来一起学习吧

  • 微服务架构下的核心话题 (三):微服务架构的技术选型

喜欢就点个"在看"呗,留言、转发朋友圈

开发 Wasm 协议插件指南相关推荐

  1. 分享13个帮助你简化开发的jQuery插件

    为什么80%的码农都做不了架构师?>>>    日期:2012-7-23  来源:GBin1.com jQuery的社区力量的重要体现就是jQuery插件,我们每隔一段时间就会在jQ ...

  2. canopen服务器协议,ZOPC_Server(ZLG通用OPC服务器)CANopen协议插件

    ZOPC_Server(ZLG通用OPC服务器)CANopen协议插件是一款基于CAN的一种高层协议,是一种具有灵活配置功能的标准嵌入式网络.它的任务是控制和监测所有NMT从站的NMT状态.通常,CA ...

  3. 《微软云计算Windows Azure开发与部署权威指南》——6.8 AppFabric服务总线的多播服务开发...

    本节书摘来自异步社区<微软云计算Windows Azure开发与部署权威指南>一书中的第6章,第6.8节,作者: 尹成 , 郝庭毅 , 张俊强 , 孙奉刚 , 寇睿明 更多章节内容可以访问 ...

  4. Flutter App开发蓝牙协议

    Flutter App开发蓝牙协议 Summary BLE低功耗蓝牙,是我们常说的蓝牙4.0, 该技术有极低的运行待机功耗,本文记录使用Flutter开发安卓App的过程,使用蓝牙模块的配置和一些细节 ...

  5. 基于开源 Openfire 聊天服务器 - 开发Openfire 聊天记录插件

    上一篇文章介绍到怎么在自己的Java环境中搭建openfire插件开发的环境,同时介绍到怎样一步步简单的开发openfire插件.一步步很详细的介绍到简单插件开发,带Servlet的插件的开发.带JS ...

  6. 开发Openfire聊天记录插件

    上一篇文章介绍到怎么在自己的Java环境中搭建openfire插件开发的环境,同时介绍到怎样一步步简单的开发openfire插件.一步步很详细的介绍到简单插件开发,带Servlet的插件的开发.带JS ...

  7. 实用Jquery开发自己的插件

    实用Jquery开发自己的插件 jQuery.fn.extend(object); jQuery.extend(object); jQuery.extend(object); 为扩展jQuery类本身 ...

  8. iOS网络编程开发—HTTP协议

    iOS网络编程开发-HTTP协议 说明:apache tomcat服务器必须占用8080端口 一.URL 1.基本介绍 URL的全称是Uniform Resource Locator(统一资源定位符) ...

  9. 使用 WordPress 插件模板开发高质量插件

    WordPress 插件样板是标准化的,有组织的,面向对象的基础,用于构建高品质的 WordPress 插件.样板遵循编码标准和文件标准,所以你不必自己学习这些,根据注释编写代码即可. 官方网站    ...

最新文章

  1. coreldraw绘制兔子视频_53个CAD市政工程设计视频教程+62个图纸,3天学会制图!
  2. python中 __name__及__main()__的妙处02
  3. LeetCode - 413. Arithmetic Slices - 含中文题意解释 - O(n) - ( C++ ) - 解题报告
  4. uCOS-II核心算法分析(μCOS-Ⅱ)
  5. MySQL Proxy和 Amoeba 工作机制浅析
  6. 构建大数据网络 需要重视这五个地方
  7. matlab图片文件批量处理
  8. 振铃效应与样点自适应补偿(Sample Adaptive Offset,SAO)技术
  9. 史上最简单的免费短信验证码案例
  10. Windows7双系统卸载Ubuntu
  11. 网站403报错问题原因解答
  12. wordpress博客构建
  13. Kafka 入门教程(超详细)
  14. [ZT]系统学习Linux的11点建议
  15. 大数据精准投放平台_大数据精准投放,让你摆脱千篇一律的广告投放!
  16. 实现倒计时的几种方案汇总
  17. Linux中cron的用法,Linux中cron命令的用法详解
  18. oracle 中创建表分区,oracle三种分区表的建立
  19. 【洛谷 P3348】【ZJOI2016】—大森林(LCT)
  20. WhatsApp宣布对所有通讯信息进行端到端加密

热门文章

  1. 双色球(投注号码由6个红色球号码和1个蓝色球号码组成。红色号码从1-33中选择;蓝色球号码从1-16中选择。)
  2. 2023-03-24:音视频mp3和h264混合(muxer)编码为mp4,用go语言编写。
  3. 色彩静物画法:先找体积再塑造细节~
  4. 硅谷外卖学习日记之babelrc
  5. 不用服务器,用对象存储打开网站,腾讯云,阿里云,静态网站,中文域名
  6. a记录 mysql_域名的A记录、mx记录、ns记录等怎么查看
  7. VB常见错误和难点分析
  8. 【ML算法学习】子空间不一致度量(SDM)、测地线流核(GFK)及域排名(ROD)
  9. iphone启用证书_如何在iPhone上启用紧急SOS服务
  10. Android 上下滚动条、轮训滚动、广告条