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

博客园   2023-04-28 11:32:04

本文由巩鹏军分享,原题“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 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 verNums = Arrays

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

.map(Integer::valueOf)

.collect(Collectors.toList());

returnverNums;

}

booleangte(String version, String target) {

List appVerNums = toNums(version);

Integer appMajor = appVerNums.get(0);

Integer appMinor = appVerNums.get(1);

Integer appPatch = appVerNums.get(2);

List 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、联系人、长连接、朋友圈等四个模块(如下图所示)。

如上图所示:

因为三个App面向的客户群不同,发版节奏不同,所以各自集成的IM的能力也不同。

比如下面这样:

从上图可以看出,因为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类似,只是使用的版本标识不同。

具体如下:

还拿是否支持红包的判断举例。

一个App时:

booleanisSupportRedEnvelop(String appVersion){

returngte(appVersion, "2.1.0");

}

多个App时:

booleanisSupportRedEnvelop(Integer coreVersion){

returncoreVersion >= 2;

}

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

8、关于Core版本的命名和取值

关于Core版本号的取值,有下列可能的选项:

因为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)

最新资讯