一套原創(chuàng)分布式即時(shí)通訊(IM)系統(tǒng)理論架構(gòu)方案

一 典型的即時(shí)通訊架構(gòu)可能是這樣的

無論是IM消息通信系統(tǒng)還是客戶消息系統(tǒng),其本質(zhì)都是一套消息發(fā)送與投遞系統(tǒng),或者說是一套網(wǎng)絡(luò)通信系統(tǒng),其本質(zhì)兩個(gè)詞:存儲(chǔ)與轉(zhuǎn)發(fā)。

1攜程異步消息系統(tǒng)初期架構(gòu)

上圖所示顯示了攜程家的消息系統(tǒng)的初期架構(gòu),圖中架構(gòu)直接用mongodb作為消息隊(duì)列,然后就把系統(tǒng)開發(fā)出來了,圖中中可以見到一個(gè)常見IT系統(tǒng)的接口層。

2京東咚咚初期架構(gòu)

上圖揭示了京東家的消息系統(tǒng)的初期架構(gòu),其特點(diǎn)是“為了業(yè)務(wù)的快速上線,1.0 版本的技術(shù)架構(gòu)實(shí)現(xiàn)是非常直接且簡(jiǎn)單粗暴的”,而且后臺(tái)系統(tǒng)使用.net基于Redis就把一個(gè)IM系統(tǒng)開發(fā)出來了。

兩家系統(tǒng)的初期架構(gòu)說明,一套消息系統(tǒng)對(duì)提升自家的服務(wù)質(zhì)量是多么的重要,可以認(rèn)為現(xiàn)代的服務(wù)型的互聯(lián)網(wǎng)公司成長(zhǎng)過程就是一套IM系統(tǒng)的進(jìn)化史。

二 本次方案的整體思路

本文結(jié)合鄙人對(duì)IM系統(tǒng)的了解,也給出一套初具IM系統(tǒng)系統(tǒng)特點(diǎn)的消息系統(tǒng)架構(gòu)模型。本文只考慮IM系統(tǒng)的在線消息模型,不考慮其離線消息系統(tǒng)[能夠存儲(chǔ)IM消息的系統(tǒng)]。

1根據(jù)個(gè)人理解,其應(yīng)有的feature如下

A 整個(gè)系統(tǒng)中Server端提供存儲(chǔ)轉(zhuǎn)發(fā)能力,無論整體架構(gòu)是B/S還是C/S;

B 消息發(fā)送者能夠成功發(fā)送消息給后端,且得到后端地確認(rèn);

C 接收端能夠不重不漏地接收Server端轉(zhuǎn)發(fā)來的沒有超過消息生命周期和系統(tǒng)承載能力的消息;

D 整個(gè)系統(tǒng)只考慮文本短消息[即限制其長(zhǎng)度];

E 每條消息都有生命周期,如一天,且有長(zhǎng)度限制如1440B【盡量不要超過一個(gè)frame的size】,只考慮在線消息的處理,無論是超時(shí)的消息還是超出系統(tǒng)承載能力的消息[如鍵盤狂人或者鍵盤狂機(jī)器人發(fā)出的消息]都被認(rèn)為是"垃圾消息";

F 為簡(jiǎn)單起見,不給消息很多類型,如個(gè)人對(duì)個(gè)人消息,群消息,討論組消息等,都認(rèn)為是一種群[下文用channel替代之,也有人用Room這個(gè)詞]消息類型;

G 為簡(jiǎn)單起見,這個(gè)群的建立與銷毀流程本文不述及,也即消息流程開始的時(shí)候各個(gè)消息群都已經(jīng)組建完畢,且流程中沒有成員的增減;

H 賬戶申請(qǐng)、用戶鑒權(quán)和天朝獨(dú)有的黃反詞檢查等IM安全層等暫不考慮。

2根據(jù)以上系統(tǒng)特點(diǎn),先給出一套稍微完備的IM系統(tǒng)的框架圖

3系統(tǒng)名詞解釋

1PC:單機(jī)型客戶端,如windows端和mac端等等;

2Web/h5:網(wǎng)頁(yè)客戶端;

3Android:手機(jī)移動(dòng)端,取其典型Android端,當(dāng)然也有ios端[但是考慮到各家開發(fā)App都是安卓客戶端最先上系統(tǒng)新版本,故用Android代表之];

