IM開發(fā)干貨分享:IM客戶端不同版本兼容運行的技術(shù)思路和實踐總結(jié)

本文由鞏鵬軍分享,原題“IM兼容性基建”,本文有修訂。
1、引言
一個成熟的IM成品,在運營過程中隨著時間的推移,會發(fā)布不同的版本,但為了用戶體驗并不能強制要求用戶必須升級到最新版本,而服務(wù)端此時已經(jīng)是最新版本了,所以為了讓這些不同客戶端版本的用戶都能正常使用(尤其IM這種產(chǎn)品,不同版本可能通信協(xié)議都會有變動,這就更要命了),則必須要針對不同客戶端版本的兼容處理。
本文將基于筆者的IM產(chǎn)品開發(fā)和運營實踐,為你分享如何實現(xiàn)不同APP客戶端版本與服務(wù)端通信的兼容性處理方案。

?
學習交流:
- 移動端IM開發(fā)入門文章:《新手入門一篇就夠:從零開發(fā)移動端IM》
- 開源IM框架源碼:https://github.com/JackJiang2011/MobileIMSDK(備用地址點此)
(本文已同步發(fā)布于:http://www.52im.net/thread-4202-1-1.html)
2、關(guān)于作者

鞏鵬軍:專注移動開發(fā)十多年,熱愛即時通訊技術(shù)。個人微信公眾號:“鞏鵬軍”。
作者在即時通訊網(wǎng)分享的另一篇《知識科普:IM聊天應(yīng)用是如何將消息發(fā)送給對方的?(非技術(shù)篇)》,感興趣的讀者也可以看看。
3、一個App時怎么辦?
提示:“一個App”指的是同一個IM服務(wù)端,只服務(wù)于一個特定的IM產(chǎn)品。
首先想到的就是直接使用App版本號判斷新老版本并進行兼容處理。
如下圖所示:

一般來說,不同的IM客戶端(如iOS、Android、Windows、Mac)都是同步迭代,多端發(fā)版時間一致,App版本號也一樣。
所以用跨多端的App版本號可以很容易地讓服務(wù)端只用寫一遍判斷和兼容邏輯。
示例:假設(shè)從V2.1.0開始應(yīng)用紅包消息,那么判斷客戶端是否支持紅包的邏輯就很簡單。
偽代碼如下:
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服務(wù)端,可能作為通用服務(wù),作為多個不同APP產(chǎn)品中的聊天模塊使用的場景。
只有一個App時肯定是比較簡單的。但現(xiàn)實情況是一套IM系統(tǒng)通常會用于多個業(yè)務(wù)場景,這是很普遍的現(xiàn)象。業(yè)界的知名IM產(chǎn)品,比如釘釘、飛書、企業(yè)微信、美團大象等都是這樣。
底層邏輯大概是:IM系統(tǒng)比較復雜,功能繁多而且難以實現(xiàn)、更難以穩(wěn)定,所以一個IM團隊維護一套IM系統(tǒng),然后應(yīng)用在多個業(yè)務(wù)場景就是最具性價比的選擇了。
4.2使用App版本
每個業(yè)務(wù)場景都會有自己的客戶端App,每個App都有自己的版本號,那么根據(jù)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推出新版本后,用戶不可能瞬間就升級到最新版本,根據(jù)經(jīng)驗,每個App往往都會同時存在十個以上的不同版本。
這就會形成如下圖所示的局面:

5、多個App時,可將IM能力提煉為一套公用代碼
多個App時的問題總結(jié)起來就是:一套服務(wù)端代碼如何適應(yīng)集成了不同IM能力的不同App客戶端?
我們來具體舉例分析一下,假設(shè)一個IM團隊維護的IM相關(guān)的客戶端模塊有IM Client SDK、聯(lián)系人、長連接、朋友圈等四個模塊(如下圖所示)。

如上圖所示:
1)App 1:集成了全部四個模塊;
2)App 2:只集成了三個模塊;
3)App 3:只集成了三個模塊。
因為三個App面向的客戶群不同,發(fā)版節(jié)奏不同,所以各自集成的IM的能力也不同。
比如下面這樣:
1)App 1:面向內(nèi)部員工辦公溝通使用的App 1需要功能豐富,對于穩(wěn)定性和Bug有一定的包容性,也容易溝通和修復再發(fā)版;
2)App 2:面向客服場景,用于企業(yè)的客服專員和企業(yè)的C端用戶溝通解決客訴問題,對于穩(wěn)定性要求高,C端用戶升級率不好控制,發(fā)版節(jié)奏慢,最快只能和主業(yè)務(wù)App一致;
3)App 3:面向企業(yè)和B端供應(yīng)商,比如美團和美團上的商戶,京東和京東平臺上的第三方商家,對于穩(wěn)定性要求也比較高,B端商家的升級率好控制一點,發(fā)版節(jié)奏也可以快一些。
從上圖可以看出,因為IM核心能力是同一個團隊維護,所以Core包含的多個模塊的代碼必然是只有一套源代碼。不同App只是Core集成打包出來的產(chǎn)物,或者說不同App只是Core外面套了不同的殼而已,只要Core一樣,則App的IM能力就一樣(這就是本節(jié)標題所述的“多個App時,可將IM能力提煉為一套公用的代碼”這個意思)。
6、給每個App中使用的公用代碼(Core)一個版本號
如上節(jié)所述,我們將IM能力提煉為一套公用代碼(以下內(nèi)容簡稱“Core”)。
那么,我們能不能給Core一個版本標識呢?
答案是肯定的:

站在App的角度,每個App相當于打上了Core版本標簽:

7、如何正確地解讀Core版呢?
7.1拋開App看Core版本
如果不看App版本,只看Core版本標簽:

