Day 19 / 共 20 天 · 第 4 周 高级特性与实战

性能、稳定性与测试

前面几天你已经能写出功能完整的插件;今天补上"上生产"的最后一环——性能与稳定性。插件跑在网关关键路径上,一点抖动都会放大。今天看 wasm-go 应对 Wasm 特有问题的三个机制(rebuild 重建、IO 限流、streaming inject),以及怎么给插件写单元测试。明天(Day 20)就编译部署收官。

📍 你在整门课的位置(wasm-go 20 天 · 第 4 周 高级特性与实战)
Token 解析 D18 性能/稳定/测试 D19 构建部署 D20 🎓 毕业
L01

Wasm 的内存难题

💡 本质:rebuild = 长跑接力"换人",把内存清零 Wasm 里 Go 的内存"只进不太出"——就像一条越用越占地方的长跑选手,跑久了会越来越"胖"(内存膨胀)。你没法让他中途减肥,但可以换一个体力满格的新人接棒(重建一个干净 VM),旧的退场、内存归零。rebuild 就是"跑够一定路程 或 体重超标就换人"的自动接力机制,用一次性小抖动换长期稳定。
Wasm 里 Go 的 GC 有点尴尬 Go 有垃圾回收(GC),但在 Wasm 环境里,Go runtime 的内存管理受限——内存只增不太还给宿主,长期运行可能累积、导致 VM 内存越来越大。而且 Wasm VM 的内存一旦分配很难收缩。长跑的插件可能慢慢"内存膨胀",影响网关。解法:定期"重建"(rebuild)这个 VM——销毁旧的、建个干净的新的,内存归零。就像"跑久了重启一下"。
VM 内存随请求增长 → 触发 rebuild 归零(锯齿形) 内存 请求数 → 100MB 阈值 rebuild rebuild rebuild 满 1000 请求
图注:不重建时内存会一路涨破阈值;开了 rebuild,每满 1000 请求或到 100MB 就换新 VM,内存被压成安全的锯齿形。
L02

rebuild 重建

CommonVmCtx 有三个 rebuild 相关字段(plugin_wrapper.go:91-93):rebuildAfterRequests(处理多少请求后重建)、requestCount(当前计数)、rebuildMaxMem(内存达多少字节重建)。

通过选项开启(Day 05):WithRebuildAfterRequests(1000) / WithRebuildMaxMemBytes(100*1024*1024)

读法:SDK 在处理请求时累加 requestCount、检查 VM 内存,达到阈值就设"重建标志",让网关在合适时机换一个新 VM。这是"主动预防内存问题"而非"等出事再处理"。
L03

两个触发条件

  • 按请求数WithRebuildAfterRequests):处理满 N 个请求就重建——适合"内存随请求数线性增长"的场景。
  • 按内存WithRebuildMaxMemBytes):VM 内存达到阈值就重建——更精准,直接盯内存。
📝 举个例子:谁先到谁触发WithRebuildAfterRequests(1000) + WithRebuildMaxMemBytes(100MB)
场景 A(内存涨得慢):处理到第 1000 个请求时内存才 40MB → 按请求数先触发重建。
场景 B(有个大 body 请求):才处理 300 个请求,内存已冲到 100MB → 按内存先触发重建。
两条防线像"每 1 万公里 或 每半年保养",哪个先到走哪个。
两条防线可以同时用 请求数是"粗略估计"(假设每请求耗一点内存),内存阈值是"精准兜底"。两个都设,谁先到谁触发。好比汽车保养"每 1 万公里 或 每半年",哪个先到都提醒。取值要权衡:太频繁重建(比如每 100 请求)会有重建开销和瞬时抖动,太久又可能内存涨太多。rebuild-example 里用的是 1000 请求 / 100MB。
L04

rebuild 例子

examples/rebuild-example/main.go

wrapper.SetCtx("rebuild-example",
    wrapper.ParseConfig(parseConfig),
    wrapper.ProcessRequestHeaders(onHttpRequestHeaders),
    wrapper.WithRebuildAfterRequests[RebuildConfig](1000),          // 1000 请求后重建
    wrapper.WithRebuildMaxMemBytes[RebuildConfig](100*1024*1024),   // 或内存达 100MB
)
// 钩子里能读 VM 内存监控:
data, _ := proxywasm.GetProperty([]string{"plugin_vm_memory"})
memorySize := binary.LittleEndian.Uint64([]byte(data))   // 当前 VM 内存字节数
读法:plugin_vm_memory 是 Higress 版 Envoy 暴露的属性(回想 Day 03 的 CallForeignFunction——Higress 定制),能读到当前 VM 内存用量。你可以监控它、打日志观察增长趋势,据此调 rebuild 阈值。
L05

