Google protobuf使用技巧和经验总结

技巧 & 经验

性能优化

把repeated message结构尽可能摊平为基础类型的repeated字段

基础类型的repeated字段,包含 repeated int32, int64, float,double,bool等,但不包含string、bytes、message

比如:

message Item {
    int32 id = 1;
    int32 score = 2;
}

message R {
    repeated Item items = 1;
}

改为下面的设计,会提升序列化和反序列效率

message R {
    repeated int32 item_id = 1;
    repeated int32 item_score = 2;
}

原理是非string的基础类型的repeated字段,在申请内存时pb会申请连续线性大块内存,效率高;而message 的repeated字段,会按对象逐个去申请空间。

善用arena管理内存

  • arena对基础类型,比如int32, int64, float,double,bool等管理效率优化明显
  • arena不会管理字符串类型的内存申请。

用固定长度repeated uint32 替换字符串

字符串是一种不定长的数据结构,内存管理方式成本较高。通过转换成repeated uint32类型,则可以获得更高效的管理。

除此之外,repeated uint32 也支持由arena管理。

善用Any类型

假设3个网络服务的调用关系如下:
A->B->C。
其中存在某些pb结构仅会由B透传给C,而B不需要解析,则可以把这些pb放入定义为any类型的字段中。

利用protobuf一些特性来规避陷阱

良好的可扩展性 & 保留未定义字段

良好的可扩展性使得protobuf更好地向后兼容。上游更新了proto,新增字段,下游虽然没有更新proto文件,但是新增的字段依然可以保留,来自上游的字段可以透传给下游。拼接下游请求的结构pb时,尽可能使用CopyFrom,避免把字段逐个set。

使用编号定位存储的字段

为了更好地向后兼容,应该避免修改proto文件中现有字段的名字、类型。需要修改时,通过追加新字段(字段编号增加),弃用旧字段的方式。

故障相关

protobuf被广泛使用,饱经业界考验,如果遇到问题,绝大多数还是自身软件设计的问题。遇到问题,首先不应该怀疑protobuf,应该把视角集中到去发现自身的系统设计缺陷中。

一次内存泄露的故障排查

现象:

公司里一个c++网络服务中, PV较低时没有内存泄露;而PV较高,cpu idle降到30%以下,开始内存泄露,直到OOM。

排查过程:

用了 tcmalloc和gperf,逐步定位是protobuf 申请 repeated字段的空间,没有及时释放。repeated字段约1k~1w的规模。然后逐步缩小范围。

结果:

竟然是释放内存,都放到了一个线程中。当流量大时,单个线程计算能力成为瓶颈,内存释放变慢,表现为内存泄漏。


转载请注明来源,原地址保持持续更新。
博客首页:作程的技术博客
文章标题:《Google protobuf使用技巧和经验总结》
本文链接:https://it.zuocheng.net/google-protobuf-skills-experience-zh

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Google Protobuf 是一种序列化的数据格式,它支持通过 extend 机制扩展已有的 protobuf 消息类型,同时也提供了插件机制,可以方便地自定义代码生成器。 使用 protobuf extend 扩展已有消息类型,可以在不修改原有消息定义的情况下,为其添加新的字段或方法。具体操作步骤如下: 1. 在一个单独的 .proto 文件中定义 extend 对象,用于扩展某个消息类型。例如,我们可以定义一个 extend 对象用于扩展 google.protobuf.FileDescriptorProto 消息类型: ``` extend google.protobuf.FileDescriptorProto { optional string new_field = 1000; } ``` 2. 在另一个 .proto 文件中引用该 extend 对象,并使用它来扩展原有的消息类型。例如,在一个 .proto 文件中,我们可以使用上述定义的 extend 对象来扩展 google.protobuf.FileDescriptorProto 消息类型: ``` import "google/protobuf/descriptor.proto"; extend google.protobuf.FileDescriptorProto { optional string new_field = 1000; } ``` 3. 在代码中使用扩展后的消息类型时,需要使用扩展字段的编号来访问。例如,在 C++ 中,可以通过以下方式访问扩展字段: ```cpp const auto& value = message.GetExtension(google::protobuf::new_field); ``` 编写 protobuf 插件可以帮助我们自动生成代码、文档等辅助工具,从而提高开发效率。具体操作步骤如下: 1. 编写 protobuf 插件需要使用 protobuf 自带的代码生成器 protoc,并指定使用我们自己编写的插件。同时,我们还需要为插件编写一个 protobuf 描述文件,用于描述该插件接收的输入和输出数据格式。 2. 在代码中实现插件的主要逻辑。我们需要解析 protoc 传递给插件的输入数据,生成相应的输出数据,并将其传递给 protoc 进行处理。 3. 将插件编译成可执行文件,并将其与 protoc 放在同一个目录下,以便 protoc 可以找到并使用它。 4. 使用 protoc 命令时,指定使用我们自己编写的插件,并指定输入和输出文件的路径。例如,在使用 C++ 代码生成器时,可以使用以下命令: ```bash protoc --plugin=protoc-gen-custom=./custom_generator --custom_out=./output_dir ./input.proto ``` 上述命令将使用名为 custom_generator 的插件来生成 C++ 代码,并将生成的代码输出到 output_dir 目录下。 需要注意的是,protobuf 插件的编写需要熟悉 protobuf 的底层实现原理,并具备一定的编程能力。因此,对于初学者来说可能会比较困难。建议先学习 protobuf 基础知识和使用方法,再考虑编写自己的插件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值