一、前言

笔者曾负责vivo应用商店服务器开发,有幸见证应用商店从百万日活到几千万日活的发展历程。应用商店客户端经历了大大小小上百个版本迭代后,服务端也在架构上完成了单体到服务集群、微服务升级。

下面主要聊一聊在业务快速发展过程中,产品不断迭代,服务端在兼容不同版本客户端的API遇到的问题的一些经验和心得。一方面让团队内童鞋对已有的一些设计思想有一个更彻底的理解,另一方面也是希望能引起一些遇到类似场景同行的共鸣,提供解决思路。

二、通用解决方案

应用商店客户端迭代非常频繁,发布新的APP版本的时候,势必导致出现多版本,这样服务端就会导致多个不同的客户端请求。强制用户升级APP,可能会导致用户流失,因此采用多版本共存就是必须的。以下是业界讨论过的的一些SOA服务API版本控制方法参考[1]。在实际开发中原则上离不开以下三个方案。

方案一:The Knot 无版本——即平台的API永远只有一个版本,所有的用户都必须使用最新的API,任何API的修改都会影响到平台所有的用户。(如下图1)

方案二:Point-to-Point——点对点,即平台的API版本自带版本号,用户根据自己的需求选择使用对应的API,需要使用新的API特性,用户必须自己升级。(如下图2)

方案三:Compatible Versioning——兼容性版本控制,和The Knot一样,平台只有一个版本,但是最新版本需要兼容以前版本的API行为。(如下图3)

(引用自:The Costs of Versioning an API

简单分析,The Knot只维护最新版本,对服务端而言维护有一定简化了,但是要求服务使用者及时适配最新的版本,这种做法不太适用用户产品,目前内部服务比较适用。Point-Point针对不同客户的版本提供独立的服务,当随着版本的增加开发和运维的维护成本会增加,这种在后面我们面对“协议升级”的时候有使用。

方案三应该是最常用的情况,服务端向后兼容。后面案例也主要采用这种思想,具体的做法也是有很多种,会结合具体的业务场景使用不同策略,这个会是接下来讨论的重点。

三、具体业务场景面临的挑战和探索

3.1 The Knot 无版本和Point-to-Point模式的应用场景

上图是我们应用商店迭代变化的一个缩影,业务发展到一定阶段面临以下挑战:

1)业务发展前期,作为服务提供方,服务端不仅要支撑多个版本应用商店客户端,同时服务于软件侧的PC助手;

2)产品形态变化多样,服务端接口变更和维护面临多版本客户端兼容的挑战;

3)架构逻辑上,服务端采用早期传统架构,开发和维护成本比较高;服务端与客户端进行交互的协议优化升级;以及服务拆分势在必行。

所以服务端协议、框架升级以及公共服务拆分是首要解决的方向。改造经历了两个过程:

  • 阶段一新版本新的接口一律采用新的JSON协议;已有功能接口进行兼容处理,根据客户端版本进行区分,返回不同协议的格式内容。

  • 阶段二随着业务迭代,新的版本商店依赖的所有接口都完成了协议升级后,为了提升服务的稳定性,旧的协议性能无法明显提升,一方面升级后端架构和框架,提升开发效率和可维护性。同时拆分和独立新的工程,实现历史工程只提供给历史版本使用。我们针对大流量高并发、以及基础服务场景比如首页、详情、下载进行独立服务独立拆分。同时也提取一些公共的内部RPC服务,比如获取应用详情、过滤服务等。

经过改造,服务端架构如上图所示。

1)至此Old-Service后续只用进行相应的维护工作即可,对应Point-to-Point版本。

2)内部的RPC服务由于只提供内部服务,服务端和客户端可以随时同步升级,只要维护最新的版本就可以,采用The Knot模式。这里需要注意的是服务的升级需要注意保持向下兼容,在扩展字段或者修改字段的时候需要特别小心,不然可能在服务升级的时候会引起客户端调用的异常。

