Higress项目解析(二):Proxy-Wasm Go SDK

3、Proxy-Wasm Go SDK

Proxy-Wasm Go SDK 依赖于 tinygo,同时 Proxy - Wasm Go SDK 是基于 Proxy-Wasm ABI 规范使用 Go 编程语言扩展网络代理(例如 Envoy)的 SDK,而 Proxy-Wasm ABI 定义了网络代理和在网络代理内部运行的 Wasm 虚拟机之间的接口。通过这个 SDK,可以轻松地生成符合 Proxy-Wasm 规范的 Wasm 二进制文件,而无需了解 Proxy-Wasm ABI 规范,同时开发人员可以依赖这个 SDK 的 Go API 来开发插件扩展 Enovy 功能

1)、Proxy-Wasm Go SDK API
1)Contexts

上下文(Contexts) 是 Proxy-Wasm Go SDK 中的接口集合,它们在 types 包中定义。 有四种类型的上下文:VMContextPluginContextTcpContextHttpContext。它们的关系如下图:

  1. VMContext 对应于每个 .vm_config.code,每个 VM 中只存在一个 VMContext
  2. VMContextPluginContexts 的父上下文,负责创建 PluginContext
  3. PluginContext 对应于一个 Plugin 实例。一个 PluginContext 对应于 Http FilterNetwork FilterWasm Serviceconfiguration 字段配置
  4. PluginContextTcpContextHttpContext 的父上下文,并且负责为处理 Http 流的 Http Filter 或 处理 Tcp 流的 Network Filter 创建上下文
  5. TcpContext 负责处理每个 Tcp 流
  6. HttpContext 负责处理每个 Http 流
2)Hostcall API

Hostcall API 是指在 Wasm 模块内调用 Envoy 提供的功能。这些功能通常用于获取外部数据或与 Envoy 交互。在开发 Wasm 插件时,需要访问网络请求的元数据、修改请求或响应头、记录日志等,这些都可以通过 Hostcall API 来实现。 Hostcall API 在 proxywasm 包的 hostcall.go 中定义。 Hostcall API 包括配置和初始化、定时器设置、上下文管理、插件完成、共享队列管理、Redis 操作、Http 调用、TCP 流操作、HTTP 请求/响应头和体操作、共享数据操作、日志操作、属性和元数据操作、指标操作

3)插件调用入口 Entrypoint

当 Envoy 创建 VM 时,在虚拟机内部创建 VMContext 之前,它会在启动阶段调用插件程序的 main 函数。所以必须在 main 函数中传递插件自定义的 VMContext 实现。 proxywasm 包的 SetVMContext 函数是入口点。main 函数如下:

func main() {proxywasm.SetVMContext(&myVMContext{})
}type myVMContext struct { .... }var _ types.VMContext = &myVMContext{}// Implementations follow...
2)、跨虚拟机通信

Envoy 中的跨虚拟机通信(Cross-VM communications)允许在不同线程中运行 的Wasm 虚拟机(VMs)之间进行数据交换和通信。这在需要在多个 VMs 之间聚合数据、统计信息或缓存数据等场景中非常有用。 跨虚拟机通信主要有两种方式:

  • 共享数据(Shared Data):
    • 共享数据是一种在所有 VMs 之间共享的键值存储,可以用于存储和检索简单的数据项
    • 它适用于存储小的、不经常变化的数据,例如配置参数或统计信息
  • 共享队列(Shared Queue):
    • 共享队列允许 VMs 之间进行更复杂的数据交换,支持发送和接收更丰富的数据结构
    • 队列可以用于实现任务调度、异步消息传递等模式
1)共享数据 Shared Data

如果想要在所有 Wasm 虚拟机(VMs)运行的多个工作线程间拥有全局请求计数器,或者想要缓存一些应被所有 Wasm VMs 使用的数据,那么共享数据(Shared Data)或等效的共享键值存储(Shared KVS)就会发挥作用。 共享数据本质上是一个跨所有 VMs 共享的键值存储(即跨 VM 或跨线程)

共享数据 KVS 是根据 vm_config 中指定的创建的。可以在所有 Wasm VMs 之间共享一个键值存储,而它们不必具有相同的二进制文件 vm_config.code,唯一的要求是具有相同的 vm_id

在上图中,可以看到即使它们具有不同的二进制文件( hello.wasmbye.wasm ),vm_id=foo 的 VMs 也共享相同的共享数据存储。hostcall.go 中定义共享数据相关的 API如下:

