本文描述一種基于消息的會(huì)話列表差量更新的實(shí)現(xiàn),也算是屬于原創(chuàng)吧薛训,哈媒吗。
先描述一下,用戶在消息列表界面看到的“與某人或某個(gè)群的對(duì)話列表”乙埃,此處稱為會(huì)話列表闸英。
對(duì),奏是這個(gè)圖所示的列表介袜。
本文描述的方案甫何,在存儲(chǔ)方面選用的存儲(chǔ)是mongodb。其實(shí)在做之前沒(méi)有特意為實(shí)現(xiàn)細(xì)節(jié)去選db遇伞,而是確定選用mongodb之后辙喂,基于它的特性來(lái)細(xì)化方案(對(duì)于海量數(shù)據(jù)級(jí)別的實(shí)現(xiàn)基于mongodb比較有優(yōu)勢(shì),便于擴(kuò)展,但是對(duì)于數(shù)據(jù)量較小的企業(yè)巍耗,選用mongodb作為存儲(chǔ)有點(diǎn)尷尬秋麸,一個(gè)是小數(shù)據(jù)量對(duì)于存儲(chǔ)的擴(kuò)展要求不高,另一個(gè)是mongodb性能趕不上其他的nosql)芍锦。但是我相信基于其他的存儲(chǔ)也可以做到。下面分開(kāi)描述這個(gè)需求要做的事情飞盆。
1. 用戶每次登錄首先去拉取會(huì)話列表娄琉,差量返回有過(guò)修改(刪除)或者有新消息(任何設(shè)備未拉取過(guò))的會(huì)話列表,會(huì)話附帶一條消息返回吓歇。
2. 要求會(huì)話列表是云端存儲(chǔ)的孽水,設(shè)備間要能同步。我在安卓手機(jī)上面做了刪除會(huì)話Ca(C代表會(huì)話城看,a代表對(duì)方的id)操作女气,切換到另一臺(tái)手機(jī)(比如蘋果)需要告知會(huì)話Ca被刪除。我在安卓手機(jī)與韓梅聊過(guò)天测柠,并生成了會(huì)話記錄Ua炼鞠,切換到另一臺(tái)手機(jī),需要拉到這個(gè)會(huì)話Ua轰胁。
3. 要求消息是差量更新的谒主,而且每個(gè)會(huì)話里只拉取最新的一條消息記錄。差量更新時(shí)赃阀,只有包含新消息的會(huì)話才會(huì)返回霎肯。為了保證最省流量(幫助用戶省流量的產(chǎn)品才是良心產(chǎn)品),沒(méi)有新消息或者沒(méi)有會(huì)話更改的榛斯,一律不返回?cái)?shù)據(jù)观游。這個(gè)消息或會(huì)話差量更新的實(shí)現(xiàn)使用數(shù)據(jù)版本號(hào)來(lái)做。
客戶端看到的會(huì)話偽代碼數(shù)據(jù)結(jié)構(gòu)簡(jiǎn)要描述如下:
conversation
{
uint32 delta_flag;//差量操作標(biāo)志驮俗,增刪
uint64 snapshot;//數(shù)據(jù)版本號(hào)
uint64 peer_id;//對(duì)方的id,標(biāo)識(shí)與誰(shuí)產(chǎn)生的會(huì)話
MsgInfo lastest_msg;//最新的消息
uint32 unread_msg_num;//本會(huì)話的未讀消息數(shù)
}
差量會(huì)話同步數(shù)據(jù)的實(shí)現(xiàn)在客戶端看來(lái)只有一步懂缕,具體在服務(wù)器的實(shí)現(xiàn)分為1.同步有過(guò)修改的會(huì)話列表,2.同步新消息王凑,再根據(jù)消息生成會(huì)話列表提佣,3.對(duì)1,2的結(jié)果做交集。
先介紹服務(wù)端在實(shí)現(xiàn)這個(gè)方案所涉及到的存儲(chǔ)數(shù)據(jù)結(jié)構(gòu)荤崇。
消息的存儲(chǔ)結(jié)構(gòu)簡(jiǎn)要偽代碼:
MsgInfo
{
uint64 id;//這是給某人存儲(chǔ)的消息
uint64 from_id;//消息發(fā)送方
uint64 to_id;//消息接收方
uint64 msg_id;//標(biāo)識(shí)一條消息
uint32 msg_time;//消息的產(chǎn)生時(shí)間
string msg_content;//消息內(nèi)容
}
會(huì)話的存儲(chǔ)結(jié)構(gòu)簡(jiǎn)要偽代碼:
ConversationInfo
{
uint32 delta_flag;//差量操作標(biāo)志拌屏,增刪
uint64 snapshot;//數(shù)據(jù)版本號(hào)
uint64 peer_id;//標(biāo)識(shí)與誰(shuí)產(chǎn)生的會(huì)話
MsgInfo lastest_msg;//最新的消息
}
涉及到mongodb的特性:
mongodb是介于sql和nosql之間的產(chǎn)品,有sql域的概念术荤∫形梗可以對(duì)文檔的部分字段更新,可以在查詢數(shù)據(jù)的時(shí)候指定排序的域及其排序方式,更新數(shù)據(jù)的時(shí)候匹配就更新或者不更新端圈,更新數(shù)據(jù)時(shí)沒(méi)有匹配就插入新數(shù)據(jù)焦读,更新數(shù)據(jù)同時(shí)返回更新之后(或者更新之前的)的數(shù)據(jù)。
會(huì)話列表的更新時(shí)機(jī)及更新的字段:
每產(chǎn)生一條消息的時(shí)候舱权,就會(huì)相應(yīng)的為每個(gè)用戶更新會(huì)話信息矗晃,更新的內(nèi)容就是最新消息和差量操作標(biāo)志(新增),還有一些支持其他特性字段宴倍,此處無(wú)關(guān)不列出了张症,但是此處不更新會(huì)話的數(shù)據(jù)版本號(hào)。
會(huì)話信息的更新時(shí)機(jī)有多個(gè)鸵贬,上述做法會(huì)導(dǎo)致db寫的復(fù)雜度上升俗他。還有一種是同步的時(shí)候做更新。這種做法會(huì)導(dǎo)致db讀的復(fù)雜度上升阔逼,暫不描述兆衅。
會(huì)話列表的刪除時(shí)機(jī)及做法:
當(dāng)用戶手動(dòng)刪除某個(gè)會(huì)話或者清空會(huì)話列表時(shí),會(huì)與服務(wù)器進(jìn)行交互嗜浮,客戶端請(qǐng)求的參數(shù)包括會(huì)話的peer_id羡亩、當(dāng)前會(huì)話中的最大消息id。peer_id用于查詢會(huì)話危融,消息id用于判斷請(qǐng)求是否已過(guò)期夕春。如果是過(guò)期的請(qǐng)求,將不會(huì)刪除會(huì)話专挪。如果條件都符合及志,就將db中的會(huì)話進(jìn)行標(biāo)記刪除,注意此處并沒(méi)有將數(shù)據(jù)清除寨腔。
接下來(lái)描述刪除請(qǐng)求過(guò)期場(chǎng)景:
1. 用戶在設(shè)備A沒(méi)有聯(lián)網(wǎng)的情況下手動(dòng)點(diǎn)刪除會(huì)話Cc速侈,本地將會(huì)話刪除,同時(shí)將這個(gè)刪除請(qǐng)求緩存在本地迫卢,等待下次聯(lián)網(wǎng)時(shí)告知服務(wù)器倚搬。然后用戶在設(shè)備B與用戶c聊天,聊完之后并未刪除這個(gè)會(huì)話Cc乾蛤。之后設(shè)備A聯(lián)網(wǎng)每界,將之前緩存的刪除請(qǐng)求上傳服務(wù)器執(zhí)行,此時(shí)服務(wù)器將Cc標(biāo)記刪除家卖,但是用戶在設(shè)備B上并未刪除會(huì)話Cc眨层,導(dǎo)致設(shè)備B的數(shù)據(jù)與服務(wù)器不一致。這就是A的刪除請(qǐng)求過(guò)期導(dǎo)致數(shù)據(jù)不一致上荡。
2. 用戶在某個(gè)時(shí)間點(diǎn)手動(dòng)刪除會(huì)話Cc趴樱,本地會(huì)話先清空,在這個(gè)刪除請(qǐng)求未在服務(wù)器執(zhí)行之前,c發(fā)過(guò)來(lái)一條消息叁征,服務(wù)器更新了會(huì)話列表纳账,然后客戶端也同步消息到本地,本地再次生成這個(gè)會(huì)話捺疼,之后服務(wù)器執(zhí)行刪除操作∈璩妫現(xiàn)在的情況是服務(wù)器認(rèn)為會(huì)話已刪除,本地則是新增會(huì)話啤呼,數(shù)據(jù)出現(xiàn)不一致卧秘。
所以為了解決請(qǐng)求過(guò)期導(dǎo)致的數(shù)據(jù)不一致問(wèn)題,我們?cè)谡?qǐng)求中添加了操作版本號(hào)(不過(guò)此處使用的是最大消息id)媳友。刪除操作時(shí)斯议,當(dāng)服務(wù)器判斷到客戶端請(qǐng)求中的消息id小于服務(wù)器會(huì)話中保存的消息id产捞,就不執(zhí)行刪除操作醇锚。
會(huì)話列表的同步:
用戶登錄完成之后就要同步會(huì)話列表,其請(qǐng)求的參數(shù)包括本地會(huì)話最大版本號(hào)和本地消息的最大版本號(hào)坯临。兩個(gè)版本號(hào)共同用于差量同步會(huì)話列表焊唬。
會(huì)話列表的最大版本號(hào)只能同步到用戶手動(dòng)更新過(guò)的會(huì)話列表,并不能同步到有新消息的會(huì)話列表看靠,因?yàn)橛邢a(chǎn)生的時(shí)候并沒(méi)有更新會(huì)話列表的版本號(hào)赶促。[1]
消息的最大版本號(hào)則用于同步新消息,服務(wù)器使用消息生成會(huì)話列表挟炬,并與上述同步到的會(huì)話列表做合并鸥滨,兩者合并的結(jié)果就是差量的會(huì)話列表。[2]
優(yōu)化的點(diǎn):
當(dāng)客戶端請(qǐng)求中的會(huì)話最大版本號(hào)為0時(shí)谤祖,僅僅通過(guò)[1]就可以把全量會(huì)話列表同步到婿滓,不需要再同步消息。因?yàn)闀?huì)話列表中已經(jīng)有最新消息粥喜。同時(shí)凸主,刪除的會(huì)話列表也不用返回,因?yàn)楸镜財(cái)?shù)據(jù)是空的额湘,返回刪除過(guò)的會(huì)話列表毫無(wú)意義卿吐。
單人消息的會(huì)話列表機(jī)制db訪問(wèn)時(shí)間復(fù)雜度:
產(chǎn)生一條消息會(huì)為消息雙方更新會(huì)話信息脂凶,這里有兩次寫操作秕豫,所有設(shè)備共用這個(gè)信息护侮。
拉取會(huì)話列表時(shí)股毫,一次遍歷會(huì)話列表操作整袁,一次遍歷新消息操作肢专,故有兩次讀操作媳握。
所以平均db訪問(wèn)時(shí)間復(fù)雜度為O(k)各拷,k為常量4
群消息的會(huì)話列表機(jī)制db訪問(wèn)時(shí)間復(fù)雜度:
產(chǎn)生一條消息會(huì)為群里所有用戶更新會(huì)話信息,有n次寫操作紧阔。
拉取會(huì)話列表時(shí)與單人一致坊罢。
所以平均db訪問(wèn)時(shí)間復(fù)雜度為O(n+k),n為群用戶數(shù)擅耽,k為常量2活孩。所以對(duì)于群的會(huì)話列表實(shí)現(xiàn)需要進(jìn)行方案修改。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
會(huì)話列表同步優(yōu)化:
上述流程是
1.同步有過(guò)修改的會(huì)話列表乖仇,
2.同步新消息憾儒,再根據(jù)消息生成會(huì)話列表,
3.對(duì)1,2的結(jié)果做交集乃沙。
可以優(yōu)化為
1.同步會(huì)話版本號(hào)比客戶端請(qǐng)求中的版本號(hào)大的或者消息版本號(hào)比客戶端請(qǐng)求中的消息版本號(hào)大的會(huì)話列表起趾,
2.直接返回上述結(jié)果。
因?yàn)闀?huì)話中的消息是最新的警儒,只要滿足(服務(wù)器會(huì)話版本>客戶端會(huì)話版本 或者 服務(wù)器消息版本>客戶端消息版本)這個(gè)條件训裆,下發(fā)的會(huì)話列表一定是差量包含最新消息的。