4broker:文本消息的有線或者無線接口端,考慮到攜程采用了這個(gè)詞,我也姑且先用之,它提供了消息的接收與投遞功能;

5Relay:圖片/語音/視頻 轉(zhuǎn)發(fā)接口端,其后端可以是自家的服務(wù)也可以是第三方服務(wù)(如提供圖片存儲(chǔ)服務(wù)的七牛、提供云視頻解決方案的騰訊云等);

6msg chat server:消息邏輯處理端;

7Router:在線狀態(tài)服務(wù)端,存儲(chǔ)在線的用戶以及其登錄的broker接口機(jī)的id以及一些心跳包時(shí)間等數(shù)據(jù);

8Counter:消息計(jì)數(shù)器,為每個(gè)text等類型的消息分配MSG id;

9Msg Queue:每個(gè)channel消息的msg id隊(duì)列,存儲(chǔ)每個(gè)client未接收的且未超時(shí)的且未超出隊(duì)列大小的msg id集合;

10Mysql/mongodb:消息存儲(chǔ)服務(wù)、用戶資料數(shù)據(jù)、以及channel成員列表服務(wù)數(shù)據(jù)庫(kù),因?yàn)槎弑容^典型,所以取用了這個(gè)名字,當(dāng)然你可以在其上部署一層cache服務(wù);

11Client:客戶端層;

12Interface/If(下文簡(jiǎn)稱If):服務(wù)接口層;

13Logic:消息邏輯處理層,[這層其實(shí)應(yīng)該有系統(tǒng)最多的模塊];

14DB:存儲(chǔ)層,存儲(chǔ)了在線狀態(tài)、消息id以及msg id隊(duì)列和消息內(nèi)容等;

15http:消息發(fā)送和接收協(xié)議,IM協(xié)議中一般理解為long polling消息處理方式,在web端多采用這種協(xié)議;

16Websocket:另一種消息發(fā)送和接收協(xié)議,在移動(dòng)環(huán)境或者采用html5開發(fā)的系統(tǒng)多采用這種協(xié)議;

17TCP:另一種消息發(fā)送和接收協(xié)議,在環(huán)境或者采用html5開發(fā)的系統(tǒng)多采用這種協(xié)議;

18UDP:另一種消息發(fā)送和接收協(xié)議,某個(gè)不保證提供穩(wěn)定消息傳輸服務(wù)的廠家采用的協(xié)議,也許也是用戶最多使用的協(xié)議,它的優(yōu)點(diǎn)是無論是無線還是有線環(huán)境下都非??欤钟捎趆ttp/Websocket的基礎(chǔ)都是tcp協(xié)議,UDP協(xié)議在環(huán)境擁塞情況下由于不提供擁塞控制等退讓算法,反而會(huì)去爭(zhēng)用網(wǎng)絡(luò)通道,所以在網(wǎng)絡(luò)復(fù)雜的特別是發(fā)生網(wǎng)絡(luò)風(fēng)暴的情況下它會(huì)顯得更快^ _ ^ & ^ _ ^【呵呵噠】;

19RPC:一種遠(yuǎn)程過程調(diào)用協(xié)議,提供分布式環(huán)境下的函數(shù)調(diào)用能力;

20Restful:一種遠(yuǎn)程服務(wù)提供的架構(gòu)風(fēng)格,跟RPC比起來貌似更高級(jí)點(diǎn)。

三 具體消息發(fā)送流程

在介紹消息發(fā)送流程之前,先介紹一些基本概念。

1pub/sub、UIN和session

一個(gè)消息系統(tǒng),從宏觀上來說,就是一個(gè)PUB/SUB系統(tǒng),有消息生成者publisher[or producer],有消息中轉(zhuǎn)者broker,有消息處理者msg server,以及消息消費(fèi)者subscriber[or consumer]。消息消費(fèi)者可以是一個(gè)人,也可以是一群人,在pub/sub系統(tǒng)之中producer&consumer一起構(gòu)成了一個(gè)channel,或者稱之為room,或者稱之為group。

無論是producer還是consumer,每個(gè)具體單位都要由系統(tǒng)分配給一個(gè)id,稱之為UIN[名詞來源于icq]。

