Protobuf零值陷阱:当默认值成为业务逻辑的隐形杀手

美股有三个交易时段,分别是:盘前、盘中、盘后;接口推送数据还是数值增量的逻辑(尽可能的节约带宽),仅在第一次发送全量,第二次开始所有字段都是增量推送逻辑。

为什么不用最优方案?牵扯到不同项目组,有些都已经上线多年。我方属于新对接,所以只能尽量兼容。

一系列问题

单从摘要上看,可能还觉得没什么问题,问题带入组内的系统架构,带出来一系列问题。刚处理上一个问题,又遇到了新的问题,这个问题是由于之前的问题引起的。

无法识别交易时段

已知盘中阶段定义为在 protobuf 中定义为 0,但是在收到数据的时候由于是增量推送,业务方无法有效识别这个是默认值,还是真实的业务值。

通俗理解:每次收到 0,无法判断这个 0 是新行情设置的值,还是 protobuf 的默认值

引入 optional

Since protobuf release 3.15, proto3 supports using the optional keyword (just as in proto2) to give a scalar field presence information

组内的通讯协议是基于 protobuf 的,但是由于历史原因,选择的版本比较老,并没支持 optional 关键字。懂的都理解,由于从底层开始引入 protobuf,项目底层通过静态库的方式发布,导致整个编译链路都需要升级,这个成本是非常高的。

gcc 版本问题

好不容易想了方案,底层发布两个不同的版本,尽可能的控制 protobuf 新版本的编译依赖传播。但是在编译的时候,发现 gcc 版本太低,不支持 protobuf 的新特性。

组内常用的服务器类型:centos7、centos8。centos7 默认的 gcc 版本是 4.8,centos8 默认的 gcc 版本是 8.3。由于 protobuf 的新特性需要 gcc 版本在 7.4 以上,所以 centos7 无法支持。

Bug 82461 - [7 Regression] Temporary required for brace-initializing (non-literal-type) member variable

最后折腾一圈,相关服务的部署、编译服务器都挪到 centos8 上,解决了这个问题。

合理的枚举

回顾整个问题,其实有一个更简单、高效的解决方案:调整枚举的定义,从 1 开始编号,而不是从 0 开始。这样就能有效区分默认值和业务值,避免上述一系列麻烦。

为什么从 1 开始更合理?

protobuf 中,枚举类型的默认值固定为 0。如果我们将有意义的业务值定义为 0(比如“盘中”),在增量推送时,业务方无法判断收到的 0 是业务值还是未设置的默认值。而如果将枚举从 1 开始定义,0 可以保留为一个无意义的默认值或“未知”状态,问题迎刃而解。

建议的实践:

在设计 protobuf 枚举时,始终将 0 定义为无意义的默认值(如 UNKNOWNRESERVED)。 将实际业务值从 1 开始分配,确保与默认值 0 区分开。

通过这一小调整,我们不仅解决了交易时段识别的问题,还为未来的协议设计提供了一个宝贵的经验教训。

金融IT程序员的瞎折腾、日常生活的碎碎念
使用 Hugo 构建
主题 StackJimmy 设计