// GetSharedData 用于检索给定 key 的值
// 返回的 CAS 应用于 SetSharedData 以实现该键的线程安全更新
func GetSharedData(key string) (value []byte, cas uint32, err error)// SetSharedData 用于在共享数据存储中设置键值对
// 共享数据存储按主机中的 vm_config.vm_id 定义
//
// 当给定的 CAS 值与当前值不匹配时,将返回 ErrorStatusCasMismatch
// 这表明其他 Wasm VM 已经成功设置相同键的值,并且该键的当前 CAS 已递增
// 建议在遇到此错误时实现重试逻辑
//
// 将 CAS 设置为 0 将永远不会返回 ErrorStatusCasMismatch 并且总是成功的,
// 但这并不是线程安全的,即可能在您调用此函数时另一个 VM 已经设置了该值,
// 看到的值与存储时的值已经不同
func SetSharedData(key string, value []byte, cas uint32) error

共享数据 API 是其线程安全性和跨 VM 安全性,这通过 CAS (Compare-And-Swap)值来实现。如何使用 GetSharedDataSetSharedData 函数可以参考 示例

在 Higress ai-proxy 插件处理 apiToken 的故障转移场景中就运用了该 API,具体代码可以查看 failover.go

2)共享队列 Shared Queue

如果要在请求/响应处理的同时跨所有 Wasm VMs 聚合指标,或者将一些跨 VM 聚合的信息推送到远程服务器,可以通过 Shared Queue 来实现

Shared Queue 是为 vm_id 和队列名称的组合创建的 FIFO(先进先出)队列。并为该组合(vm_id,名称)分配了一个唯一的 queue id,该 ID 用于入队/出队操作

入队和出队等操作具有线程安全性和跨 VM 安全性。在 hostcall.go 中与 Shared Queue 相关 API 如下:

// DequeueSharedQueue 从给定 queueID 的共享队列中出队数据
// 要获取目标队列的 queue id,请先使用 ResolveSharedQueue
func DequeueSharedQueue(queueID uint32) ([]byte, error)// RegisterSharedQueue 在此插件上下文中注册共享队列
// 注册意味着每当该 queueID 上有新数据入队时,将对此插件上下文调用 OnQueueReady
// 仅适用于 types.PluginContext。返回的 queueID 可用于 Enqueue/DequeueSharedQueue
// 请注意 name 必须在所有共享相同 vm_id 的 Wasm VMs 中是唯一的。使用 vm_id 来分隔共享队列的命名空间
//
// 只有在调用 RegisterSharedQueue 之后,ResolveSharedQueue(此 vm_id, 名称) 才能成功
// 通过其他 VMs 检索 queueID
func RegisterSharedQueue(name string) (queueID uint32, err error)// EnqueueSharedQueue 将数据入队到给定 queueID 的共享队列
// 要获取目标队列的 queue id,请先使用 ResolveSharedQueue
func EnqueueSharedQueue(queueID uint32, data []byte) error// ResolveSharedQueue 获取给定 vmID 和队列名称的 queueID
// 返回的 queueID 可用于 Enqueue/DequeueSharedQueues
func ResolveSharedQueue(vmID, queueName string) (queueID uint32, err error)

RegisterSharedQueueDequeueSharedQueue 由队列的消费者使用,而 ResolveSharedQueueEnqueueSharedQueue 是为队列生产者准备的。请注意:

  • RegisterSharedQueue 用于为调用者的 name 和 vm_id 创建共享队列。使用一个队列,那么必须先由一个 VM 调用这个函数。这可以由 PluginContext 调用,因此可以认为 消费者 = PluginContexts
  • ResolveSharedQueue 用于获取 name 和 vm_id 的 queue id。这是为生产者准备的

这两个调用都返回一个队列 ID,该 ID 用于 DequeueSharedQueueEnqueueSharedQueue。同时当队列中入队新数据时消费者 PluginContext 中有 OnQueueReady(queueID uint32) 接口会收到通知。 还强烈建议由 Envoy 的主线程上的单例 Wasm Service 创建共享队列。否则 OnQueueReady 将在工作线程上调用,这会阻塞它们处理 Http 或 Tcp 流

在这里插入图片描述

在上图中展示共享队列工作原理,更详细如何使用共享队列可以参考 示例

3)、Higress 插件 Go SDK 与处理流程

相对应于 proxy-wasm-go-sdk 中的 VMContext、PluginContext、HttpContext 3 个上下文, 在 Higress 插件 Go SDK 中是 CommonVmCtx、CommonPluginCtx、CommonHttpCtx 3 个支持泛型的 struct。 3 个 struct 的核心内容如下:

