写点什么

IM 开发干货分享:IM 客户端不同版本兼容运行的技术思路和实践总结

作者:JackJiang
  • 2023-04-28
    江苏
  • 本文字数:4395 字

    阅读完需:约 14 分钟

IM开发干货分享:IM客户端不同版本兼容运行的技术思路和实践总结

本文由巩鹏军分享,原题“IM 兼容性基建”,本文有修订。


1、引言一个成熟的 IM 成品,在运营过程中随着时间的推移,会发布不同的版本,但为了用户体验并不能强制要求用户必须升级到最新版本,而服务端此时已经是最新版本了,所以为了让这些不同客户端版本的用户都能正常使用(尤其 IM 这种产品,不同版本可能通信协议都会有变动,这就更要命了),则必须要针对不同客户端版本的兼容处理。


本文将基于笔者的 IM 产品开发和运营实践,为你分享如何实现不同 APP 客户端版本与服务端通信的兼容性处理方案。


学习交流:


  • 移动端 IM 开发入门文章:《新手入门一篇就够:从零开发移动端 IM》

  • 开源 IM 框架源码:https://github.com/JackJiang2011/MobileIMSDK(备用地址点此)


(本文已同步发布于:http://www.52im.net/thread-4202-1-1.html


2、关于作者


巩鹏军:专注移动开发十多年,热爱即时通讯技术。个人微信公众号:“巩鹏军”。


作者在即时通讯网分享的另一篇《知识科普:IM 聊天应用是如何将消息发送给对方的?(非技术篇)》,感兴趣的读者也可以看看。


3、一个 App 时怎么办?提示:“一个 App”指的是同一个 IM 服务端,只服务于一个特定的 IM 产品。


首先想到的就是直接使用 App 版本号判断新老版本并进行兼容处理。


如下图所示:


一般来说,不同的 IM 客户端(如 iOS、Android、Windows、Mac)都是同步迭代,多端发版时间一致,App 版本号也一样。


所以用跨多端的 App 版本号可以很容易地让服务端只用写一遍判断和兼容逻辑。


示例:假设从 V2.1.0 开始应用红包消息,那么判断客户端是否支持红包的逻辑就很简单。


伪代码如下:


booleanisSupportRedEnvelop(String appVersion) {


returngte(appVersion, "2.1.0");


}


附:版本号比对逻辑(未充分考虑异常情况):


List<Integer> toNums(String version) {


Matcher matcher = Pattern


.compile("/[0-9]+\\.[0-9]+\\.[0-9]+")


.matcher(version);
复制代码


String versionString = matcher.find()


? matcher.group(0).substring(1)


: "1.0.0";
复制代码


List<Integer> verNums = Arrays


.stream(versionString.split("\\."))


.map(Integer::valueOf)
.collect(Collectors.toList());
复制代码


returnverNums;


}


booleangte(String version, String target) {


List<Integer> appVerNums = toNums(version);


Integer appMajor = appVerNums.get(0);


Integer appMinor = appVerNums.get(1);


Integer appPatch = appVerNums.get(2);


List<Integer> targetNums = toNums(target);


Integer targetMajor = targetNums.get(0);


Integer targetMinor = targetNums.get(1);


Integer targetPatch = targetNums.get(2);


return(appMajor >= targetMajor) ||


     (appMinor >= targetMinor) ||
(appPatch >= targetPatch);
复制代码


}


4、多个 App 时怎么办?4.1 概述提示:“多个 App”指的是同一个 IM 服务端,可能作为通用服务,作为多个不同 APP 产品中的聊天模块使用的场景。


只有一个 App 时肯定是比较简单的。但现实情况是一套 IM 系统通常会用于多个业务场景,这是很普遍的现象。业界的知名 IM 产品,比如钉钉、飞书、企业微信、美团大象等都是这样。


底层逻辑大概是:IM 系统比较复杂,功能繁多而且难以实现、更难以稳定,所以一个 IM 团队维护一套 IM 系统,然后应用在多个业务场景就是最具性价比的选择了。


4.2 使用 App 版本每个业务场景都会有自己的客户端 App,每个 App 都有自己的版本号,那么根据 App 版本号判断新老版本的逻辑就不适用了(如下图所示)。


一个 App 时可以这样做兼容性判断:


booleanisSupportRedEnvelop(String appVersion) {


returngte(appVersion, "2.1.0");


}


多个 App 时的兼容性判断:


booleanisSupportRedEnvelop(String version) {


return
(app.equals("App1")&>e(version,"2.1.0"))||
(app.equals("App2")&>e(version,"2.2.3"))||
(app.equals("App3")&>e(version,"6.1"));
复制代码


}


4.3 使用 App 版本号的麻烦随着 App 的增多,需要的判断也越多,这会很麻烦,也很容易出错。


每个 App 推出新版本后,用户不可能瞬间就升级到最新版本,根据经验,每个 App 往往都会同时存在十个以上的不同版本。


这就会形成如下图所示的局面:


5、多个 App 时,可将 IM 能力提炼为一套公用代码多个 App 时的问题总结起来就是:一套服务端代码如何适应集成了不同 IM 能力的不同 App 客户端?


我们来具体举例分析一下,假设一个 IM 团队维护的 IM 相关的客户端模块有 IM Client SDK、联系人、长连接、朋友圈等四个模块(如下图所示)。


如上图所示:


1)App 1:集成了全部四个模块;2)App 2:只集成了三个模块;3)App 3:只集成了三个模块。因为三个 App 面向的客户群不同,发版节奏不同,所以各自集成的 IM 的能力也不同。