后端的if層的broker機(jī)器可以在全球或者某個(gè)區(qū)域分布多個(gè),UIN依據(jù)dns系統(tǒng)可以得到if層所有的機(jī)器列表,如果dns層由于機(jī)器壞掉或者是被攻擊時(shí)不能服務(wù),那么客戶端應(yīng)該根據(jù)記憶[無論是上次成功登陸的機(jī)器還是被廠家內(nèi)置的機(jī)器列表]知道某些機(jī)器的ip&port地址,然后根據(jù)測(cè)速結(jié)果來選擇一個(gè)離其最近的broker。

UIN在于broker之間進(jìn)行一段時(shí)間內(nèi)有效的會(huì)話服務(wù),稱之為一個(gè)session。這個(gè)session存活于一個(gè)長(zhǎng)連接里,也可以橫跨幾個(gè)長(zhǎng)連接或者短連接,即session自身依賴的網(wǎng)絡(luò)鏈接是不穩(wěn)定的。session有效期間內(nèi),Server認(rèn)為UIN在線,session有效期內(nèi)客戶端要定時(shí)地給broker發(fā)送心跳包。本文認(rèn)為的session可以是不穩(wěn)定的,即session有效期內(nèi)下發(fā)給客戶端的消息可以丟失,但是可以通過一些其他手段保證消息被投遞給客戶端。

2四 發(fā)送流程

消息的制造者[producer]一般是IM系統(tǒng)的最基本單元UIN[即一個(gè)自然人],既然是一個(gè)自然人,就認(rèn)為其發(fā)送能力有限,不可能一秒內(nèi)發(fā)出多于一條的消息,即其消息頻率最高為: 1條msg / s。高于這個(gè)頻率,都被認(rèn)為是鍵盤狂人或者狂躁機(jī)器人,客戶端或者服務(wù)端應(yīng)該具有拒絕給這種人提供服務(wù)或者丟棄其由于發(fā)狂而發(fā)出的消息。

基于上面這個(gè)假設(shè),producer發(fā)出的消息請(qǐng)求被稱為msg req,服務(wù)器給客戶端返回的消息響應(yīng)稱為msg ack。整個(gè)消息流程為:

A client以阻塞方式發(fā)出msg req,req = {producer uin, channel name, msg device id, msg time, msg content};

B broker收到消息后,以u(píng)in為hash或者通過其他hash方式把消息轉(zhuǎn)發(fā)給某個(gè)msg chat server;

C msg chat server收到消息后以key = Hash{producer uin【發(fā)送者id】 + msg device id【設(shè)備id】+ msg time【消息發(fā)送時(shí)間,精確到秒】}到本地消息緩存中查詢消息是否已經(jīng)存在,如果存在則終止消息流程,給broker返回"duplicate msg"這個(gè)msg ack,否則繼續(xù);

D msg chat server到Counter模塊以channel name為key查詢其最新的msg id,把msg id自增一后作為這條消息的id;

E msg chat server把分配好id的消息插入本地msg cache和msg DB[mysql/mongoDB]中;

F msg chat server給broker返回msg ack, ack = {producer uin, channel name, msg device id, msg time, msg id};

G broker把msg ack下發(fā)給producer;

H producer收到ack包后終止消息流程,如果在發(fā)送流程超時(shí)后仍未收到消息則轉(zhuǎn)到步驟1進(jìn)行重試,并計(jì)算重試次數(shù);

I 如果重試次數(shù)超過兩次依然失敗則提示“系統(tǒng)繁忙” or “網(wǎng)絡(luò)環(huán)境不佳,請(qǐng)主人稍后再嘗試發(fā)送”等,終止消息發(fā)送流程。

上面設(shè)計(jì)到了一個(gè)模塊圖中沒有的概念:msg cache,之所以沒有繪制出來,是因?yàn)閙sg cache的大小是可預(yù)估的,它只是用于消息去重判斷,所以只需存下去重msg key即可。假設(shè)msg chat server的服務(wù)人數(shù)是40 000人,消息發(fā)送頻率是1條/s,消息的生命周期是24 hour,消息key長(zhǎng)度是64B,那么這個(gè)cache大小 = 64B * (24 * 3600)s * 40000 = 221 184 000 000B,這個(gè)數(shù)字可能有點(diǎn)恐怖,如果是真實(shí)商業(yè)環(huán)境這個(gè)數(shù)字只會(huì)更小,因?yàn)闆]有人一天一夜不吃不喝不停發(fā)消息嘛。其本質(zhì)是一個(gè)hashset(C++中對(duì)應(yīng)的是unordered_set),物理存儲(chǔ)介質(zhì)當(dāng)然是共享內(nèi)存了。

