关于服务兼容性设计一点思考

  • Post author:
  • Post category:其他


又是一年中秋时,离10.1比较近的原因,没有回家。这么多年,第一次在异地他乡独过中秋。三天时间基本状态就是吃吃吃、睡睡睡、玩玩玩;三天时间即将过去,不免心生愧疚,吃完饭回来已经晚上九点,还是水文一篇,说说服务兼容性方面的知识点。

7bfcccbcd17bbde23e9232799259ad82.png

说到兼容性,首先想到的是接口的兼容性设计,设计方式多种多样,一般情况下对于Http的接口协议设计通常通过在请求路径、头部进行版本号标识,不同的标识进入不同的逻辑;如果一开始就明确版本号策略设计,并考虑整个服务版本号统一,一般后期问题不大。

为什么?因为请求接口的版本相对比较简单,保证接口调用路径不会出现问题即可。真正复杂的部分在于如何做到代码逻辑兼容、数据存储结构兼容。

代码逻辑和数据存储结构的兼容性这两块关系紧密,通常数据都是由代码逻辑生成和读取处理。但是处理和设计方式又稍微有所区别。

关于代码逻辑的兼容性设计要做到两点:

  • 向后兼容:新代码可以处理旧代码产生的数据;

通常情况下这个比较简单,在编写新逻辑的时候都会考虑旧数据的兼容性;

  • 向前兼容:旧代码可以处理新代码产生的数据;

代码逻辑要能够处理之前老版本的请求逻辑。怎么理解呢?比如服务端升级了,客户端不一定跟随升级,这个时候可能还会出现老的请求数据结构,代码逻辑应该能够处理。

另外一种情况更为常见,抛开客户端不谈,服务端在滚动升级过程中,一般会出现新老两个服务共存的场景,这个时候,老服务可能会读到新服务产生的数据,如果不加以兼容,一定会造成不必要的流量损失。

还有一种情况,非常重要,当你发布完成之后,其它功能出现问题,不得不回退版本,但是数据已经生成,如果不做向前兼容性设计,肯定是连环炸了。

为了更清楚的说明问题,我说个简单的示例:

  1. 如下这个KV数据结构作为value值存储在redis中

{
  "ip": "11.12.18.79",
  "rule": "open|closed|20210921"
}
  1. 后来你又在KV数据结构value中拼接了一个「1」

{
  "ip": "11.12.18.79",
  "rule": "open|closed|20210921|1"
}
  1. 因为这个「1」是服务端拼接的,于是开始解析,这时,一定记得兼容老版本不包含「1」的数据结构,要不然,一定出个空指针转换异常。具体原因不在赘述了,上文已经说过了。

  2. 处理方式不也非常简单,按照数量判等进行处理即可;看似无懈可击,实则蕴藏巨大风险。比如刚开始rule字段是3个元素,按照3个元素处理,后来变成了4个,你按照四个进行处理;如果这时老版本服务产生了一个3个字段的数据结构怎么办呢?或者说新版本服务产生了4个字段的数据结构,老版本如何处理呢?

  3. 有点迷糊了,不过处理方式也很简单,不要太严谨,兼容性设计中有个词叫宽进严出,就是这个意思。你把等于改成大于等于即可。

大傻子,为啥要用这种数据结构,直接全部json结构化的数据结构不就完事了(这种基本是比较优雅的解决问题方式),说着简单,设计者初衷数据结构不会发生特别大的变化,对于每天上亿级的数据,可以节省更多的存储。

谁知道唯一不变的就是变化。

这时你不要尝试在json中添加版本号,这种骚操作根本解决不了问题。有兴趣的话,可以去参考下etcd的版本号设计。

假设已经解决了兼容性问题,其实还会存在其它问题,比如后来的rule新增了一个1,这个新增的1,如果被老版本处理,直接就舍弃掉了,这种问题基本很难避免,如果想要解决要有相应办法,可以考虑使用Map、JSONObject等数据结构,代码做出兼容性处理。

具体可以根据业务需要做出权衡,另外关于存储方面还是建议使用前后兼容的结构化数据结构,比如JSON,如果考虑到空间占用的问题,也可以自定义数据类型、长度、内容;当然也可以使用thrift、 protobuf等紧凑数据格式,但这种数据结构相对可读性不会太好。这里给个美团的存储结构最佳实践,需要的可以参考:

https://tech.meituan.com/2015/02/26/serialization-vs-deserialization.html

今天就这样吧,假期最后一天了,轻松一点!

推荐


A Big Picture of Kubernetes


Kubernetes入门培训(内含PPT)


原创不易,随手关注或者”在看“,诚挚感谢!



版权声明:本文为u012516914原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。