protobuf/thrift解析enum导致的大坑

背景:

今天升级服务器时发生了一个生产事故,原因是因为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就好了.

你可能感兴趣的:(数据结构,redis,数据库,java)