亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

在編組包含它的消息時,我可以重用現有的 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 關注
  • 288 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號