[2016/03/10日:經(jīng)過思考,msg cache只需存下某個(gè)UIN在某個(gè)device上的最新的消息時(shí)間即可,msg cache的結(jié)構(gòu)應(yīng)為hashtable,以{UIN + device id}為key,以其最新的消息的發(fā)送時(shí)間(客戶端發(fā)送消息的時(shí)間)為value,不再考慮消息的生命周期。msg chat server每收到一條新消息就把新消息中記錄的發(fā)送時(shí)間與緩存中記錄的消息時(shí)間比較即可,如果新消息的時(shí)間小于這個(gè)msg pool記錄的時(shí)間即說明其為重復(fù)消息,大于則為新消息,并用新消息的msg time作為msg cache中對(duì)應(yīng)kv的value的最新值。假設(shè)UIN為4B,device id為4B,時(shí)間為4B,則msg cache的數(shù)據(jù)的size(不計(jì)算hashtable數(shù)據(jù)結(jié)構(gòu)本身占用的內(nèi)存size)為12B * 40000 = 480 000B,新msg pool完全與每條消息的lifetime無關(guān),這就大大下降了其內(nèi)存占用。

那么還有一個(gè)問題,如果用戶修改了手機(jī)的本地時(shí)間怎么辦?那就換做另一個(gè)參數(shù):本地手機(jī)時(shí)鐘累計(jì)運(yùn)行時(shí)長(zhǎng),手機(jī)出廠后其運(yùn)行累計(jì)時(shí)長(zhǎng)只會(huì)一直增加不會(huì)減小。

這個(gè)流程牽涉到一個(gè)比較重要的模塊:Counter,這個(gè)模塊其實(shí)都可以用Redis充當(dāng),怎么做你自己想^ _ ^。這個(gè)模塊自身的實(shí)現(xiàn)就是一個(gè)分布式的計(jì)數(shù)器,直接使用Redis也沒什么問題,但是最好的方法是采用消息id批發(fā)器的方式,msg chat server到Counter每次批發(fā)一批id回來,然后分配給每個(gè)msg,當(dāng)使用完畢的時(shí)候再接著去Counter申請(qǐng)一批回來,以減輕Counter的壓力,具體的設(shè)計(jì)請(qǐng)參考專利《即時(shí)消息的處理方法和裝置》[參考文檔9]。

上面還有一個(gè)概念未敘述到:發(fā)送端的消息郵箱{有人稱為消息盒子,或者某大廠稱之為客戶端消息db},它存儲(chǔ)了所有本地發(fā)送出去的消息,其中沒有服務(wù)端分配的msg id的消息都被認(rèn)為是發(fā)送失敗的消息,待用戶主動(dòng)嘗試發(fā)送或者網(wǎng)絡(luò)環(huán)境重新穩(wěn)定后可以有客戶端嘗試重新發(fā)送流程。

用戶查看消息郵箱中的本地歷史消息的時(shí)候,就要依據(jù)msg id把消息排序好展現(xiàn)給用戶。至于用戶發(fā)送過程中看到的消息可以認(rèn)為是本地消息的一個(gè)cache,每個(gè)channel最多給他展現(xiàn)100條,這100條消息的排序要依照每條消息的發(fā)出時(shí)間或者是消息的接收時(shí)間[這個(gè)接收到的消息時(shí)間以消息到達(dá)本機(jī)時(shí)的本地時(shí)鐘為依據(jù)]。當(dāng)用戶要查看超出數(shù)目如100條消息之外的消息,客戶端要引導(dǎo)用戶去走歷史消息查看流程。

3消息狀態(tài)部分流程

在進(jìn)行消息的發(fā)送流程中,msg chat server充當(dāng)了消息的處理者,其實(shí)消息的發(fā)送流程就可以認(rèn)為是一次客戶端與服務(wù)端進(jìn)行簡(jiǎn)單的“心跳邏輯”的過程,這個(gè)過程msg chat server[實(shí)際上就是下面提到的heartbeat server]還要完成如下部分消息狀態(tài)處理邏輯:

1 heartbeat server到Router中直接修改producer的狀態(tài)為在線;

2 heartbeat server要把client連接的broker的id以及其最新登錄時(shí)間更新至Router中;

至于Router具體的構(gòu)造,下一章節(jié)會(huì)敘述到。

4關(guān)于長(zhǎng)文本消息

還有一個(gè)問題,如果消息超過服務(wù)端規(guī)定的短文本消息的最大長(zhǎng)度怎么辦?

一種方法是干脆丟棄,拒絕給客戶端發(fā)送出去,貌似用戶體驗(yàn)沒那么好。

還有另一種方法,分片。用分片的方法拆成若干條短消息,每條短消息由客戶端或者服務(wù)端自己給他分配好序列號(hào),待用戶收到的時(shí)候再拼裝起來。其本質(zhì)跟tcp層處理大package時(shí)拆分若干個(gè)子packet道理一樣。

長(zhǎng)文本如果能借用第二種方法處理,發(fā)送圖片是不是也可以這么干?其本質(zhì)都是數(shù)據(jù)嘛,語音和視頻數(shù)據(jù)的處理亦不外乎如是。

四 消息處理以及消息投遞流程

上述的消息發(fā)送流程中,msg chat server把分配的msg id的消息返回給producer后,還要繼續(xù)進(jìn)行消息的投遞。消息的投遞涉及到一系列的技巧,涉及到消息的訂閱者能否不重不漏地在消息還“活著”的消息,這些技巧其實(shí)也沒什么神秘之處,下面的流程會(huì)詳細(xì)地描述到。

1消息投遞流程

消息投遞,顧名思義,就是消息的下發(fā)而已,有人美其名曰消息Push流程。

如果說消息的發(fā)送 = msg req + msg ack, 那么消息的投遞就簡(jiǎn)單多了:

A msg chat server到channel成員列表服務(wù)數(shù)據(jù)庫(kù)拉取成員列表;

B msg chat server循環(huán)到Router中查看每個(gè)成員是否在線,如果在線則獲取成員連接的broker接口機(jī)地址;

C msg chat server發(fā)送消息到broker;

D broker接收到消息后就把msg下發(fā)給客戶端;

E msg chat server循環(huán)給在線的成員發(fā)送完消息后,把msg id放入其channel在msg queue中的msg id list的末尾;

F 如果msg queue的msg id list超過長(zhǎng)度限制,則要?jiǎng)h除掉鏈表的head部分的若干id,以保證list長(zhǎng)度不超過系統(tǒng)規(guī)定的參數(shù);