// plugins/wasm-go/pkg/wrapper/plugin_wrapper.go
type CommonVmCtx[PluginConfig any] struct {// proxy-wasm-go-sdk VMContext 接口默认实现types.DefaultVMContext// 插件名称pluginName string// 插件日志工具log             LoghasCustomConfig bool// 插件配置解析函数parseConfig ParseConfigFunc[PluginConfig]// 插件路由、域名、服务级别配置解析函数parseRuleConfig ParseRuleConfigFunc[PluginConfig]// 以下是自定义插件回调钩子函数onHttpRequestHeaders        onHttpHeadersFunc[PluginConfig]onHttpRequestBody           onHttpBodyFunc[PluginConfig]onHttpStreamingRequestBody  onHttpStreamingBodyFunc[PluginConfig]onHttpResponseHeaders       onHttpHeadersFunc[PluginConfig]onHttpResponseBody          onHttpBodyFunc[PluginConfig]onHttpStreamingResponseBody onHttpStreamingBodyFunc[PluginConfig]onHttpStreamDone            onHttpStreamDoneFunc[PluginConfig]
}type CommonPluginCtx[PluginConfig any] struct {// proxy-wasm-go-sdk PluginContext 接口默认实现types.DefaultPluginContext// 解析后保存路由、域名、服务级别配置和全局插件配置matcher.RuleMatcher[PluginConfig]// 引用 CommonVmCtxvm          *CommonVmCtx[PluginConfig]// tickFunc 数组onTickFuncs []TickFuncEntry
}type CommonHttpCtx[PluginConfig any] struct {// proxy-wasm-go-sdk HttpContext 接口默认实现types.DefaultHttpContext// 引用 CommonPluginCtxplugin *CommonPluginCtx[PluginConfig]// 当前 Http 上下文下匹配插件配置,可能是路由、域名、服务级别配置或者全局配置config *PluginConfig// 是否处理请求体needRequestBody bool// 是否处理响应体needResponseBody bool// 是否处理流式请求体streamingRequestBody bool// 是否处理流式响应体streamingResponseBody bool// 非流式处理缓存请求体大小requestBodySize int// 非流式处理缓存响应体大小responseBodySize int// Http 上下文 IDcontextID uint32// 自定义插件设置自定义插件上下文userContext map[string]interface{}// 用于在日志或链路追踪中添加自定义属性userAttribute map[string]interface{}
}

它们的关系如下图:

在这里插入图片描述

