为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
呼如林 2023-03-15 15:26:07
Protobuf 的定义是这样的:syntax = "proto3"message HugeMessage {    // omitted}message Request {    string name = 1;    HugeMessage payload = 2;}在某种情况下,我HugeMessage从某人那里收到了一个消息,我想用额外的字段将其打包,然后将消息传输给其他人。因此,我必须将二进制文件解HugeMessage组为 Go 结构,将其打包为Request,然后再次编组。由于 的 hgue 大小, Unmarshal和MarshalHugeMessage的成本无法承受。那么我可以在不更改 protobuf 定义的情况下重用二进制文件吗?HugeMessagefunc 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)}
查看完整描述

2 回答

?
繁星coding

TA贡献1797条经验 获得超4个赞

简短的回答是:不,没有简单或标准的方法来实现这一点。


最明显的策略是按照您当前的方式进行 - 解组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包 - 同样,随意,建议谨慎。


查看完整回答
反对 回复 2023-03-15
?
守候你守候我

TA贡献1802条经验 获得超10个赞

很快,它可以通过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: darwin

goarch: arm64

pkg: pbembedding/pkg

BenchmarkCommon-8             49         288026442 ns/op

BenchmarkEmbedding-8         201         176032133 ns/op

PASS

ok      pbembedding/pkg 80.196s


package pkg


import (

    "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)

        }

    }

}


查看完整回答
反对 回复 2023-03-15
  • 2 回答
  • 0 关注
  • 275 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号