G 流程結(jié)束。

消息的投遞是不是顯得輕松多了,至于"被認(rèn)為在線"客戶端有沒有收到msg,msg chat server壓根就不管!

這個(gè)流程牽涉到另一個(gè)比較重要的模塊:router,它其實(shí)也可以用Redis充當(dāng),利用Redis的bitmap記錄所有用戶的狀態(tài),0標(biāo)示離線,1表示在線,然后再利用hashtable存儲(chǔ)每個(gè)用戶登錄的broker的id和最新登錄時(shí)間。

至于msg queue模塊,其實(shí)也是一個(gè)hashtable,key為channel的name或者id,value就是一個(gè)msg id list。

聽說Redis最近要添加Bloom Filter,那就更好玩了,關(guān)鍵就看其能否應(yīng)對(duì)刪除操作,如果有刪除接口,把它當(dāng)做bitmap玩玩倒也無妨。

五 心跳流程

一個(gè)客戶端要維持與服務(wù)端的session有效,就須與其broker維持一個(gè)心跳流程,以被認(rèn)為是處于在線狀態(tài)。那么,最基本的問題就是:心跳時(shí)長(zhǎng)。

這個(gè)問題會(huì)讓很多移動(dòng)開發(fā)者頭疼許久,最基本的要根據(jù)網(wǎng)絡(luò)環(huán)境來設(shè)計(jì)不同的心跳時(shí)長(zhǎng):譬如有線環(huán)境把頻率設(shè)置為10s,wifi環(huán)境下這個(gè)頻率設(shè)計(jì)為30s,在3G或者4G環(huán)境下設(shè)置為1.5分鐘,在2G環(huán)境下設(shè)置為4分鐘??傊湓瓌t是:網(wǎng)絡(luò)環(huán)境越差勁,心跳時(shí)間間隔越長(zhǎng)。