3.2 Compatible Versioning:兼容性版本控制

兼容性版本控制应该是最常见的版本控制方式,特别是在C/S架构当中,具体的兼容性版本不同的策略总结有API版本、客户端版本号、功能参数标志等。

场景一:API版本号控制

随着互联网发展的,用户体验要求也是越来越高,产品形式也会随之每年有不一样的变化。除了避免审美疲劳外,也是在不断探索如何提升屏效、点击率和转化。就拿应用商店首页列表举例。

应用列表在形态上经历过单一的应用双排 -> 单排  -> 单排+穿插的布局。内容上也经历了不同商业化模式、人工排期到算法等演进。

每个版本接口内部逻辑变化是十分大的,有明显差异。如果只是简单在service层根据版本进行判断处理,会导致处理逻辑会变得异常复杂,并且还可能导致对低版本产生影响。同时商店首页是十分重要的业务场景,结合风险考虑,类似这样对场景,在接口URL上新增版本字段,不同对版本使用不同的值,在控制层根据不同的版本进行不同的处理逻辑会更加合理,简单有效。具体策略也有比如在URL上新增接口版本字段/{version}/index、请求头携带版本参数等。

场景二:客户端版本号控制

类似首页列表,商店的穿插Banner也经历了多个版本的迭代。如下图所示。这些穿插样式都是在不同版本下出现的,在样式布局,支持跳转能力等方面各个版本的支持程度不一样,接口返回时需要进行相应的处理适配、过滤等处理。

这类场景如果采用场景一的方案升级新的接口也能够解决,但是会存在大量重复代码,而且新增接口对于客户端接口改造、特别是一些接口路径会影响到大数据埋点统计,也是有比较高的沟通和维护成本在里面。

为了提升代码复用性。使用客户端版本号控制是首选考虑策略。但是需要注意,如果只是简单的在代码层面根据客户端版本号进行判断,会存在以下问题需要考虑:

1)代码层面会存在各种判断,造成的代码可读性差,有没有更加优雅的方法;

2)存在一个客观情况。那就是客户端的版本号是存在不确定性的。由于客户端采用火车发布模式 参考[2],多版本并行开发,导致版本号存在变动、版本跳跃不连续的情况时有发生,也给服务端开发带来了不少困扰。

如何思考解决这些问题呢?其实对于不同的产品形态涉及的一些资源或者产品模块本身出现在不同的迭代周期,可以认为他们具备了版本或者时间的属性。站在程序员视角,把某个资源支持对应的客户端版本作为这个资源对象的一个成员属性。每种资源具有这种属性后,也有相应的逻辑行为来对应成员方法---根据属性进行过滤。这样的设计赋予资源了属性和行为后,资源具备了统一的、灵活的过滤能力,而不再是简单的硬编码根据版本进行if-else判断。

有了方案后,实施起来就比较容易了。开发分配资源ID,并且设置对应支持客户端版本范围。过滤逻辑统一到资源对象。

代码层面可以将过滤逻辑统一封装到一个工具类(示例代码),在各个业务接口返回进行过滤。更加优雅的方案是建立统一的资源上层类,封装资源过滤方法,所有资源位的资源对象实现该上层类,统一在获取资源逻辑完成过滤能力。

场景三:新增功能标识参数

应用商店业务主要提供用户发现和下载新应用、更新手机已安装的应用。商店有增量更新可以减小更新包体积,因此也叫省流量更新,有效提升用户体验。前期我们使用开源的增量算法,但是发现该算法在部分机器合成拆分包会耗时很长,甚至引起crash。

于是项目组寻求更加高效拆分算法。类似在这些已有接口的进行功能增强的场景,除了提供新的API或者内部简单通过客户端版本判断进行扩展外,有没有更好的方案呢?因为除了这些方案已知的弊端外,需要从长远考虑,比如前面提到的算法,后续还会不会存在升级的可能,下载接口会不会有更多能力的增强。