IO 并发限制(回收 Day 10 伏笔)

WithMaxRequestsPerIoCycleplugin_wrapper.go:490,Day 10 在 OnPluginStart 里见它被设置)。注释(:466-489)详解了它的必要性:

复杂插件 + 外部调用的隐患 当插件逻辑复杂、要做 HTTP/Redis 调用时,如果一个 IO 周期里塞了太多并发请求,插件的执行会"堵住"IO 周期,导致外部调用的回调(明明后端已经快速返回了)来不及在本周期处理、被误判超时。限制每 IO 周期的并发请求数能缓解。注意三点:①全局生效(不只当前插件);②多插件设置时取最小值;③推荐值 = 外部调用超时 / 所有插件总执行时间。设太小反而增加 CPU 开销——需要按实际压测调。
L06

streaming inject

Day 12 提过的 NeedPauseStreamingResponse + PushBuffer/PopBuffer + InjectEncodedDataToFilterChain(配套 proto 在 pkg/protos/inject_encoded_data.pb.go):用于"流式响应期间调外部服务再改写内容"。

高级场景:边流边改 普通流式响应是"边收边转发"。但有时要"收到某块后调个外部服务、拿结果改写这块再转发"(比如流式内容审核:把敏感词替换掉)。这就需要:暂停流式(NeedPauseStreamingResponse)→ 缓冲当前数据到内部队列(PushBuffer)→ 异步调外部服务 → 拿到结果后 InjectEncodedDataToFilterChain 把改写后的数据注入回流。这是 wasm-go 里最复杂的能力,只有内容改写类插件才用得上,一般插件无需碰。
L07

pkg/test 单元测试

pkg/test/ 提供测试辅助:test.go(测试框架)、host.go(模拟宿主环境)、redis.go(模拟 Redis)、utils.go。配合 rule_matcher_test.gocluster_wrapper_test.go 等测试文件。

怎么测一个跑在 Wasm 里的插件? 插件正常要在 Envoy 里跑才能测——太重。pkg/test 提供一个"模拟宿主",让你在普通 Go 测试里模拟"请求头到达""发起 HTTP 调用"等,验证插件行为。这样能像测普通 Go 代码一样 go test 插件逻辑,不用起真网关。写插件时配上单测,能在部署前抓住大部分 bug——这是工程化的重要一环。rule_matcher_test.go 学怎么测规则匹配。
L08

今日小结 + 动手

🧠 今天你应该能回答

  • 为什么 Wasm 里 Go 插件会内存膨胀?rebuild 怎么解决?
  • rebuild 两个触发条件?取值怎么权衡?
  • IO 并发限制解决什么隐患?为什么全局取最小值?
  • streaming inject 适合什么场景?怎么给插件写单测?
🎯 记忆口诀 "内存膨胀就 rebuild(够请求数 或 超内存),并发堵 IO 就限流,边流边改用 inject,上线前 pkg/test 跑单测。"
⚠️ 常见误解:小白以为"rebuild 越勤越安全,干脆每 100 请求重建一次"。其实重建本身有开销和瞬时抖动,太勤反而拖慢网关。阈值要按实际内存增长曲线权衡(rebuild-example 用 1000 请求 / 100MB 是个合理起点)。

✋ 动手

cd /Users/bitmart/work/codes/github/higress-group/wasm-go
cat examples/rebuild-example/main.go
sed -n '435,505p' pkg/wrapper/plugin_wrapper.go
ls pkg/test/
go test ./pkg/matcher/ 2>&1 | tail -20
明天预告 · Day 20(收官)构建部署 + 五站大串讲——把 Go 编译成 wasm、打成 OCI 镜像、用 WasmPlugin CRD 部署,走完插件的一生;再把 Envoy→Istio→Higress→Console→wasm-go 五站连成完整全貌。
← Day 18 Day 20 · 构建部署收官 →