1)启动入口和 VM 上下文(CommonVmCtx)
func main() {wrapper.SetCtx(// 插件名称"hello-world",// 设置自定义函数解析插件配置,这个方法适合插件全局配置和路由、域名、服务级别配置内容规则是一样wrapper.ParseConfig(parseConfig),// 设置自定义函数解析插件全局配置和路由、域名、服务级别配置,这个方法适合插件全局配置和路由、域名、服务级别配置内容规则不一样wrapper.ParseOverrideConfig(parseConfig, parseRuleConfig),// 设置自定义函数处理请求头wrapper.ProcessRequestHeaders(onHttpRequestHeaders),// 设置自定义函数处理请求体wrapper.ProcessRequestBody(onHttpRequestBody),// 设置自定义函数处理响应头wrapper.ProcessResponseHeaders(onHttpResponseHeaders),// 设置自定义函数处理响应体wrapper.ProcessResponseBody(onHttpResponseBody),// 设置自定义函数处理流式请求体wrapper.ProcessStreamingRequestBody(onHttpStreamingRequestBody),// 设置自定义函数处理流式响应体wrapper.ProcessStreamingResponseBody(onHttpStreamingResponseBody),// 设置自定义函数处理流式请求完成wrapper.ProcessStreamDone(onHttpStreamDone),)
}

根据实际业务需要来选择设置回调钩子函数

跟踪一下 wrapper.SetCtx 的实现:

  1. 创建 CommonVmCtx 对象同时设置自定义插件回调钩子函数
  2. 然后再调用 proxywasm.SetVMContext 设置 VMContext
// plugins/wasm-go/pkg/wrapper/plugin_wrapper.go
func SetCtx[PluginConfig any](pluginName string, options ...CtxOption[PluginConfig]) {// 调用 proxywasm.SetVMContext 设置 VMContextproxywasm.SetVMContext(NewCommonVmCtx(pluginName, options...))
}func NewCommonVmCtx[PluginConfig any](pluginName string, options ...CtxOption[PluginConfig]) *CommonVmCtx[PluginConfig] {logger := &DefaultLog{pluginName, "nil"}opts := []CtxOption[PluginConfig]{WithLogger[PluginConfig](logger)}for _, opt := range options {if opt == nil {continue}opts = append(opts, opt)}return NewCommonVmCtxWithOptions(pluginName, opts...)
}func NewCommonVmCtxWithOptions[PluginConfig any](pluginName string, options ...CtxOption[PluginConfig]) *CommonVmCtx[PluginConfig] {ctx := &CommonVmCtx[PluginConfig]{pluginName:      pluginName,hasCustomConfig: true,}// CommonVmCtx 里设置自定义插件回调钩子函数for _, opt := range options {opt.Apply(ctx)}if ctx.parseConfig == nil {var config PluginConfigif unsafe.Sizeof(config) != 0 {msg := "the `parseConfig` is missing in NewCommonVmCtx's arguments"panic(msg)}ctx.hasCustomConfig = falsectx.parseConfig = parseEmptyPluginConfig[PluginConfig]}return ctx
}

NewCommonVmCtxWithOptions 方法中遍历 options 调用其 Apply 方法来设置自定义插件回调钩子函数

以 onProcessRequestHeadersOption 为例,其定义及 Apply 方法实现如下:

// plugins/wasm-go/pkg/wrapper/plugin_wrapper.go
type onProcessRequestHeadersOption[PluginConfig any] struct {f    onHttpHeadersFunc[PluginConfig]oldF oldOnHttpHeadersFunc[PluginConfig]
}func (o *onProcessRequestHeadersOption[PluginConfig]) Apply(ctx *CommonVmCtx[PluginConfig]) {//  设置 onHttpRequestHeaders 处理函数,这里兼容了旧版本方法(新版本方法中移除了 log 参数)if o.f != nil {ctx.onHttpRequestHeaders = o.f} else {ctx.onHttpRequestHeaders = func(context HttpContext, config PluginConfig) types.Action {return o.oldF(context, config, ctx.log)}}
}func ProcessRequestHeaders[PluginConfig any](f onHttpHeadersFunc[PluginConfig]) CtxOption[PluginConfig] {return &onProcessRequestHeadersOption[PluginConfig]{f: f}
}
2)插件上下文(CommonPluginCtx)

创建 CommonPluginCtx 对象:

通过 CommonVmCtx 的 NewPluginContext 方法创建 CommonPluginCtx 对象, 设置 CommonPluginCtx 的 vm 引用。

// plugins/wasm-go/pkg/wrapper/plugin_wrapper.go
func (ctx *CommonVmCtx[PluginConfig]) NewPluginContext(uint32) types.PluginContext {return &CommonPluginCtx[PluginConfig]{vm: ctx,}
}

插件启动和插件配置解析:

CommonPluginCtx 的 OnPluginStart 部分核心代码如下:

// plugins/wasm-go/pkg/wrapper/plugin_wrapper.go
func (ctx *CommonPluginCtx[PluginConfig]) OnPluginStart(int) types.OnPluginStartStatus {// 调用 proxywasm.GetPluginConfiguration 获取插件配置data, err := proxywasm.GetPluginConfiguration()globalOnTickFuncs = nilif err != nil && err != types.ErrorStatusNotFound {ctx.vm.log.Criticalf("error reading plugin configuration: %v", err)return types.OnPluginStartStatusFailed}var jsonData gjson.Resultif len(data) == 0 {if ctx.vm.hasCustomConfig {ctx.vm.log.Warn("config is empty, but has ParseConfigFunc")}} else {if !gjson.ValidBytes(data) {ctx.vm.log.Warnf("the plugin configuration is not a valid json: %s", string(data))return types.OnPluginStartStatusFailed}pluginID := gjson.GetBytes(data, PluginIDKey).String()if pluginID != "" {ctx.vm.log.ResetID(pluginID)data, _ = sjson.DeleteBytes([]byte(data), PluginIDKey)}// 插件配置转成 jsonjsonData = gjson.ParseBytes(data)}// 设置 parseOverrideConfigvar parseOverrideConfig func(gjson.Result, PluginConfig, *PluginConfig) errorif ctx.vm.parseRuleConfig != nil {parseOverrideConfig = func(js gjson.Result, global PluginConfig, cfg *PluginConfig) error {// 解析插件路由、域名、服务级别插件配置return ctx.vm.parseRuleConfig(js, global, cfg)}}// 解析插件配置err = ctx.ParseRuleConfig(jsonData,func(js gjson.Result, cfg *PluginConfig) error {// 解析插件全局或者当 parseRuleConfig 没有设置时候同时解析路由、域名、服务级别插件配置return ctx.vm.parseConfig(js, cfg)},parseOverrideConfig,)if err != nil {ctx.vm.log.Warnf("parse rule config failed: %v", err)ctx.vm.log.Error("plugin start failed")return types.OnPluginStartStatusFailed}if globalOnTickFuncs != nil {ctx.onTickFuncs = globalOnTickFuncsif err := proxywasm.SetTickPeriodMilliSeconds(100); err != nil {ctx.vm.log.Error("SetTickPeriodMilliSeconds failed, onTick functions will not take effect.")ctx.vm.log.Error("plugin start failed")return types.OnPluginStartStatusFailed}}ctx.vm.log.Info("plugin start successfully")return types.OnPluginStartStatusOK
}

