在编组包含它的消息时,我可以重用现有的 protobuf 二进制文件吗?(protobuf3)

Protobuf 的定义是这样的:


syntax = "proto3"


message HugeMessage {

    // omitted

}


message Request {

    string name = 1;

    HugeMessage payload = 2;

}

在某种情况下,我HugeMessage从某人那里收到了一个消息,我想用额外的字段将其打包,然后将消息传输给其他人。因此,我必须将二进制文件解HugeMessage组为 Go 结构,将其打包为Request,然后再次编组。由于 的 hgue 大小, Unmarshal和MarshalHugeMessage的成本无法承受。那么我可以在不更改 protobuf 定义的情况下重用二进制文件吗?HugeMessage


func main() {

    // receive it from file or network, not important.

    bins, _ := os.ReadFile("hugeMessage.dump")

    var message HugeMessage

    _ = proto.Unmarshal(bins, &message) // slow

    request := Request{

        name: "xxxx",

        payload: message,

    }

    requestBinary, _ := proto.Marshal(&request) // slow

    // send it.

    os.WriteFile("request.dump", requestBinary, 0644)

}


呼如林
浏览 276回答 2
2回答

繁星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)        }    }}
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go