比如下面这样:


1)App 1:面向内部员工办公沟通使用的 App 1 需要功能丰富,对于稳定性和 Bug 有一定的包容性,也容易沟通和修复再发版;

2)App 2:面向客服场景,用于企业的客服专员和企业的 C 端用户沟通解决客诉问题,对于稳定性要求高,C 端用户升级率不好控制,发版节奏慢,最快只能和主业务 App 一致;

3)App 3:面向企业和 B 端供应商,比如美团和美团上的商户,京东和京东平台上的第三方商家,对于稳定性要求也比较高,B 端商家的升级率好控制一点,发版节奏也可以快一些。


从上图可以看出,因为 IM 核心能力是同一个团队维护,所以 Core 包含的多个模块的代码必然是只有一套源代码。不同 App 只是 Core 集成打包出来的产物,或者说不同 App 只是 Core 外面套了不同的壳而已,只要 Core 一样,则 App 的 IM 能力就一样(这就是本节标题所述的“多个 App 时,可将 IM 能力提炼为一套公用的代码”这个意思)。


6、给每个 App 中使用的公用代码(Core)一个版本号如上节所述,我们将 IM 能力提炼为一套公用代码(以下内容简称“Core”)。


那么,我们能不能给 Core 一个版本标识呢?


答案是肯定的:


站在 App 的角度,每个 App 相当于打上了 Core 版本标签:


7、如何正确地解读 Core 版呢?7.1 抛开 App 看 Core 版本如果不看 App 版本,只看 Core 版本标签:


7.2 从一套服务端代码看 Core 版本同一个 IM 团队,其 IM Servers 必然也是同一套代码集,不考虑部署的区别。


那么上图逻辑上等价于下图:


7.3 使用 Core 版本的兼容性判断站在 Core 的视角,多个 App 就像单个 App 类似,只是使用的版本标识不同。


具体如下:


1)单个 App 时,IM 服务端要区分不同 App 版本;2)多个 App 时,IM 服务端要区分不同 Core 版本。还拿是否支持红包的判断举例。


一个 App 时:


booleanisSupportRedEnvelop(String appVersion){


returngte(appVersion, "2.1.0");


}


多个 App 时:


booleanisSupportRedEnvelop(Integer coreVersion){


returncoreVersion >= 2;


}


通过 Core 版本号,我们可以把兼容逻辑判断简化到和单个 App 一样的简单。


8、关于 Core 版本的命名和取值关于 Core 版本号的取值,有下列可能的选项:


选项一:语义版本号 1.2.0;选项二:整数 自然数 1 2 3;选项三:整数 迭代日期 20220819 或 220819。因为 Core 版本号不用给最终用户看的,无需遵循常见的语义版本号规范。而且 Core 版本号只用于版本对比,所以整数会是一个比较好的选择,方便比较,准确可靠。


用自然数 1、 2、 3 作为 Core 版本号是可以的,每个迭代发布新的 Core 版本时递增一下就可以了。


但是考虑到有多个终端平台 iOS、Android、Windows、Mac,如果某个平台的 Core 发布后发现小 Bug 需要 HotFix,那么要递增版本号,就会挤占其它端的下一个自然数。究其原因,在于自然数是连续的,没办法在两个常规的版本间插入一个 HotFix 版本。


