又是一年中秋时,离10.1比较近的原因,没有回家。这么多年,第一次在异地他乡独过中秋。三天时间基本状态就是吃吃吃、睡睡睡、玩玩玩;三天时间即将过去,不免心生愧疚,吃完饭回来已经晚上九点,还是水文一篇,说说服务兼容性方面的知识点。
说到兼容性,首先想到的是接口的兼容性设计,设计方式多种多样,一般情况下对于Http的接口协议设计通常通过在请求路径、头部进行版本号标识,不同的标识进入不同的逻辑;如果一开始就明确版本号策略设计,并考虑整个服务版本号统一,一般后期问题不大。
为什么?因为请求接口的版本相对比较简单,保证接口调用路径不会出现问题即可。真正复杂的部分在于如何做到代码逻辑兼容、数据存储结构兼容。
代码逻辑和数据存储结构的兼容性这两块关系紧密,通常数据都是由代码逻辑生成和读取处理。但是处理和设计方式又稍微有所区别。
关于代码逻辑的兼容性设计要做到两点:
-
向后兼容:新代码可以处理旧代码产生的数据;
通常情况下这个比较简单,在编写新逻辑的时候都会考虑旧数据的兼容性;
-
向前兼容:旧代码可以处理新代码产生的数据;
代码逻辑要能够处理之前老版本的请求逻辑。怎么理解呢?比如服务端升级了,客户端不一定跟随升级,这个时候可能还会出现老的请求数据结构,代码逻辑应该能够处理。
另外一种情况更为常见,抛开客户端不谈,服务端在滚动升级过程中,一般会出现新老两个服务共存的场景,这个时候,老服务可能会读到新服务产生的数据,如果不加以兼容,一定会造成不必要的流量损失。
还有一种情况,非常重要,当你发布完成之后,其它功能出现问题,不得不回退版本,但是数据已经生成,如果不做向前兼容性设计,肯定是连环炸了。
为了更清楚的说明问题,我说个简单的示例:
-
如下这个KV数据结构作为value值存储在redis中
{
"ip": "11.12.18.79",
"rule": "open|closed|20210921"
}
-
后来你又在KV数据结构value中拼接了一个「1」
{
"ip": "11.12.18.79",
"rule": "open|closed|20210921|1"
}
-
因为这个「1」是服务端拼接的,于是开始解析,这时,一定记得兼容老版本不包含「1」的数据结构,要不然,一定出个空指针转换异常。具体原因不在赘述了,上文已经说过了。
-
处理方式不也非常简单,按照数量判等进行处理即可;看似无懈可击,实则蕴藏巨大风险。比如刚开始rule字段是3个元素,按照3个元素处理,后来变成了4个,你按照四个进行处理;如果这时老版本服务产生了一个3个字段的数据结构怎么办呢?或者说新版本服务产生了4个字段的数据结构,老版本如何处理呢?
-
有点迷糊了,不过处理方式也很简单,不要太严谨,兼容性设计中有个词叫宽进严出,就是这个意思。你把等于改成大于等于即可。
大傻子,为啥要用这种数据结构,直接全部json结构化的数据结构不就完事了(这种基本是比较优雅的解决问题方式),说着简单,设计者初衷数据结构不会发生特别大的变化,对于每天上亿级的数据,可以节省更多的存储。
谁知道唯一不变的就是变化。
这时你不要尝试在json中添加版本号,这种骚操作根本解决不了问题。有兴趣的话,可以去参考下etcd的版本号设计。
假设已经解决了兼容性问题,其实还会存在其它问题,比如后来的rule新增了一个1,这个新增的1,如果被老版本处理,直接就舍弃掉了,这种问题基本很难避免,如果想要解决要有相应办法,可以考虑使用Map、JSONObject等数据结构,代码做出兼容性处理。
具体可以根据业务需要做出权衡,另外关于存储方面还是建议使用前后兼容的结构化数据结构,比如JSON,如果考虑到空间占用的问题,也可以自定义数据类型、长度、内容;当然也可以使用thrift、 protobuf等紧凑数据格式,但这种数据结构相对可读性不会太好。这里给个美团的存储结构最佳实践,需要的可以参考:
https://tech.meituan.com/2015/02/26/serialization-vs-deserialization.html
。
今天就这样吧,假期最后一天了,轻松一点!
推荐
原创不易,随手关注或者”在看“,诚挚感谢!