心跳時(shí)間間隔長(zhǎng)那么其心跳頻率就低,其消息收發(fā)速度就慢。

進(jìn)一步,無線環(huán)境下這個(gè)心跳時(shí)間長(zhǎng)度不是固定不變的,具體時(shí)長(zhǎng)要由服務(wù)端進(jìn)行判斷,如果無線環(huán)境下假設(shè)起始心跳間隔是4分鐘,客戶端連續(xù)最近3次心跳有一次失敗,那就把時(shí)長(zhǎng)修改為2分鐘,如果有兩次失敗就修改為1分鐘,如果連續(xù)3次超時(shí)未上報(bào)心跳,就認(rèn)為客戶端離線!

(2016/03/10): 經(jīng)過今日思考,覺得上面這一段的例子中參數(shù)是錯(cuò)誤的,它違背了上上段敘述的原則,當(dāng)出現(xiàn)心跳超時(shí)的情況后就說明網(wǎng)絡(luò)環(huán)境發(fā)生了變化,但是僅僅憑借一次超時(shí)還不足以說明網(wǎng)絡(luò)環(huán)境變好還是變壞。其實(shí)把心跳時(shí)長(zhǎng)的問題轉(zhuǎn)換一個(gè)角度進(jìn)行思考:當(dāng)知道了前三次或者前兩次實(shí)際心跳時(shí)間間隔,怎么預(yù)測(cè)接下來的心跳時(shí)間間隔?其本質(zhì)就是一個(gè)拉格朗日外插法的應(yīng)用而已。我這里不多敘述,僅僅給出一種方法:如果已經(jīng)知道最近兩次心跳時(shí)間間隔為iv1和iv2,則接下來的給客戶端返回的iv3 = k * ((iv1 + iv2) / 2),如果iv1 > iv2,則k = 0.8,否則k = 1.2,這兩個(gè)值也僅僅是經(jīng)驗(yàn)值而已,具體怎么取值需系統(tǒng)設(shè)計(jì)者自己權(quán)衡,但足以自適應(yīng)一些復(fù)雜的網(wǎng)絡(luò)環(huán)境,如坐在火車上使用移動(dòng)網(wǎng)絡(luò)的APP。

如果系統(tǒng)設(shè)計(jì)者覺得麻煩,就可以把上面的值修改為經(jīng)驗(yàn)參數(shù)值,如無線環(huán)境下假設(shè)起始心跳間隔是4分鐘,客戶端連續(xù)最近3次心跳有一次失敗,那就把時(shí)長(zhǎng)修改為4.5分鐘,如果有兩次失敗就修改為5.5分鐘,如果連續(xù)3次超時(shí)未上報(bào)心跳,就認(rèn)為客戶端離線!

解決了心跳時(shí)長(zhǎng)問題,再來看看具體的心跳流程:

A 客戶端發(fā)送心跳包hearbeat,heartbeat = {uin, device id, network type, list{channel name:newest channel msg id},other info},即heartbeat包要上報(bào)uin所在的所有channel,以及本地歷史消息記錄中每個(gè)channel最新的消息的id;

B broker把心跳包轉(zhuǎn)給專門處理心跳邏輯的msg chat server[以下稱為heartbeat server];

C heartbeat server到Router中更新client的在線狀態(tài)以及登錄的broker的id和最新登錄時(shí)間;

D heartbeat server到Counter服務(wù)器循環(huán)查詢每個(gè)channel的最新消息id,如果客戶端上報(bào)的id與這個(gè)id不等,就發(fā)送一條msg通知msg chat server,msg = {uin, channel name, client newest msg id of channel};

E msg chat server收到這條消息后,重新啟動(dòng)消息下發(fā)邏輯,到msg queue中取出所有的大于{client newest msg id of channel}的id列表;

F msg chat server依據(jù)list中的id到消息存儲(chǔ)服務(wù)器中依次取出每個(gè)msg[取不到也就表示這個(gè)消息因?yàn)槌瑫r(shí)而被消息存儲(chǔ)服務(wù)器刪除了];

G msg chat server把這些消息作為"未讀消息"下發(fā)給客戶端;

H heartbeat server根據(jù)Router存儲(chǔ)的客戶端的最近三次的登錄時(shí)間,調(diào)整session的心跳時(shí)間間隔,作為心跳回包的一部分參數(shù)值給客戶端下發(fā)heartbeat ack包,其他數(shù)據(jù)包括其所在的每個(gè)channel的最新消息的msg id;

