繁星coding
简短的回答是:不,没有简单或标准的方法来实现这一点。最明显的策略是按照您当前的方式进行 - 解组HugeMessage,将其设置为Request,然后再次编组。golang protobuf API 表面并没有真正提供一种方法来做更多的事情——这是有充分理由的。也就是说,有多种方法可以实现您想要做的事情。但这些不一定安全或可靠,所以你必须权衡成本与你现在拥有的成本。避免解组的一种方法是利用消息通常序列化的方式;message Request { string name = 1; HugeMessage payload = 2;}.. 相当于message Request { string name = 1; bytes payload = 2;}.. 其中包含针对某些payload调用的结果。Marshal(...)HugeMessage所以,如果我们有以下定义:syntax = "proto3";message HugeMessage { bytes field1 = 1; string field2 = 2; int64 field3 = 3;}message Request { string name = 1; HugeMessage payload = 2;}message RawRequest { string name = 1; bytes payload = 2;}以下代码:req1, err := proto.Marshal(&pb.Request{ Name: "name", Payload: &pb.HugeMessage{ Field1: []byte{1, 2, 3}, Field2: "test", Field3: 948414, },})if err != nil { panic(err)}huge, err := proto.Marshal(&pb.HugeMessage{ Field1: []byte{1, 2, 3}, Field2: "test", Field3: 948414,})if err != nil { panic(err)}req2, err := proto.Marshal(&pb.RawRequest{ Name: "name", Payload: huge,})if err != nil { panic(err)}fmt.Printf("equal? %t\n", bytes.Equal(req1, req2))产出equal? true这个“怪癖”是否完全可靠尚不清楚,也不能保证它会无限期地继续工作。显然,RawRequest类型必须完全反映Request类型,这并不理想。另一种选择是以更手动的方式构建消息,即使用protowire包 - 同样,随意,建议谨慎。
守候你守候我
很快,它可以通过protowire完成,如果重用的结构不复杂的话,这并不难。根据protobuf的编码章节,协议缓冲区消息是一系列字段值对,这些对的顺序无关紧要。我想到了一个明显的想法:就像 protoc 编译器一样工作,手动组成嵌入字段并将其附加到请求的末尾。在这种情况下,我们想重用HugeMessagein Request,所以字段的键值对将是2:{${HugeMessageBinary}}。所以代码(有点不同)可能是:func binaryEmbeddingImplementation(messageBytes []byte, name string) (requestBytes []byte, err error) { // 1. create a request with all ready except the payload. and marshal it. request := protodef.Request{ Name: name, } requestBytes, err = proto.Marshal(&request) if err != nil { return nil, err } // 2. manually append the payload to the request, by protowire. requestBytes = protowire.AppendTag(requestBytes, 2, protowire.BytesType) // embedded message is same as a bytes field, in wire view. requestBytes = protowire.AppendBytes(requestBytes, messageBytes) return requestBytes, nil}告诉字段号,字段类型和字节,就是这样。常见的方式就是这样。func commonImplementation(messageBytes []byte, name string) (requestBytes []byte, err error) { // receive it from file or network, not important. var message protodef.HugeMessage _ = proto.Unmarshal(messageBytes, &message) // slow request := protodef.Request{ Name: name, Payload: &message, } return proto.Marshal(&request) // slow}一些基准。$ go test -bench=a -benchtime 10s ./pkg/ goos: darwingoarch: arm64pkg: pbembedding/pkgBenchmarkCommon-8 49 288026442 ns/opBenchmarkEmbedding-8 201 176032133 ns/opPASSok pbembedding/pkg 80.196spackage pkgimport ( "github.com/stretchr/testify/assert" "golang.org/x/exp/rand" "google.golang.org/protobuf/proto" "pbembedding/pkg/protodef" "testing")var hugeMessageSample = receiveHugeMessageFromSomewhere()func TestEquivalent(t *testing.T) { requestBytes1, _ := commonImplementation(hugeMessageSample, "xxxx") requestBytes2, _ := binaryEmbeddingImplementation(hugeMessageSample, "xxxx") // They are not always equal int bytes. you should compare them in message view instead of binary from // due to: https://developers.google.com/protocol-buffers/docs/encoding#implications // I'm Lazy. assert.NotEmpty(t, requestBytes1) assert.Equal(t, requestBytes1, requestBytes2) var request protodef.Request err := proto.Unmarshal(requestBytes1, &request) assert.NoError(t, err) assert.Equal(t, "xxxx", request.Name)}// actually mock one.func receiveHugeMessageFromSomewhere() []byte { buffer := make([]byte, 1024*1024*1024) _, _ = rand.Read(buffer) message := protodef.HugeMessage{ Data: buffer, } res, _ := proto.Marshal(&message) return res}func BenchmarkCommon(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { _, err := commonImplementation(hugeMessageSample, "xxxx") if err != nil { panic(err) } }}func BenchmarkEmbedding(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { _, err := binaryEmbeddingImplementation(hugeMessageSample, "xxxx") if err != nil { panic(err) } }}