protobuf/thrift解析enum导致的大坑

文章讲述了在升级服务器时,由于PB和Thrift的枚举类型不向后兼容,导致服务端新增枚举值后,客户端无法正确解析,进而产生严重生产事故的问题。PB会返回默认值,而Thrift则返回null,两者都可能导致错误的处理逻辑或异常。作者建议避免在接口返回中使用enum类型,以防止类似问题的发生。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景:

今天升级服务器时发生了一个生产事故,原因是因为pb的枚举类型无法向后兼容引起的,服务端写到pb的数据和客户端解析出来的pb竟然不一样,大惊失色之下写下这篇关于pb/thrift解析enum不当导致的大坑的血泪文章

事情来龙去脉:

首先一开始服务器和客户端的pb定义如下所示,

enum  RetCode{

  SUCC = 0,

  MYSQL_ERROR=1,


}

message Rsponse {

 optional RetCode code = 1 [default = SUCC];}

}

使用过程中一切正常,直到有一天,业务变动,新增了一种错误类型,服务端的接口就首先把pb的定义改成了

enum  RetCode{

  SUCC = 0,

  MYSQL_ERROR=1,

  REDIS_ERROR=2

}

message Rsponse {

 optional RetCode code = 1 [default = SUCC];}

}

由于没有意识到要通知客户端,客户端依然使用

enum  RetCode{

  SUCC = 0,

  MYSQL_ERROR=1,

}

message Response {

 optional RetCode code = 1 [default = SUCC];}

}

这个少了REDIS_ERROR=2这个枚举值的pb解析服务端的返回值,我们发现当服务器的返回值中设置code = REDIS_ERROR时,客户端解析出来的依然是SUCC,导致本来是告诉客户端错误code的代码代码变成了正常的Code,导致后续的处理逻辑出错,这里主要的原因在于pb的enum类型其实不是向后兼容的,使用旧的enum来解析具有新值的enum值时,只会得到一个pb定义中的默认值,而且不会有任何错误的日志或提示

举一反三

其实不仅仅pb对enum的处理存在问题,thrift对于enum的处理也是存在问题的,还是使用上面的枚举值作为例子
服务端的thrift文件使用:

enum  RetCode{

  SUCC = 0,

  MYSQL_ERROR=1,

  REDIS_ERROR=2

}

客户端的thrift文件使用:


```java
enum  RetCode{

  SUCC = 0,

  MYSQL_ERROR=1,

}

当服务端设置REDIS_ERROR的值时,Response的code对象返回的值是null,这对于那些使用resp.getCode().getValue()来获取枚举值的value的客户端,这种情况下就会抛出NullPointerException异常,那时候客户端一定很奇怪,我明明没有改动任何东西怎么会有空指针异常?这个时候服务端还一无所知,因为在服务端是看不到任何错误的,直到客户端的开发找上门来告诉服务端,说你这个字段返回null了,服务端才会意识到可能几天前新增了enum枚举值,并在最近几天设置了新的枚举值导致的。

总结:

由以上pb和thrift对enum新增枚举值的不同处理方式可知,不论是pb还是thrift,他们都不能很好的处理新增enum的枚举值的场景,对于pb他会返回一个默认值,而这个默认值完全就是错的。因为服务端那里设置的是新的enum枚举值,而对于thrift来说,对于不认识的enum枚举值,他会返回null,相对来说好点,但是一般的客户端很少判断一个enum字段还能是null的情况,所以也会导致客户端抛出NullPointerException,所以还是约定:接口返回的字段不要使用enum枚举类型,返回的POJO里面的字段也不要使用枚举类型,直接使用int或者byte就好了.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值