结合上面思考,在原来接口基础上新增标志参数字段,表示该请求发出的客户端支持的能力。为了后续扩展,字段类型为整数值,不只是简单的boolean,服务端通过位运算完成判断逻辑。客户端也摆脱某个功能与版本的强一致性,不用去记录某个版本具有某种能力。

四、关于接口设计的更多思考

最后补充一些踩过的坑和反思。服务端在提供接口时,不能仅仅关注接口的实现,更多的时候需要关注接口的使用方,他们使用的场景、调用时机等等。否则开发在对接口问题排查、维护花费的时间会比实际开发的耗时要多上好几倍。

1)场景化:具体到什么是场景化呢,拿商店客户端的帮助用户检测手机安装的应用版本是否最新的服务举例,检测时机是存在不同的场景的,比如用户启动、用户切换wlan环境、定时检测等。当需要进行精细化分析,哪些请求是有效的,哪些会引起集中请求时,这个时候如果请求上没有场景区分,那么分析将无从下手。所以在与客户端沟通接口设计时,请带上场景这个因素。接口设计上可参考如/app/{scene}/upgrade,定义好各个场景名称,在路径上带上具体的场景,这样对线上不同来源请求量级、问题分析都会有很大好处。

2)鉴权和服务隔离:除了场景需要考虑外,接口调用在分配时做好记录和鉴权以及服务隔离。比如商店的部分接口服务不仅提供给客户端,同时也会提供给手机系统应用调用。目前vivo上亿的存量用户体量,这里要十分小心,系统应用的调用量控制不当,并发可比商店本身要大的多。首先前期与服务调用方评估沟通、做好设计,避免出问题。即使在出问题时,也要有机制能够快速发现问题、能够分析出问题的来源,降低问题带来的损失。

至此上面解决问题的思路,都与具体业务以及背景有一定关系。随着技术不断迭代和发展,在移动端APP页面动态性,目前业界也有了更多高效的技术方案,比如谷歌的Flutter、Weex等。这些技术能够实现灵活扩展,多端统一,性能也能够接近native。不仅减少了客户端发版频次,也减少了服务端兼容性处理成本。目前我们vivo也有团队在使用和实践。

技术不断更迭,没有最好的方案,只有最适合的方案。开发过程中不仅满足当前实现,更多的是考虑到后续扩展性和可维护性。开发不能一味追求高端技术,技术最终服务于业务,坚持长期主义,效率至上

五、参考资料

1、The Costs of Versioning an API

2、敏捷开发,火车发布模式

服务API版本控制设计与实践的更多相关文章

  1. atitit.基于http json api 接口设计 最佳实践 总结o7

    atitit.基于http  json  api 接口设计 最佳实践 总结o7 1. 需求:::服务器and android 端接口通讯 2 2. 接口开发的要点 2 2.1. 普通参数 meth,p ...

  2. php后台对接ios,安卓,API接口设计和实践完全攻略,涨薪必备技能

    2016年12月29日13:45:27    关于接口设计要说的东西很多,可能写一个系列都可以,vsd图都得画很多张,但是由于个人时间和精力有限,所有有些东西后面再补充   说道接口设计第一反应就是r ...

  3. Restful API的设计与实践

    Restful这个名称应该很多人都不陌生,但是我发现不少人对Restful存在或多或少的理解偏差,其中不泛比较厉害的程序员,所以有必要为Restful来“正名”. Restful是一种软件架构风格,设 ...

  4. 唯品会API网关设计与实践--转

    原文地址:https://609518.kuaizhan.com/86/70/p4108366952248f 刘璟宇Leo 唯品会资深研发工程师,在大型高性能分布式系统设计和开发方面有丰富的经验.目前 ...

  5. 微服务设计 - api版本控制

    要描述了几种API版本控制的方法.用户可以查询原始的API,或者添加定制的头文件来接收特定的版本.如果应用程序收到一个重大修订,将URI修改为V2.在进行迭代改进时,将创建与更改日期相一致的端点,并允 ...

  6. [转] 阿里研究员谷朴:API 设计最佳实践的思考

    API是软件系统的核心,而软件系统的复杂度Complexity是大规模软件系统能否成功最重要的因素.但复杂度Complexity并非某一个单独的问题能完全败坏的,而是在系统设计尤其是API设计层面很多 ...

  7. .net core实践系列之短信服务-Api的SDK的实现与测试

    前言 上一篇<.net core实践系列之短信服务-Sikiro.SMS.Api服务的实现>讲解了API的设计与实现,本篇主要讲解编写接口的SDK编写还有API的测试. 或许有些人会认为, ...

  8. RESTful API 设计最佳实践

    背景 目前互联网上充斥着大量的关于RESTful API(为了方便,以后API和RESTful API 一个意思)如何设计的文章,然而却没有一个"万能"的设计标准:如何鉴权?API ...

  9. SOA实践之基于服务总线的设计

    在上文中,主要介绍了SOA的概念,什么叫做“服务”,“服务”应该具备哪些特性.本篇中,我将介绍SOA的一种很常见的设计实践--基于服务总线的设计. 基于服务总线的设计 基于总线的设计,借鉴了计算机内部 ...

