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 关注
- 275 浏览
添加回答
举报