可以发现在解析插件配置过程中有两个回调钩子函数,parseConfig 和 parseRuleConfig

  • parseConfig:解析插件全局配置,如果 parseRuleConfig 没有设置,那么 parseConfig 会同时解析全局配置和路由、域名、服务级别配置。也就是说插件全局配置和路由、域名、服务级别配置规则是一样
  • parseRuleConfig:解析路由、域名、服务级别插件配置。如果设置 parseRuleConfig,也就是说插件全局配置和路由、域名、服务级别配置规则是不同的

大部分情况下插件全局配置和路由、域名、服务级别配置规则是一样的,因此在定义插件时只需要调用 wrapper.ParseConfigBy(parseConfig) 来设置插件配置解析回调钩子函数。 而有些插件(如 basic-auth)的全局配置和路由、域名、服务级别配置规则是不一样的

3)HTTP 上下文(CommonHttpCtx)

创建 CommonHttpCtx:

CommonPluginCtx 的 NewHttpContext 部分核心代码如下:

// plugins/wasm-go/pkg/wrapper/plugin_wrapper.go
func (ctx *CommonPluginCtx[PluginConfig]) NewHttpContext(contextID uint32) types.HttpContext {httpCtx := &CommonHttpCtx[PluginConfig]{plugin:        ctx,contextID:     contextID,userContext:   map[string]interface{}{},userAttribute: map[string]interface{}{},}// 根据插件实现的函数设置是否需要处理请求和响应的 bodyif ctx.vm.onHttpRequestBody != nil || ctx.vm.onHttpStreamingRequestBody != nil {httpCtx.needRequestBody = true}if ctx.vm.onHttpResponseBody != nil || ctx.vm.onHttpStreamingResponseBody != nil {httpCtx.needResponseBody = true}if ctx.vm.onHttpStreamingRequestBody != nil {httpCtx.streamingRequestBody = true}if ctx.vm.onHttpStreamingResponseBody != nil {httpCtx.streamingResponseBody = true}return httpCtx
}

OnHttpRequestHeaders:

// plugins/wasm-go/pkg/wrapper/plugin_wrapper.go
func (ctx *CommonHttpCtx[PluginConfig]) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action {requestID, _ := proxywasm.GetHttpRequestHeader("x-request-id")_ = proxywasm.SetProperty([]string{"x_request_id"}, []byte(requestID))// 获取当前 HTTP 请求生效插件配置config, err := ctx.plugin.GetMatchConfig()if err != nil {ctx.plugin.vm.log.Errorf("get match config failed, err:%v", err)return types.ActionContinue}if config == nil {return types.ActionContinue}// 设置插件配置到 HttpContextctx.config = config// 如果请求 content-type 是 octet-stream/grpc 或者定义 content-encoding,则不处理请求 body// To avoid unexpected operations, plugins do not read the binary content bodyif IsBinaryRequestBody() {ctx.needRequestBody = false}if ctx.plugin.vm.onHttpRequestHeaders == nil {return types.ActionContinue}// 调用自定义插件 onHttpRequestHeaders 回调钩子函数return ctx.plugin.vm.onHttpRequestHeaders(ctx, *config)
}

主要处理逻辑如下:

  • 获取匹配当前 HTTP 请求插件配置,可能是路由、域名、服务级别配置或者全局配置
  • 设置插件配置到 HttpContext
  • 如果请求 content-type 是 octet-stream/grpc 或者定义 content-encoding,则不处理请求 body
  • 调用自定义插件 onHttpRequestHeaders 回调钩子函数