随机推荐

  1. 第十一章 Dockerfile安装Jenkins-2.249.3-1.1

    一.安装Docker Docker部署Jenkins前提已经安装Docker,这边脚本安装Docker. #1.编写Docker安装脚本 [root@ip-10-0-12-212 ~]# vim In ...

  2. iOS路由最佳选择是什么

    背景 记得四年前iOS路由开始盛行,当时比较有名的是蘑菇街的,后来CTMediator写了几篇文章把蘑菇街批的体无完肤,导致我后来写新项目用了CTMediator,那一堆组件创建的叫一个酸爽啊!再后来 ...

  3. JVM详解(六)——对象的实例化、内存布局与访问定位

    一.对象的实例化 1.创建对象的方式 2.创建对象的步骤 脑图:https://www.processon.com/view/link/61701a927d9c087040525226 3.对象属性赋 ...

  4. python3去除行号

    问题:在复制一些代码时会同时复制每行的行号,删除比较麻烦,所以利用python3本身的代码进行一键删除. # 导入re 模块来使用正则表达式 import re """去 ...

  5. python字符串调用举例

    以如下打印为例: my name is tom and my age is 12 方式一:字符串格式化表达式 name = 'tom' age = 12 print("my name is ...

  6. 跟着老猫一起来学GO,环境搭建

    老猫的GO学习系列博客已经正式发车了,相信大家以前学习一门编程语言的时候也有经验,咱们一般都是从环境开始,在此呢,大家也跟着老猫从最开始的搭建环境开始. GO语言的安装 首先呢,我们开始需要下载GO语 ...

  7. 如何洗白xi校长?(初稿)

    看看咱们太子殿下,谁还敢黑全世界最好的太子殿下 我们不如来考虑一下如何给校长洗白. 第一当然是买断热搜了.买断热搜可以阻止消息进一步传播.当然这种操作学校再8月18日晚就已经做过了.8月18日该条消息 ...

  8. C语言基础知识总结大全

    1.入门程序 #include <stdio.h> int main() { printf("Hello World!"); return 0; } 2.数据类型 数据 ...

  9. 零基础入门c语言函数之递归函数

    今天来总结一下关于递归函数的使用方面的问题. 递归函数就是在函数使用的时候自己调用自己,层层调用,来实现你想要的功能. 有两个最常用的例子,我们来写一下. (1)计算阶乘 #include int f ...

  10. set prompt = "任意匹配字符" 当前目录详解

    转载:https://blog.csdn.net/alexdream/article/details/6865730 研究了两天的FreeBSD,总是感觉输入提示符那里怪怪的,而且默认的提示符还不带显 ...