Thrift枚举对象兼容性问题


最近在一个基于Thrift框架的RPC服务中碰到一个枚举值的兼容性问题。
简单描述如下:
有一个位置定义,起初只有两个枚举值,LEFT和RIGHT。然后发布schema定义版本1.0,客户端服务引用version = 1.0。

1
2
3
4
5
// commom.thrift
enum Position {
LEFT =0,
RIGHT =1,
}

经过一段时间,Position需要扩展,增加UP和DOWN,发布schema定义版本2.0。

1
2
3
4
5
6
7
// commom.thrift
enum Position {
LEFT =0,
RIGHT =1,
UP =2,
DOWN =3,
}

此时,客户端还在使用schema=1.0版本。若服务端返回了Position.UP或者Position.DOWN数据到客户端,客户端是不识别这两个枚举值的。问题就这样产生了,客户端有可能因为没有对未知数据进行兼容处理而直接报错异常了。

实际上,有几种设计是违反向后兼容原则的,上述情况就是典型的一种,即向枚举类型中新增枚举值。这种场景下,原则上要求所有客户端均先于服务端进行升级到最新版本。鉴于分布式服务应用场景,要求所有客户端都统一升级,是几乎不可能实现的。

针对向已有枚举类型中新增枚举值的情况,Thrift和Protobuf各有一些处理:

  1. Thrift:针对不认识的枚举值,默认返回null(通过findByValue实现)
  2. Protobuf:针对不认识的枚举值,默认返回枚举类型的第一个值(或者返回default value,如有)
    • 注: 所以Protobuf定义的schema enum一般都会设第一个值为UNKNOWN以兼容低版本客户端

在阿里巴巴的Java编码规范中也有相关规则:强制“接口返回值不允许使用枚举类型或者包含枚举类型的POJO对象”

Thrift/Protobuf协议本身对新增枚举值的处理没有问题(至少在反序列化方法中没有简单粗暴地直接抛出异常),但对业务层的处理及使用提出了一定的要求,即业务层有可能读到null(Thrift)或者UNKONWN类型枚举(Protobuf),需要做一定的防御性判断。

进一步阅读资料:

  1. thrift-versioning-doc
  2. Updating A Message Type