关于插件配置可以看出, Higress 插件 Go SDK 封装如下:

  • 在插件启动时候,解析插件路由、域名、服务级别插件配置和全局配置保存到 CommonPluginCtx 中
  • 在 onHttpRequestHeaders 阶段,根据当前 HTTP 上下文中路由、域名、服务等信息匹配插件配置,返回路由、域名、服务级别配置或者全局配置。然后把匹配到插件配置设置到 HttpContext 对象的 config 属性中,这样自定义插件的所有回调钩子函数就可以获取到这个配置

参考:

Wasm 插件原理

Higress 插件 Go SDK 与处理流程

4)、proxy-wasm-go-sdk tinygo 内存泄漏问题

前置知识:

什么是保守式 GC?

以 JVM 场景下为例:

对于变量 A,JVM 在得到 A 的值后,能够立刻判断出它不是一个引用。因为引用是一个地址,JVM 中地址是 32 位的,也就是 8 位的 16 进制,很明显 A 是一个 4 位 16 进制,不能作为引用(这里称为对齐检查

对于变量 D, JVM 也能够立刻判断出它不是引用,因为 Java 堆的上下边界是知道的,如图中所标识的堆起始地址和最后地址,JVM 发现变量 D 的值早就超出了 Java 堆的边界,故认为它不是引用(这里称为上下边界检查

对于变量 B(实际是一个引用) 和变量 C(实际就是一个 int 型变量),发现它们两个的值是一样的,于是 JVM 就不能判断了。基于这种无法精确识别指针(引用)和非指针(非引用)的垃圾回收方式,被称为保守式 GC

当执行 b = null 之后,对象 B 的实例就应该没有任何指向了,此时它就是个垃圾,应该被回收掉。但是 JVM 错误的认为变量 C 的值是一个引用,因为此时 JVM 很保守,担心会判断错误,所以只好认为 C 也是一个引用,这样,JVM 认为仍然有人在引用对象 B,所以不会回收对象 B

保守式 GC 采用的是模糊的检查方式,这就导致一些实际上已经没有引用指向的对象(即死掉的对象)被错误地认为仍然有引用存在。这些对象无法被垃圾回收器回收,从而造成了无用的内存占用,最终引发资源浪费。这就是保守式 GC 可能导致内存泄漏的核心原因

1)内存泄漏问题

性能问题:

tinygo 最初的 GC 实现性能较差,引入 bdwgc 的保守式 GC 后,性能有了显著提升,例如在 coraza-proxy-wasm 中,每个请求的处理时间从 300ms 缩减到 30ms,GC 暂停时间从几百毫秒减少到 5 - 10ms。但这只是部分情况,并非所有场景都能有如此好的效果

保守式 GC 内存泄漏问题:

保守式 GC 在某些工作负载下会导致无界内存使用。这是因为 32 位、非随机化的地址空间会使指针和普通数学值大量重叠。保守式 GC 在判断一个值是否为指针时,只能通过一些启发式规则进行猜测,当指针和普通数据的值范围重叠时,就可能误判,从而无法正确回收一些不再使用的内存,导致内存不断增长

精确 GC 信息缺失问题:

当尝试使用 bdwgc 的精确 GC 时,虽然能为一些失败的工作负载带来合理的性能,但仍然存在许多内存泄漏的报告。原因是 tinygo 编译器仅在某些情况下为精确 GC 填充信息,而不是所有情况。精确 GC 需要编译器提供准确的对象布局和指针信息,以便准确判断哪些是指针,哪些是普通数据。由于信息不完整,精确 GC 无法正常工作,这本质上还是与保守式 GC 的局限性相关,因为保守式 GC 依赖于不完整或不准确的信息来管理内存

多插件独立 GC 堆导致内存浪费:

即使解决了上述问题,将 bdwgc 集成到 tinygo 中,还会面临另一个问题。当有多个用 Go 编写的 Envoy 插件时,每个插件都有独立的 GC 堆,这会导致大量的内存浪费。因为每个插件的 GC 堆都需要维护自己的内存管理结构,而这些结构可能会有重复,并且无法共享内存资源。虽然 wasm-gc 提案可以解决 GC 语言的这个问题,但由于它不支持内部指针,无法用于 go 语言,并且要实现对 go 语言的支持可能需要大约 2 年的时间

综上所述,保守式 GC 存在性能、内存使用、信息准确性、稳定性和多实例内存管理等多方面的问题,这些问题使得在某些场景下使用保守式 GC 变得困难,甚至不可行

2)社区的后续解决思路