I heartbeat server定時(shí)地到Router中檢查所有客戶端的最新登錄時(shí)間,如果超過其session有效時(shí)間,就把其state置為“離線”,并刪除其登錄服務(wù)id等數(shù)據(jù);

J 客戶端收到heartbeat ack包后,修改下次心跳時(shí)間,并依據(jù)每個(gè)channel的最新的msg id與本地消息郵箱中對(duì)應(yīng)的channel的最新消息id做對(duì)比,如果id不等,客戶端可以啟動(dòng)拉取消息流程或者等待server端把這些消息下發(fā)過來。

上面提到的一個(gè)詞:newest channel id 或者 client newest msg id of channel,其意思就是消息接收者所在的channel的所擁有的本地消息的最新id。一般地,如果server端的Counter能夠穩(wěn)定地提供服務(wù),channel中的msg id應(yīng)該是連續(xù)的,如果客戶端檢測(cè)到msg id不連續(xù),可以把不連續(xù)處的id作為newest channel id,要求server端再把這個(gè)msg id以后的消息重發(fā)下來,這就要求client有消息去重判斷的功能。

每次收到server端下發(fā)的消息后,用戶必須更新local newest channel msg id,把消息id窗口往前推進(jìn),不要因?yàn)閕d不連續(xù)而一直不更新這個(gè)值,因?yàn)榉?wù)端的服務(wù)也不一定超級(jí)穩(wěn)定。

上面的一段我寫的稍嫌“囋”一些,其實(shí)其思想類似于tcp的滑動(dòng)窗口思想,自己做對(duì)比去理解之。

step H要求router至少要存儲(chǔ)client最新四次的登錄時(shí)間,然后根據(jù)這三次時(shí)間間隔以及網(wǎng)絡(luò)類型修改下次心跳時(shí)間間隔有效時(shí)長(zhǎng)。我這里已經(jīng)很明了的寫出了原理了,至于怎么取值可以依據(jù)上面提到的原理修改相關(guān)參數(shù)[這個(gè)得需要測(cè)試才能得出一些關(guān)鍵數(shù)據(jù),但是這個(gè)參數(shù)值應(yīng)該跟我本文提到的參數(shù)值相差無幾]。

至于step J敘述到的client是否啟用消息拉取邏輯,取決于你的服務(wù)類型。具體場(chǎng)景分別對(duì)待,本文不會(huì)再設(shè)計(jì)消息的pull流程。

其實(shí)結(jié)合第4章節(jié)以及本章節(jié),用流行的術(shù)語來說,消息的下發(fā)就是微信所謂的"是參考Activesyec,SYNC協(xié)議"[參考文檔7]流程,江湖人稱推拉相結(jié)合的過程。

這個(gè)過程可以用一副流程圖做參考:

注意上圖與本文一些名詞的用法不同,它的所謂的“離線消息”,咱本文中被稱為"未讀消息"。隨著本章節(jié)的結(jié)束,IM的主要流程就描述完畢。

六 消息存儲(chǔ)服務(wù)

由于本文敘述的消息系統(tǒng)是一個(gè)在線消息模型,所以msg db中存儲(chǔ)的超時(shí)消息必須被刪除。首先db的大小可以根據(jù)服務(wù)人數(shù)的數(shù)目以及每條消息的時(shí)長(zhǎng)估算出來。

其次,簡(jiǎn)單的im系統(tǒng)中不考慮用戶的等級(jí)的話,可以認(rèn)為所有的msg都是平等的有相同的lifetime。但是如果區(qū)分了用戶優(yōu)先級(jí),則其消息lifetime也就不等,就得有服務(wù)等級(jí)不同用戶的msg db[其實(shí)優(yōu)先級(jí)越高,其消息存儲(chǔ)越久,企業(yè)付出了存儲(chǔ)成本,某種神秘的力量也就越容易獲取到其聊天數(shù)據(jù)]。

最后,啟動(dòng)一個(gè)定時(shí)消息刪除模塊,它定時(shí)啟動(dòng)刪除msg db中超時(shí)的msg即可。

七 其他類型消息

