2 回答

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包 - 同樣,隨意,建議謹慎。

TA貢獻1802條經驗 獲得超10個贊
很快,它可以通過protowire完成,如果重用的結構不復雜的話,這并不難。
根據protobuf的編碼章節,協議緩沖區消息是一系列字段值對,這些對的順序無關緊要。我想到了一個明顯的想法:就像 protoc 編譯器一樣工作,手動組成嵌入字段并將其附加到請求的末尾。
在這種情況下,我們想重用HugeMessage
in?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)
? ? ? ? }
? ? }
}
- 2 回答
- 0 關注
- 288 瀏覽
添加回答
舉報