Go 1.24 已支持用原生 Go 编写 Wasm 插件,可通过原生 GC 解决 tinygo + 保守式 GC 的内存泄漏问题。Higress 社区正升级,后续将以 Go 1.24 编写 Wasm 插件为主(代码分支:https://github.com/alibaba/higress/tree/wasm-go-1.24)

在这里插入图片描述

使用原生 Go 语言编写 wasm 的一些劣势:

相比于 tinygo 来说,使用原生 Go 语言编写的 Wasm 插件,Wasm 插件的文件大小会更大一些,也有一定的 RT 损耗,详细可以看下 Higress 中使用 Go 1.24 编译 Wasm 插件验证(https://github.com/alibaba/higress/issues/1768)

参考:

保守式 GC 与准确式 GC,如何在堆中找到某个对象的具体位置?

proxy-wasm-go-sdk 内存泄漏问题说明

Higress go wasm 插件内存泄漏相关 issue:

go-wasm插件需要自行考虑gc问题吗?

推荐阅读:

使用 nottinygc 内存泄漏 case

件的文件大小会更大一些,也有一定的 RT 损耗,详细可以看下 Higress 中使用 Go 1.24 编译 Wasm 插件验证(https://github.com/alibaba/higress/issues/1768)

参考:

保守式 GC 与准确式 GC,如何在堆中找到某个对象的具体位置?

proxy-wasm-go-sdk 内存泄漏问题说明

Higress go wasm 插件内存泄漏相关 issue:

go-wasm插件需要自行考虑gc问题吗?

推荐阅读:

使用 nottinygc 内存泄漏 case

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/news/907890.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

NVMe IP现状扫盲

SSD优势 与机械硬盘(Hard Disk Driver, HDD)相比,基于Flash的SSD具有更快的数据随机访问速度、更快的传输速率和更低的功耗优势,已经被广泛应用于各种计算领域和存储系统。SSD最初遵循为HDD设计的现有主机接口协议,例…

`docker commit` 和 `docker save`区别

理解 docker commit 和 docker save 之间的区别对于正确管理 Docker 镜像非常重要。让我们详细解释一下这两个命令的作用及其区别。 1. docker commit 作用: docker commit roop-builder roop:v1 命令的作用是基于一个正在运行的容器 roop-builder 创建一个新的镜…

Linux内核体系结构简析

1.Linux内核 1.1 Linux内核的任务 从技术层面讲,内核是硬件和软件之间的一个中间层,作用是将应用层序的请求传递给硬件,并充当底层驱动程序,对系统中的各种设备和组件进行寻址。从应用程序的角度讲,应用程序与硬件没有…

python爬虫:Ruia的详细使用(一个基于asyncio和aiohttp的异步爬虫框架)

更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Ruia概述1.1 Ruia介绍1.2 Ruia特点1.3 安装Ruia1.4 使用案例二、基本使用2.1 Request 请求2.2 Response - 响应2.3 Item - 数据提取2.4 Field 提取数据2.5 Spider - 爬虫类2.6 Middleware - 中间件三、高级功能3.1 …

网络攻防技术二:密码学分析

文章目录 一、传统密码分析方法1、根据明文、密文等信息的掌握情况分类 2、从密码分析途径分类二、密码旁路分析1、概念2、旁路分析方法三、现代密码系统1、对称密码(单密钥)2、公开密码(成对密钥) 四、典型对称密码(单…

Linux --TCP协议实现简单的网络通信(中英翻译)

一、什么是TCP协议 1.1 、TCP是传输层的协议,TCP需要连接,TCP是一种可靠性传输协议,TCP是面向字节流的传输协议; 二、TCPserver端的搭建 2.1、我们最终好实现的效果是 客户端在任何时候都能连接到服务端,然后向服务…

pc端小卡片功能-原生JavaScript金融信息与节日日历

代码如下 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>金融信息与节日日历</title><…

C语言——获取变量所在地址(uint8和uint32的区别)

前言&#xff1a; 1.使用uint8 *的原因 在C语言中&#xff0c;获取或操作一个4字节地址&#xff08;指针&#xff09;时使用uint8_t*&#xff08;即unsigned char*&#xff09;而不是uint32_t*&#xff0c;主要基于以下关键原因&#xff1a; 1.1. 避免违反严格别名规则&…

Python----目标检测(《YOLOv3:AnIncrementalImprovement》和YOLO-V3的原理与网络结构)

一、《YOLOv3:AnIncrementalImprovement》 1.1、基本信息 标题&#xff1a;YOLOv3: An Incremental Improvement 作者&#xff1a;Joseph Redmon, Ali Farhadi 机构&#xff1a;华盛顿大学&#xff08;University of Washington&#xff09; 发表时间&#xff1a;2018年 代…

50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | Form Wave(表单label波动效果)

&#x1f4c5; 我们继续 50 个小项目挑战&#xff01;—— FormWave组件 仓库地址&#xff1a;https://github.com/SunACong/50-vue-projects 项目预览地址&#xff1a;https://50-vue-projects.vercel.app/ &#x1f3af; 组件目标 构建一个美观、动态的登录表单&#xff0…

【数据结构】--二叉树--堆(上)

一、树的概念和结构 概念&#xff1a; 树是一种非线性的数据结构&#xff0c;他是由n(n>0)个有限结点组成一个具有层次关系的集合。其叫做树&#xff0c;是因为他倒过来看就和一棵树差不多&#xff0c;其实际上是根在上&#xff0c;树枝在下的。 树的特点&#xff1a; 1…

linux有效裁剪视频的方式(基于ffmpeg,不改变分辨率,帧率,视频质量,不需要三方软件)

就是在Linux上使用OBS Studio录制一个讲座或者其他视频&#xff0c;可能总有些时候会多录制一段时间&#xff0c;但是如果使用剪映或者PR这样的工具在导出的时候总需要烦恼导出的格式和参数&#xff0c;比如剪映就不支持mkv格式的导出&#xff0c;导出成mp4格式的视频就会变得很…

SystemVerilog—Interface语法(一)

SystemVerilog中的接口&#xff08;interface&#xff09;是一种用于封装多模块间通信信号和协议的复合结构&#xff0c;可显著提升代码复用性和维护效率。其核心语法和功能如下&#xff1a; 一、接口的基本定义 1. 声明语法 接口通过interface关键字定义&#xff0c;支持信…

android binder(四)binder驱动详解

ref&#xff1a; Android10.0 Binder通信原理(五)-Binder驱动分析_binder: 1203:1453 ioctl 40046210 77004d93f4 return-CSDN博客 https://juejin.cn/post/7214342319347712057#heading-0 第6课第1节_Binder系统_驱动情景分析_数据结构_哔哩哔哩_bilibili

QT/c++航空返修数据智能分析系统

简介 1、区分普通用户和管理员 2、界面精美 3、功能丰富 4、使用cppjieba分词分析数据 5、支持数据导入导出 6、echarts展示图表 效果展示 演示链接 源码获取 int main(){ //非白嫖 printf("&#x1f4e1;:%S","joyfelic"); return 0; }

ToolsSet之:数值提取及批处理

ToolsSet是微软商店中的一款包含数十种实用工具数百种细分功能的工具集合应用&#xff0c;应用基本功能介绍可以查看以下文章&#xff1a; Windows应用ToolsSet介绍https://blog.csdn.net/BinField/article/details/145898264 ToolsSet中Number菜单下的Numeric Batch是一个数…

Ubuntu20.04 LTS 升级Ubuntu22.04LTS 依赖错误 系统崩溃重装 Ubuntu22.04 LTS

服务器系统为PowerEdge R740 BIOS Version 2.10.2 DELL EMC 1、关机 开机时连续按键盘F2 2、System Setup选择第一个 System BIOS 3、System BIOS Setting 选择 Boot Setting 4、System BIOS Setting-Boot Setting 选择 BIOS Boot Settings 5、重启 开启时连续按键盘F11 …

(javaSE)Java数组进阶:数组初始化 数组访问 数组中的jvm 空指针异常

数组的基础 什么是数组呢? 数组指的是一种容器,可以用来存储同种数据类型的多个值 数组的初始化 初始化&#xff1a;就是在内存中,为数组容器开辟空间,并将数据存入容器中的过程。 数组初始化的两种方式&#xff1a;静态初始化&#xff0c;动态初始化 数组的静态初始化 初始化…

支持向量机(SVM)例题

对于图中所示的线性可分的20个样本数据&#xff0c;利用支持向量机进行预测分类&#xff0c;有三个支持向量 A ( 0 , 2 ) A\left(0, 2\right) A(0,2)、 B ( 2 , 0 ) B\left(2, 0\right) B(2,0) 和 C ( − 1 , − 1 ) C\left(-1, -1\right) C(−1,−1)。 求支持向量机分类器的线…

UE特效Niagara性能分析

开启Niagara调试器 开启显示概览 界面显示 &#x1f7e9; 上方绿色面板&#xff1a;Niagara DebugHud 这是 HUD&#xff08;调试视图&#xff09; 模式下的性能统计显示&#xff0c;内容如下&#xff1a; 项目含义SystemFilter: ShockWave_01当前选中的 Niagara 粒子系统名称…