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
繁體地址,請注明出處:http://hk.pswp.cn/news/907890.shtml
英文地址,請注明出處:http://en.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 粒子系統名稱…