前言
突然发现已经有很久没有登录过csdn了,工作后一直很忙,没有什么时间来写博客,平日学习、踩坑往往都是自己简单记录一下,但实际上经常写博客还是有必要的,一方面可以加深自己的理解,另一方面,也可以不断强化文档书写能力。
背景
由于公司部门较多,语言也各不相同,跨部门协作基本都是通过gRPC协议(proto文件足够通用和认可),因此公司有一个统一API仓库,暂且叫作common。对于各业务方来说,只需要保证自己项目内的proto文件与common仓库自己路径下的proto文件一致即可。
早期主站各业务部门代码都存放在一个仓库内,且同时使用一份go.mod文件,此处暂且叫做main仓库。后来,有一位同学将go.mod中的某个依赖升级,并合入master,而那个依赖的升级引入了一个第三方bug,导致各个业务方都出现了问题,于是,部分组为了避免此类问题的再次发生,以组为单位,将自己组的代码拆到一个独立的小仓中,暂且叫做gateway仓库。
由于早期代码都在一个仓库中,调用同部门其他业务方的gRPC接口就可以不通过common,而是直接在项目内引用业务方的proto,后续拆分小仓时也没有进行依赖梳理,导致每修改一次proto文件,common、main、gateway三个仓库全部需要修改一次,这也为后面的坑埋下伏笔。
按照上述的流程,在开发过程中往往会先更新common中的proto文件,使得协作方可以与你同步开发。但有一个需求,因为开发过程中产品发现其他问题,不能上线,因此服务端代码也没有合入master,此时,common库与main、gateway库proto文件出现不一致。之后又新增了一个字段,但是字段类型都是string,于是线上运行一段时间没有问题。
common:
message Item {
int64 id = 1;
// ...
string from = 33;
}
gateway与main:
message Item {
int64 id = 1;
// ...
string from = 32;
}
某一天,因为新需求需要新增一个int32的字段,在common仓库中更新后,并未发版。过了几个小时后,有业务方更新了common仓库并发版,之后发现接口失败,反馈过来,报错日志如下:
排查
初步确认server端并未发版,该字段并未对外突出,理论上不应该出现解析不出的问题。
后查看protobuf源码,使用的是1.3.2的版本,发现当出现不认识的字段时,会将该部分写入XXX_unrecognized内,并且会继续传递给 client 端,以至于出现新的字段时,只需要client端发版,server端不发版也可以解析新增字段,但因之前造成的顺序不一致,导致新增字段解析失败,报了failed to unmarshal the received message proto的错误。
解决
将问题版本禁止编译,将三个仓库的proto改成一致。后续推进各业务方统一使用common中的proto,下掉main仓库中的proto,渐少维护的仓库。