[Feature] Extend the marshalAppender fast path to the client-side non-IDL wrapper codec
- Dominant language
- Go
- Stars
- 5k
- Forks
- 1k
- Avg merge
- 2d 8h
- Merged PRs (30d)
- 31
Description
### 摘要
#3718 已在服务端与客户端 IDL接入 `marshalAppender` 快路径(`marshalToPool` + 池化 buffer),唯独**客户端非 IDL 请求**(`protoWrapperCodec`)未实现 `MarshalAppend`:类型断言 `w.codec.(marshalAppender)` 必然失败,整条路径回退到 `codec.Marshal` 慢路径,该优化在客户端非 IDL 请求场景并未生效。
### 相关代码
| 位置 | 为什么优化未覆盖 |
| ------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| https://github.com/apache/dubbo-go/blob/develop/protocol/triple/triple_protocol/codec.go#L267-L292 | 只实现了 `Marshal`,**没有** `MarshalAppend`;类型断言命中不了,`marshalToPool` 快路径无从进入 |
| https://github.com/apache/dubbo-go/blob/develop/protocol/triple/triple_protocol/option.go#L116-L122 | 客户端非 IDL 的唯一入口,构造 `newProtoWrapperCodec(&hessian2Codec{})` / `newProtoWrapperCodec(&msgpackCodec{})`,未暴露 appender 能力 |
| https://github.com/apache/dubbo-go/blob/develop/protocol/triple/triple_protocol/envelope.go#L69-L95 | gRPC wire 的 appender 探测点(`w.codec.(marshalAppender)`)。因 wrapper 未实现而失败,落到 `marshalWithFallback` 慢路径 |
| https://github.com/apache/dubbo-go/blob/develop/protocol/triple/triple_protocol/protocol_triple.go#L504-L523 | Triple wire 的 unary 生产入口,同为探测点,同样必然失败 |
| https://github.com/apache/dubbo-go/blob/develop/protocol/triple/triple_protocol/marshal_perf_regression_test.go#L721-L755 | 当前显式断言`protoWrapperCodec`不得实现`marshalAppender`(名为 `proto wrapper (hessian inner)` 的 never 用例) |
### 建议优化
为 `protoWrapperCodec` 增加 `MarshalAppend`:内层语义逐字保持,仅把外层 wrapper 编码从"新分配"改为"追加进调用方缓冲区"。
**1. codec 层:新增可选方法(不改** **`Codec`** **接口)**
```go
// MarshalAppend appends the TripleRequestWrapper encoding of message to dst.
// The inner payload encoding is intentionally identical to Marshal: this
// extension only changes where the outer wrapper bytes are written.
func (c *protoWrapperCodec) MarshalAppend(dst []byte, message any) ([]byte, error) {
reqs, ok := message.([]any)
if !ok {
reqs = []any{message}
}
reqsLen := len(reqs)
reqsBytes := make([][]byte, reqsLen)
reqsTypes := make([]string, reqsLen)
for i, req := range reqs {
reqBytes, err := c.innerCodec.Marshal(req) // unchanged: inner payload stays as-is
if err != nil {
return nil, err
}
reqsBytes[i] = reqBytes
reqsTypes[i] = getArgType(req)
}
wrapperReq := &interoperability.TripleRequestWrapper{
SerializeType: c.innerCodec.Name(),
Args: reqsBytes,
ArgTypes: reqsTypes,
}
return proto.MarshalOptions{}.MarshalAppend(dst, wrapperReq)
}
```
`proto.MarshalOptions.MarshalAppend` 把调用方 `dst` 直接交给编码器,`cap(dst)-len(dst) >= size` 时跳过 `make + copy` 走纯 append(生成代码的 `Size` + append 编码函数,`sizecache` 缓存 size),**零分配、零清零**。
**2. 发送路径:零调用点改动**
快路径探测是类型断言(`w.codec.(marshalAppender)`),因此 wrapper 一旦实现该方法,`envelopeWriter.Marshal` 与 `tripleUnaryMarshaler.Marshal` **自动命中**现有池化管道(`marshalToPool` → `bufferPool`)。
**3. 回退语义与 #3718 保持一致**
仅 marshal 失败才回退 `backupCodec`;写失败(I/O、压缩、`sendMaxBytes` 超限)为终止性错误,绝不回退重试。`MarshalAppend` 的错误语义与 `Marshal` 等价(同一错误对象直接向上返回,由快路径转成 `CodeInternal`)。
**4. 零开关、零配置回退**
不新增配置项、不改公开接口:任何未实现 `marshalAppender` 的 codec(hessian2 / msgpack / JSON / 第三方)行为完全不变,整体回退无需发布开关。
### 测试
本提案**只新增 wrapper 定向测试**;发送管道层(池、压缩、回退、并发)的测试直接复用 #3718,不重复实现。
新增测试:
1. `TestMarshalPerfClientWrapperFastPath`:两条 wire 均命中快路径(防单侧探测点失效导致一半流量白优化)+ 快慢路径 wire 字节对拍(防协议漂移),覆盖 hessian2 / msgpack × 压缩开 / 关;
2. `TestNonIDLUnary_PublicEntry_MarshalAppendEffective`:经真实 `NewClient` → `CallUnary` 入口确认优化确实生效——wrapper 一旦丢掉 `MarshalAppend` 或探测点失效即失败,不静默退回优化前行为;
3. `TestMarshalPerfTypeGuard`(就地扩围):正向断言 wrapper 必须实现 `marshalAppender`,反向断言 `hessian2` / `msgpack` / `json` 仍不实现。
复用已合入的测试
`TestMarshalPerfPoolInvariants` / `TestMarshalPerfBackupCodecFallback` / `TestMarshalPerfCompressionAndMaxBytes` / `TestMarshalPerfConcurrentSend` 验证对象是**发送管道**(`marshalToPool` + `bufferPool` + 压缩 + 回退 + 并发)而非具体 codec,wrapper可被覆盖
### 参考实现
本提案与 #3718 为同做法,仅作用对象不同
Contributor guide
Assessment
This issue has not been assessed yet.