7.2從一套服務(wù)端代碼看Core版本
同一個IM團隊,其IM Servers必然也是同一套代碼集,不考慮部署的區(qū)別。
那么上圖邏輯上等價于下圖:

7.3使用Core版本的兼容性判斷
站在Core的視角,多個App就像單個App類似,只是使用的版本標識不同。
具體如下:
1)單個App時,IM服務(wù)端要區(qū)分不同App版本;
2)多個App時,IM服務(wù)端要區(qū)分不同Core版本。
還拿是否支持紅包的判斷舉例。
一個App時:
booleanisSupportRedEnvelop(String appVersion){
?
returngte(appVersion, "2.1.0");
}
多個App時:
booleanisSupportRedEnvelop(Integer coreVersion){
?
?returncoreVersion >= 2;
}
通過Core版本號,我們可以把兼容邏輯判斷簡化到和單個App一樣的簡單。
8、關(guān)于Core版本的命名和取值
關(guān)于Core版本號的取值,有下列可能的選項:
選項一:語義版本號 1.2.0;
選項二:整數(shù) 自然數(shù) 1 2 3;
選項三:整數(shù) 迭代日期 20220819 或 220819。
因為Core版本號不用給最終用戶看的,無需遵循常見的語義版本號規(guī)范。而且Core版本號只用于版本對比,所以整數(shù)會是一個比較好的選擇,方便比較,準確可靠。
用自然數(shù)?1、 2、 3作為Core版本號是可以的,每個迭代發(fā)布新的Core版本時遞增一下就可以了。
但是考慮到有多個終端平臺iOS、Android、Windows、Mac,如果某個平臺的Core發(fā)布后發(fā)現(xiàn)小Bug需要HotFix,那么要遞增版本號,就會擠占其它端的下一個自然數(shù)。究其原因,在于自然數(shù)是連續(xù)的,沒辦法在兩個常規(guī)的版本間插入一個HotFix版本。

選項三就可以解決這個問題:因為Core的迭代發(fā)布日期是稀疏的,若干天后才會發(fā)布一個Core版本,那么當某個端需要一個HotFix版本時,選擇HotFix當天的日期作為版本號即可。
總體上:多個端的主要版本號都是約定的統(tǒng)一的發(fā)布日期,多端一致,同時允許某個端臨時HotFix插入一個新的版本號,保留彈性。
參考 Google 對Android SDK API版本的實踐,我們可以把Core版本號命名為core_level,取值為Core的發(fā)布日期的整數(shù)表示。
9、多個App情況下的其它版本標識
1)platform:
一套Core,不同端在實際開發(fā)中,可能存在差異,為了針對具體端進行特定的兼容,需要知道當前是哪個端,可以約定platform字段表示端。取值可以是:ios、android、win、mac、linux等。
2)App版本號:
在IM相關(guān)邏輯的兼容性判斷中,只需使用跨App的多端一致的core_level了。但是為了和最終用戶、產(chǎn)品經(jīng)理等溝通方便,保留App版本號app_version用于人和人之間溝通交流。core_level主要用于研發(fā)工程師之間,還有工程師和程序之間的溝通。兩者各取所長。
10、版本標識的傳輸方式
每個API和每條長連接數(shù)據(jù)包都攜帶Core版本,這樣服務(wù)端可以無狀態(tài)得處理每一個請求。如果需要在服務(wù)端主動推送時區(qū)分目標端的版本,可以在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短轉(zhuǎn)長
短轉(zhuǎn)長時HTTP Header會轉(zhuǎn)換為長連接數(shù)據(jù)body里的header通過長鏈傳遞。
這樣就同時存在長連接header和長連接body.header兩套字段,最終以長連接body.header為準即可。
10.4其它
IM系統(tǒng)里的瀏覽器和小程序,如果可以新增HTTP Header則新增Header傳輸,實在沒有辦法可以通過User-Agent傳輸該信息,服務(wù)端優(yōu)先解析Header,沒有找到時再解析User-Agent。
服務(wù)端解析UA的正則表達式:
/ platform\/(ios|android|mac|win|linux) app_version\/([0-9]\.[0-9]+\.[0-9]+) core_level\/([1-9][0-9]+)( |$)/
以上正則表達式在線運行效果:點此查看。
11、本文小結(jié)
至此,我們找到了一個適用于多個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]?零基礎(chǔ)IM開發(fā)入門(一):什么是IM系統(tǒng)?
[5]?一套海量在線用戶的移動端IM架構(gòu)設(shè)計實踐分享(含詳細圖文)
[6]?一套原創(chuàng)分布式即時通訊(IM)系統(tǒng)理論架構(gòu)方案
[7]?從零到卓越:京東客服即時通訊系統(tǒng)的技術(shù)架構(gòu)演進歷程
[8]?一套億級用戶的IM架構(gòu)技術(shù)干貨(上篇):整體架構(gòu)、服務(wù)拆分等
[9]?基于實踐:一套百萬消息量小規(guī)模IM系統(tǒng)技術(shù)要點總結(jié)
[10]?一套十萬級TPS的IM綜合消息系統(tǒng)的架構(gòu)實踐與思考
[11]?從新手到專家:如何設(shè)計一套億級消息量的分布式IM系統(tǒng)
[12]?閑魚億級IM消息系統(tǒng)的架構(gòu)演進之路
[13]?深度解密釘釘即時消息服務(wù)DTIM的技術(shù)設(shè)計
[14]?一套高可用、易伸縮、高并發(fā)的IM群聊、單聊架構(gòu)方案設(shè)計實踐
[15]?企業(yè)微信的IM架構(gòu)設(shè)計揭秘:消息模型、萬人群、已讀回執(zhí)、消息撤回等
(本文已同步發(fā)布于:http://www.52im.net/thread-4202-1-1.html)