选项三就可以解决这个问题:因为 Core 的迭代发布日期是稀疏的,若干天后才会发布一个 Core 版本,那么当某个端需要一个 HotFix 版本时,选择 HotFix 当天的日期作为版本号即可。


总体上:多个端的主要版本号都是约定的统一的发布日期,多端一致,同时允许某个端临时 HotFix 插入一个新的版本号,保留弹性。


参考 Google 对 Android SDK API 版本的实践,我们可以把 Core 版本号命名为 core_level,取值为 Core 的发布日期的整数表示。


9、多个 App 情况下的其它版本标识 1)platform:


一套 Core,不同端在实际开发中,可能存在差异,为了针对具体端进行特定的兼容,需要知道当前是哪个端,可以约定 platform 字段表示端。取值可以是:ios、android、win、mac、linux 等。


2)App 版本号:


在 IM 相关逻辑的兼容性判断中,只需使用跨 App 的多端一致的 core_level 了。但是为了和最终用户、产品经理等沟通方便,保留 App 版本号 app_version 用于人和人之间沟通交流。core_level 主要用于研发工程师之间,还有工程师和程序之间的沟通。两者各取所长。


10、版本标识的传输方式每个 API 和每条长连接数据包都携带 Core 版本,这样服务端可以无状态得处理每一个请求。如果需要在服务端主动推送时区分目标端的版本,可以在 App 登录时将其携带的 Core 版本落库存储,然后推送时查询使用。


10.1 短连接(HTTP)HTTP 短连接通过新增 Header 字段方式传输:


curl "https://{domain}/api/v1/xxx"\


-H "platform: ios"\


-H "app_version: 8.0.25"\


-H "core_level: 220819"


10.2 长连接(Socket)长连接 SDK 通过类似 HTTP Header 的方式传输:


{


"platform":"ios",


"app_version":"8.0.25",


"core_level":"220819"


}


10.3 短转长短转长时 HTTP Header 会转换为长连接数据 body 里的 header 通过长链传递。


这样就同时存在长连接 header 和长连接 body.header 两套字段,最终以长连接 body.header 为准即可。


10.4 其它 IM 系统里的浏览器和小程序,如果可以新增 HTTP Header 则新增 Header 传输,实在没有办法可以通过 User-Agent 传输该信息,服务端优先解析 Header,没有找到时再解析 User-Agent。


服务端解析 UA 的正则表达式:


/ platform/(ios|android|mac|win|linux) app_version/([0-9].[0-9]+.[0-9]+) core_level/([1-9][0-9]+)( |$)/


以上正则表达式在线运行效果:点此查看。


11、本文小结至此,我们找到了一个适用于多个 App、多个子模块、多个功能点、临时 BugFix 的版本标识:Core 版本号,这样就可以很好地解决多 App 的 IM 能力兼容性问题。


以下是版本兼容性判断伪码:


booleanisSupportRedEnvelop(Integer coreLevel) {


returncoreLevel >= 220819;


}


12、参考资料[1] Browser vs Engine Version


[2] Node.js ABI version number


[3] Android SDK API Level


[4] 零基础 IM 开发入门(一):什么是 IM 系统?


[5] 一套海量在线用户的移动端 IM 架构设计实践分享(含详细图文)


[6] 一套原创分布式即时通讯(IM)系统理论架构方案


[7] 从零到卓越:京东客服即时通讯系统的技术架构演进历程


[8] 一套亿级用户的 IM 架构技术干货(上篇):整体架构、服务拆分等


[9] 基于实践:一套百万消息量小规模 IM 系统技术要点总结


[10] 一套十万级 TPS 的 IM 综合消息系统的架构实践与思考


[11] 从新手到专家:如何设计一套亿级消息量的分布式 IM 系统


[12] 闲鱼亿级 IM 消息系统的架构演进之路


[13] 深度解密钉钉即时消息服务 DTIM 的技术设计


[14] 一套高可用、易伸缩、高并发的 IM 群聊、单聊架构方案设计实践


[15] 企业微信的 IM 架构设计揭秘:消息模型、万人群、已读回执、消息撤回等


(本文已同步发布于:http://www.52im.net/thread-4202-1-1.html

用户头像

JackJiang

关注

还未添加个人签名 2019-08-26 加入

开源IM框架MobileIMSDK、BeautyEye的作者。

评论

发布
暂无评论
IM开发干货分享:IM客户端不同版本兼容运行的技术思路和实践总结_网络编程_JackJiang_InfoQ写作社区