阿里IM技術(shù)分享(七):閑魚IM的在線、離線聊天數(shù)據(jù)同步機制優(yōu)化實踐

本文由阿里閑魚技術(shù)團隊書閑分享,原題“如何有效縮短閑魚消息處理時長”,有修訂和改動,感謝作者的分享。
1、引言
閑魚技術(shù)團隊圍繞IM這個技術(shù)范疇,已經(jīng)分享了好幾篇實踐性總結(jié)文章,本篇將要分享的是閑魚IM系統(tǒng)中在線和離線聊天消息數(shù)據(jù)的同步機制上所遇到的一些問題,以及實踐性的解決方案。

學(xué)習(xí)交流:
- 移動端IM開發(fā)入門文章:《新手入門一篇就夠:從零開發(fā)移動端IM》
- 開源IM框架源碼:https://github.com/JackJiang2011/MobileIMSDK?
(本文已同步發(fā)布于:http://www.52im.net/thread-3856-1-1.html)
2、系列文章
本文是系列文章的第7篇,總目錄如下:
《阿里IM技術(shù)分享(一):企業(yè)級IM王者——釘釘在后端架構(gòu)上的過人之處》
《阿里IM技術(shù)分享(二):閑魚IM基于Flutter的移動端跨端改造實踐》
《阿里IM技術(shù)分享(三):閑魚億級IM消息系統(tǒng)的架構(gòu)演進之路》
《阿里IM技術(shù)分享(四):閑魚億級IM消息系統(tǒng)的可靠投遞優(yōu)化實踐》
《阿里IM技術(shù)分享(五):閑魚億級IM消息系統(tǒng)的及時性優(yōu)化實踐》
《阿里IM技術(shù)分享(六):閑魚億級IM消息系統(tǒng)的離線推送到達率優(yōu)化》
《阿里IM技術(shù)分享(七):閑魚IM的在線、離線聊天數(shù)據(jù)同步機制優(yōu)化實踐》(* 本文)
3、問題背景
隨著用戶數(shù)的快速增長,閑魚IM系統(tǒng)也迎來了前所未有的挑戰(zhàn)。
歷經(jīng)多年的業(yè)務(wù)迭代,客戶端側(cè)IM的代碼已經(jīng)因為多年的迭代層次結(jié)構(gòu)不足夠清晰,之前一些隱藏起來的聊天數(shù)據(jù)同步問題,也隨著用戶數(shù)的增大而被放大。
這里面的具體流程在于:后臺需要同步到用戶端側(cè)的數(shù)據(jù)包,后臺會根據(jù)數(shù)據(jù)包的業(yè)務(wù)類型劃分成不同的數(shù)據(jù)域,數(shù)據(jù)包在對應(yīng)域里面存在唯一且連續(xù)的編號,每一個數(shù)據(jù)包發(fā)送到端側(cè)并且被成功消費后,端側(cè)會記錄當前每一個數(shù)據(jù)域已經(jīng)同步過的版本編號,下一次數(shù)據(jù)同步就以本地數(shù)據(jù)域的編號開始,不斷的同步到客戶端。
當然用戶不會一直在線等待消息,所以之前端側(cè)采用了推拉結(jié)合的方式保證數(shù)據(jù)的同步。
具體就是:
1)客戶端在線時:使用ACCS實時的將最新的數(shù)據(jù)內(nèi)容推送到客戶端(ACCS是淘寶無線向開發(fā)者提供全雙工、低延時、高安全的通道服務(wù));
2)客戶端從離線狀態(tài)啟動后:根據(jù)本地的數(shù)據(jù)域編號,拉取不在線時候的數(shù)據(jù)差;
3)當數(shù)據(jù)獲取出現(xiàn)黑洞時:觸發(fā)數(shù)據(jù)同步拉?。ā昂诙础奔粗笖?shù)據(jù)包Version不連續(xù)的狀態(tài))。
4、問題分析
當前的聊天數(shù)據(jù)同步策略確實是可以基本保障IM的數(shù)據(jù)同步的,但是也伴隨著一些隱含的問題。
這些隱含的問題主要有:
1)短時間密集數(shù)據(jù)推送時,會快速的觸發(fā)多次數(shù)據(jù)域同步。域同步回來的數(shù)據(jù)如果存在問題,又會觸發(fā)新一輪的同步,造成網(wǎng)絡(luò)資源的浪費。冗余數(shù)據(jù)包/無效的數(shù)據(jù)內(nèi)容會占用有效內(nèi)容的處理資源,又對CPU和內(nèi)存資源造成浪費;
2)數(shù)據(jù)域中的數(shù)據(jù)包客戶端是否正常消費,服務(wù)端側(cè)無感知,只能被動地根據(jù)當前數(shù)據(jù)域信息返回數(shù)據(jù);
3)數(shù)據(jù)收取/消息數(shù)據(jù)體解析/存儲落庫邏輯拆分不夠清晰,無法針對性的對某一層的代碼拆分替換進行ABTest。
針對上述問題,我們對閑魚IM進行了分層改造——即抽離數(shù)據(jù)同步層。這樣優(yōu)化,除了希望以后這個數(shù)據(jù)的同步內(nèi)容可以用在IM之外,也希望隨著穩(wěn)定性的增加,賦能其他的業(yè)務(wù)場景。
接下來的內(nèi)容,我們重點來看下解決客戶端側(cè)閑魚IM聊天數(shù)據(jù)同步問題的一些實踐思路。
5、優(yōu)化思路
5.1 分層拆分
對于服務(wù)端來說:業(yè)務(wù)側(cè)產(chǎn)出數(shù)據(jù)包后,會拼接上當前的數(shù)據(jù)域信息,然后通過數(shù)據(jù)同步層將數(shù)據(jù)推送到端側(cè)。
對于客戶端來說:接收到數(shù)據(jù)包后,會根據(jù)當前的數(shù)據(jù)域信息,來確定需要消費數(shù)據(jù)包的業(yè)務(wù)方,確保數(shù)據(jù)包在數(shù)據(jù)域內(nèi)完整連續(xù)后,將數(shù)據(jù)體脫殼后交于業(yè)務(wù)側(cè)消費,并且應(yīng)答消費的狀況。
數(shù)據(jù)同步層的抽取:把數(shù)據(jù)同步中的加殼、脫殼、校驗、重試流程封裝到一起,可以讓上層業(yè)務(wù)只需要關(guān)心自己需要監(jiān)聽的數(shù)據(jù)域信息,然后當這些數(shù)據(jù)域更新數(shù)據(jù)的時候,可以獲取到這些數(shù)據(jù)進行消費,而不再需要關(guān)心數(shù)據(jù)包是否完整。
這樣做的話:
1)業(yè)務(wù)側(cè)只需要關(guān)心業(yè)務(wù)側(cè)對接的協(xié)議;
2)數(shù)據(jù)側(cè)只需要關(guān)心數(shù)據(jù)側(cè)包裝的協(xié)議;
3)網(wǎng)絡(luò)層負責(zé)真實的數(shù)據(jù)傳輸。
整體的架構(gòu)原理如下:

總結(jié)一下就是:
1)對齊數(shù)據(jù)層數(shù)據(jù)傳輸協(xié)議、描述當前數(shù)據(jù)包體數(shù)據(jù)域信息;
2)將消息的處理/合并/落庫抽離成數(shù)據(jù)消費者;
3)上下樓依賴抽象化,去除對于具體實現(xiàn)的依賴。
5.2 數(shù)據(jù)層結(jié)構(gòu)模型
基于對于數(shù)據(jù)模型剝離和對當下遇見問題的解決方案規(guī)整,將數(shù)據(jù)同步層拆分為下圖這樣的架構(gòu)。

具體的實施思路就是:
1)App啟動時建立ACCS長鏈接服務(wù),保證推推送信道鏈接,并且根據(jù)當前本地數(shù)據(jù)域信息觸發(fā)一次數(shù)據(jù)拉?。?/p>
2)數(shù)據(jù)消費者注冊消費者信息和需要監(jiān)聽的數(shù)據(jù)域信息,這里是一對多的關(guān)系;
3)新的數(shù)據(jù)抵達端側(cè)后,將數(shù)據(jù)包放到指定的數(shù)據(jù)域的緩沖池,批量數(shù)據(jù)歸納結(jié)束后,重新出發(fā)數(shù)據(jù)的讀?。?/p>
4)根據(jù)當前數(shù)據(jù)域優(yōu)先級彈出最高優(yōu)的數(shù)據(jù)包,判斷數(shù)據(jù)域版本是否符合消費者要求,符合則將數(shù)據(jù)包脫殼后丟給消費者消費,不符合則根據(jù)上一次正確的數(shù)據(jù)包的域信息觸發(fā)增量的數(shù)據(jù)域同步拉??;
5)觸發(fā)數(shù)據(jù)域同步拉取時,block數(shù)據(jù)讀取,此時通過ACCS觸達的數(shù)據(jù)依舊會在繼續(xù)歸納到指定的數(shù)據(jù)域隊列中,等待數(shù)據(jù)域同步拉取結(jié)果,將數(shù)據(jù)包進行排序、去重,合并到對應(yīng)的數(shù)據(jù)域隊列中。然后重新激活數(shù)據(jù)讀取;
6)數(shù)據(jù)包體被消費者正確消費后,更新域信息并且通過上行信道告知服務(wù)端已經(jīng)正確處理的數(shù)據(jù)域信息。
* 數(shù)據(jù)域同步協(xié)議:
Region中攜帶的數(shù)據(jù)不必過多,但需將數(shù)據(jù)包的內(nèi)容描述清楚,具體是:
1)目標用戶的ID,用以確定目標數(shù)據(jù)包是否正確;
2)數(shù)據(jù)域ID和優(yōu)先級信息;
3)當前數(shù)據(jù)包的域優(yōu)先級版本。
* 排序策略:
針對于域數(shù)據(jù)歸納,無論是在寫入數(shù)據(jù)的時候進行排序還是在讀取的時候進行查找都需要進行一次排序的操作,時間復(fù)雜度最優(yōu)也是O(logn)級別的。
在實際coding中發(fā)現(xiàn)由于在一個數(shù)據(jù)域里面,數(shù)據(jù)包的Version信息是連續(xù)唯一并且不存在斷層的,上一個穩(wěn)定消費的數(shù)據(jù)體的Version信息自增就是下一個數(shù)據(jù)包的Version,所以這里采用了以Versio為主鍵的Map存儲,既降低了時間復(fù)雜度,也使得唯一標識的數(shù)據(jù)包后抵達端側(cè)的包內(nèi)容可以覆蓋之前的包內(nèi)容。
6、新的問題及解決策略
6.1 多數(shù)據(jù)來源和唯一數(shù)據(jù)消費的平衡
每當產(chǎn)生一條針對于當前用戶的數(shù)據(jù)包:
1)如果當前ACCS長鏈接存在,就會通過ACCS將數(shù)據(jù)包推送到客戶端;
2)如果App切換到后臺一段時間,或者直接被殺死,ACCS鏈接斷開,那么只能通過離線推送到用戶的通知面板。
所以:每當App切換到活躍狀態(tài),都需要根據(jù)當前本地存儲的數(shù)據(jù)域信息從后臺觸發(fā)一次數(shù)據(jù)同步。
數(shù)據(jù)包觸達到客戶端側(cè)的來源主要是ACCS長鏈接的推送和域同步時的拉取,但是數(shù)據(jù)包的消費是根據(jù)數(shù)據(jù)域的監(jiān)聽劃分的唯一消費者,也就是同一時間內(nèi)只能消費一個數(shù)據(jù)包。
在壓力測試中:當后臺短時間內(nèi)密集的將數(shù)據(jù)包通過ACCS推送到端側(cè)時,端側(cè)接收到的數(shù)據(jù)包并不有序,不連續(xù)的數(shù)據(jù)包域版本又會觸發(fā)新的數(shù)據(jù)域同步,導(dǎo)致同樣的一份數(shù)據(jù)包會通過兩個不同的渠道多次的觸達到端側(cè),浪費了不必要的流量。
當數(shù)據(jù)域同步時:這個時間節(jié)點產(chǎn)生的新數(shù)據(jù)包也會推送到端側(cè),數(shù)據(jù)體有效,并且需要被正確的消費。
針對上述這些問題的解決策略:
即在數(shù)據(jù)消費和數(shù)據(jù)獲取中間裝載一個數(shù)據(jù)中間層,當觸發(fā)數(shù)據(jù)域同步的時候block數(shù)據(jù)的讀取并且ACCS推送下來的數(shù)據(jù)包會被存放在一個數(shù)據(jù)的中轉(zhuǎn)站里面,當數(shù)據(jù)域同步拉取的數(shù)據(jù)回來后,對數(shù)據(jù)進行合并后再重啟數(shù)據(jù)讀取流程。
6.2 數(shù)據(jù)域優(yōu)先級
需要推送到客戶端側(cè)的數(shù)據(jù)包,根據(jù)業(yè)務(wù)的不同優(yōu)先級也有不同的劃分。
用戶和用戶的聊天產(chǎn)生的數(shù)據(jù)包會比運營類的消息的數(shù)據(jù)包優(yōu)先級要高一些,所以要當多優(yōu)先級的數(shù)據(jù)包快速的抵達端側(cè)時,高優(yōu)先級數(shù)據(jù)域的數(shù)據(jù)包需要被優(yōu)先消費,而數(shù)據(jù)域的優(yōu)先級也是需要動態(tài)調(diào)整,需要不斷變換的優(yōu)先級策略。
針對這個問題的解決策略:
不同的數(shù)據(jù)域,產(chǎn)生不同的數(shù)據(jù)隊列,高優(yōu)隊列里面的數(shù)據(jù)包會被優(yōu)先讀取消費。
每一個數(shù)據(jù)包體中帶回的數(shù)據(jù)域信息,都可以標注當前的數(shù)據(jù)域優(yōu)先級,當數(shù)據(jù)域優(yōu)先級發(fā)生變化的時候,調(diào)整數(shù)據(jù)包消費優(yōu)先級策略。
7、優(yōu)化后的效果
除去結(jié)構(gòu)上分層梳理,使得數(shù)據(jù)同步層和依賴的服務(wù)內(nèi)容可便捷解耦/每一個環(huán)節(jié)可插拔之外,數(shù)據(jù)同步中對于消息消費時長/流量節(jié)省,壓力測試場景下優(yōu)化效果更加明顯。
在“500ms內(nèi)100條全亂序數(shù)據(jù)包推送”壓力測試場景下:
1)消息處理時長(接收-上屏)縮短 31%;
2)流量損耗(最終拉取到端側(cè)數(shù)據(jù)包累積大?。┙档?5%。
8、后續(xù)的優(yōu)化計劃
8.1 數(shù)據(jù)同步層能力提升
數(shù)據(jù)同步側(cè)的目標,既要保證數(shù)據(jù)包完整的到達端側(cè),又要在保證穩(wěn)定性的前提下盡可能的減少數(shù)據(jù)的拉取,使得每一次數(shù)據(jù)的獲取都有效。
后續(xù)數(shù)據(jù)同步層會著手于有效數(shù)據(jù)率和到達率進行更進一步的優(yōu)化。
針對不同的場景,動態(tài)智能調(diào)整數(shù)據(jù)同步的優(yōu)先級策略。
阻塞式長鏈接推送,保證同一時間只存在推模式或者拉模式,進一步減少冗余數(shù)據(jù)包的推送。
8.2 IM端側(cè)整體架構(gòu)升級
升級數(shù)據(jù)同步層策略主要還是要提升IM的能力,將數(shù)據(jù)同步分層后,接下來就是將消息的處理流程化,對每一個流程都可監(jiān)控可回溯,提升IM數(shù)據(jù)包的正確解析存儲和落庫率。
細化一下就是:
1)在數(shù)據(jù)來源側(cè)剝離開后,后續(xù)對IM的整改也會逐步的將消息的處理分層剝離;
2)消息處理關(guān)鍵節(jié)點的流程式上報、建立完整的監(jiān)控體系,讓問題發(fā)現(xiàn)先于用戶輿情;
3)消息完整性的動態(tài)自檢,最小化數(shù)據(jù)補償補全。
9、參考資料
[1]?IM單聊和群聊中的在線狀態(tài)同步應(yīng)該用“推”還是“拉”?
[2]?IM群聊消息如此復(fù)雜,如何保證不丟不重?
[3]?一套高可用、易伸縮、高并發(fā)的IM群聊、單聊架構(gòu)方案設(shè)計實踐
[4]?一套億級用戶的IM架構(gòu)技術(shù)干貨(下篇):可靠性、有序性、弱網(wǎng)優(yōu)化等
[5]?從新手到專家:如何設(shè)計一套億級消息量的分布式IM系統(tǒng)
[6]?融云技術(shù)分享:全面揭秘億級IM消息的可靠投遞機制
[7]?移動端IM中大規(guī)模群消息的推送如何保證效率、實時性?
[8]?現(xiàn)代IM系統(tǒng)中聊天消息的同步和存儲方案探討
[9]?新手入門一篇就夠:從零開發(fā)移動端IM
[10]?IM消息送達保證機制實現(xiàn)(一):保證在線實時消息的可靠投遞
[11]?IM消息送達保證機制實現(xiàn)(二):保證離線消息的可靠投遞
[12]?零基礎(chǔ)IM開發(fā)入門(四):什么是IM系統(tǒng)的消息時序一致性?
[13]?IM開發(fā)干貨分享:我是如何解決大量離線消息導(dǎo)致客戶端卡頓的
(本文已同步發(fā)布于:http://www.52im.net/thread-3856-1-1.html)