本文旨在分享在广告系统开发中使用protobuf时遇到的一个具体问题及其解决方案。通过详细分析,希望能帮助读者更好地理解和应用protobuf。
在高性能需求的广告系统中,毫秒级的延迟优化至关重要。为了提高效率,我们选择了protobuf作为与外部媒体通信的数据格式,相较于JSON和XML,它能显著减少编解码时间和网络传输成本。然而,这种高效也伴随着一定的不便,如无法直接通过文本编辑器查看或修改数据,且问题排查难度增加。
- 遇到的问题 -
7月,一家媒体提出希望一次请求能获取多条广告,以便更好地适应信息流场景。为此,我们在Impression
类型中添加了一个ads_count
字段,用于指定所需广告的数量。按照常规流程更新了.proto文件,并生成了新的Go语言代码。然而,无论媒体请求中填写的具体数值如何,我们始终只能解码出ads_count
为1的结果。
- 问题排查 -
鉴于我们的代码已经过测试,能够正确处理ads_count
,初步怀疑问题出在媒体的请求上。通过录制并保存请求至req.pb
文件,使用protoc --decode-raw
命令尝试解码,但结果难以解读。随后,利用已知的.proto文件进行更精确的解码,发现了一个未在定义中出现的字段10: 3
,这提示我们双方的.proto文件存在差异。
- 解决方案 -
经过与媒体的沟通确认,他们确实曾在ads_count
之前添加了另一个字段。调整我们的.proto文件,将ads_count
的序号改为10,重新编译并测试,问题得以解决。
- 编码机制简介 -
为加深理解,这里简要介绍protobuf的编码机制。每个字段在编码时会根据其序号和类型生成一个键值对,其中键由字段序号和类型标识组成,值则是实际的数据。例如,对于简单的Test1
类型:
message Test1 { optional int32 a = 1;}
当给a
赋值150并序列化时,会生成三个字节的数据:08 96 01。首字节08的二进制形式为0000 1000,其中最低三位000表示这是一个varint类型,接下来四位0001表示字段序号1,最高位0表示这是最后一个字节。后续两个字节96 01则表示了实际的值150,遵循varint的小端存储规则。
- 关于负整数的编码 -
对于负整数,protobuf采用了特殊的ZigZag编码方式,以确保较小的负数也能被高效编码。这种方式通过交替使用正负数来表示连续的整数,从而实现更紧凑的编码。
- 结语 -
通过这次经历,我们不仅解决了实际问题,还深入理解了protobuf的工作原理。更多关于字符串、嵌套类型及数组的编码细节,建议参考官方文档。
最后,留给大家一个小练习:下面的编码数据表示了什么?12 03 36 36 36
推荐阅读:
- 《程序员面试指南:从面试官角度看问题》
- 《Go服务性能优化案例:内存激增问题的解决》
- 《技术交流中的日志问题与沟通技巧》
- 《C++面试难题解析》