由于本文只是描述文本型短消息服務(wù)的相關(guān)流程,如果還要考慮圖片、聲音和視頻流服務(wù),這些消息就會(huì)被稱為富媒體消息。最基本的富媒體消息應(yīng)該有一個(gè)文本消息與之對(duì)應(yīng),文本消息中包含了這些富媒體文件的url地址或者其他方式定義的地址。消費(fèi)者拉倒這樣類型的消息,就可以根據(jù)消息地址去拉取富媒體文件。

至于富媒體文件怎么存儲(chǔ),個(gè)人建議可以借助目前成熟的第三方服務(wù)平臺(tái),如借助七牛的云圖片服務(wù)[我舉個(gè)栗子而已,沒收任何費(fèi)用,無做廣告的嫌疑^ _ ^]存儲(chǔ)服務(wù)存儲(chǔ)圖片,借助騰訊云的視頻服務(wù)能力處理語音和視頻消息。

富媒體消息拉取和上傳都要經(jīng)過你的Relay接口,這個(gè)服務(wù)接口因?yàn)檫壿嬇c正常的文本消息差別很大,所以建議獨(dú)立做一個(gè)接口叫做Relay模塊,以與broker作區(qū)分,也為以后更換第三方服務(wù)廠商打好基礎(chǔ)。

如果你廠有錢又有人,那就考慮自己做富媒體文件的存儲(chǔ)吧,此時(shí)在邏輯層應(yīng)該有個(gè)對(duì)應(yīng)的模塊叫做rich text msg server[下面簡(jiǎn)稱為rich server],其邏輯應(yīng)該為:

A 不管是語音還是視頻,client采用合適的文件格式格式化后壓縮好,然后再分片上傳到relay,每個(gè)分片要分好分片序號(hào);

B Relay收到這些分片后把數(shù)據(jù)透?jìng)鹘orich server;

C rich server先把分片數(shù)據(jù)存儲(chǔ)在cache中,當(dāng)收到最后一個(gè)分片的時(shí)候查收缺失的分片;

D rich server如果發(fā)現(xiàn)了缺失分片,就把缺失分片列表告知客戶端,讓其重傳即可;

E 待所有分片都收集好,rich server就可以再次把數(shù)據(jù)拼裝好放入mongodb或者其他什么db中。

整個(gè)邏輯就完成了,是不是也很easy的^ _ ^。

八 方案總結(jié)

這套IM系統(tǒng),總體有以下特點(diǎn):

1 其完備的IM系統(tǒng)設(shè)計(jì);

2 以Counter作為系統(tǒng)的心臟驅(qū)動(dòng)整個(gè)系統(tǒng)的流程設(shè)計(jì);

3 客戶端的消息流程方案有所涉及;

4 保證服務(wù)質(zhì)量的情況下保障消息不重不漏;

5 詳細(xì)敘述了消息下發(fā)的技術(shù)流程;

6 給出了自己設(shè)計(jì)的智能心跳方案;

7 對(duì)長(zhǎng)消息、圖片、語音和視頻等“長(zhǎng)數(shù)據(jù)”的處理給出了自己的解決方法;

8 天生的分布式能力,保證其多IDC的部署能力;

9 盡個(gè)人能力,不斷優(yōu)化中......


來源:http://www.52im.net/thread-151-1-1.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,545評(píng)論 19 139
  • 分布式開放消息系統(tǒng)(RocketMQ)的原理與實(shí)踐 來源:http://www.itdecent.cn/p/453...
    meng_philip123閱讀 13,222評(píng)論 6 104
  • 實(shí)時(shí)消息協(xié)議---流的分塊 版權(quán)聲明: 版權(quán)(c)2009 Adobe系統(tǒng)有限公司。全權(quán)所有。 摘要: 本備忘錄描...
    一個(gè)人zy閱讀 2,063評(píng)論 0 9
  • kafka的定義:是一個(gè)分布式消息系統(tǒng),由LinkedIn使用Scala編寫,用作LinkedIn的活動(dòng)流(Act...
    時(shí)待吾閱讀 5,538評(píng)論 1 15
  • 一兒女又一次聚在一起,談?wù)撃赣H的去留。牙齒差不多都脫落干凈的母親,坐在沙發(fā)上,她抿緊嘴唇,像極了那張既是...
    凝固的火閱讀 168評(píng)論 0 0

友情鏈接更多精彩內(nèi)容