微信為什么不丟消息?

2018-09-06 17:20 更新
上一章和大家分享了《http如何像tcp一樣實(shí)時(shí)的收消息?》, 本章來(lái)聊一聊即時(shí)通訊(Instant Messaging,后簡(jiǎn)稱(chēng)im)消息的可靠投遞。

一、報(bào)文類(lèi)型

im的客戶(hù)端與服務(wù)器通過(guò)發(fā)送報(bào)文(也就是網(wǎng)絡(luò)包)來(lái)完成消息的傳遞,報(bào)文分為三種

請(qǐng)求報(bào)文(request,后簡(jiǎn)稱(chēng)為為R)

應(yīng)答報(bào)文(acknowledge,后簡(jiǎn)稱(chēng)為A)

通知報(bào)文(notify,后簡(jiǎn)稱(chēng)為N),這三種報(bào)文的解釋如下:

報(bào)文類(lèi)型
R:客戶(hù)端主動(dòng)發(fā)送給服務(wù)器的報(bào)文
A:服務(wù)器被動(dòng)應(yīng)答客戶(hù)端的報(bào)文,一個(gè)A對(duì)應(yīng)一個(gè)R
N:服務(wù)器主動(dòng)發(fā)送給客戶(hù)端的報(bào)文

二、普通消息投遞流程

用戶(hù)A給用戶(hù)B發(fā)送一個(gè)“你好”,流程如下:
消息投遞流程(上)

1)client-A向im-server發(fā)送一個(gè)消息請(qǐng)求包,即msg:R

2)im-server在成功處理后,回復(fù)client-A一個(gè)消息響應(yīng)包,即msg:A

3)如果此時(shí)client-B在線(xiàn),則im-server主動(dòng)向client-B發(fā)送一個(gè)消息通知包,即msg:N(當(dāng)然,如果client-B不在線(xiàn),則消息會(huì)存儲(chǔ)離線(xiàn))


三、上述消息投遞流程出現(xiàn)的問(wèn)題

從流程圖中容易看到,發(fā)送方client-A收到msg:A后,只能說(shuō)明im-server成功接收到了消息,并不能說(shuō)明client-B接收到了消息。在若干場(chǎng)景下,可能出現(xiàn)msg:N包丟失,且發(fā)送方client-A完全不知道,例如:

1)服務(wù)器崩潰,msg:N包未發(fā)出

2)網(wǎng)絡(luò)抖動(dòng),msg:N包被網(wǎng)絡(luò)設(shè)備丟棄

3)client-B崩潰,msg:N包未接收

結(jié)論是悲觀(guān)的:接收方client-B是否有收到msg:N,發(fā)送方client-A完全不可控,那怎么辦呢?


四、應(yīng)用層確認(rèn)+im消息可靠投遞的六個(gè)報(bào)文

upd是一種不可靠的傳輸層協(xié)議,tcp是一種可靠的傳輸層協(xié)議,tcp是如何做到可靠的?答案是:超時(shí)、重傳、確認(rèn)。

要想實(shí)現(xiàn)應(yīng)用層的消息可靠投遞,必須加入應(yīng)用層的確認(rèn)機(jī)制,即:要想讓發(fā)送方client-A確保接收方client-B收到了消息,必須讓接收方client-B給一個(gè)消息的確認(rèn),這個(gè)應(yīng)用層的確認(rèn)的流程,與消息的發(fā)送流程類(lèi)似:
消息投遞流程(下)

4)client-B向im-server發(fā)送一個(gè)ack請(qǐng)求包,即ack:R

5)im-server在成功處理后,回復(fù)client-B一個(gè)ack響應(yīng)包,即ack:A

6)則im-server主動(dòng)向client-A發(fā)送一個(gè)ack通知包,即ack:N

至此,發(fā)送“你好”的client-A,在收到了ack:N報(bào)文后,才能確認(rèn)client-B真正接收到了“你好”。

會(huì)發(fā)現(xiàn),一條消息的發(fā)送,分別包含(上)(下)兩個(gè)半場(chǎng),即msg的R/A/N三個(gè)報(bào)文,ack的R/A/N三個(gè)報(bào)文,一個(gè)應(yīng)用層即時(shí)通訊消息的可靠投遞,共涉及6個(gè)報(bào)文,這就是im系統(tǒng)中消息投遞的最核心技術(shù)。


五、可靠消息投遞存在什么問(wèn)題

期望六個(gè)報(bào)文完成消息的可靠投遞,但實(shí)際情況,msg:N,ack:N這兩個(gè)報(bào)文都可能丟失(原因如第二章所述,可能是服務(wù)器奔潰、網(wǎng)絡(luò)抖動(dòng)、或者客戶(hù)端奔潰),此時(shí)client-A都收不到期待的ack:N報(bào)文,即client-A不能確認(rèn)client-B是否收到“你好”,但這兩個(gè)報(bào)文的丟失對(duì)應(yīng)的業(yè)務(wù)影響又大有不同:

1)msg:N包丟失,業(yè)務(wù)結(jié)果是client-B沒(méi)有收到消息

2)ack:N包丟失,業(yè)務(wù)結(jié)果是client-B收到了消息,只是client-A不知道而已

那怎么辦呢?


六、消息的超時(shí)與重傳

client-A發(fā)出了msg:R,收到了msg:A之后,在一個(gè)期待的時(shí)間內(nèi),如果沒(méi)有收到ack:N,client-A會(huì)嘗試將msg:R重發(fā)??赡躢lient-A同時(shí)發(fā)出了很多消息,故client-A需要在本地維護(hù)一個(gè)等待ack隊(duì)列,并配合timer超時(shí)機(jī)制,來(lái)記錄哪些消息沒(méi)有收到ack:N,以定時(shí)重發(fā)。

等待ack隊(duì)列

一旦收到了ack:N,說(shuō)明client-B收到了“你好”消息,對(duì)應(yīng)的消息將從“等待ack隊(duì)列”中移除。


七、消息的重傳存在什么問(wèn)題

第五章提到過(guò),msg:N,ack:N都有可能丟失:

1)msg:N報(bào)文丟失,說(shuō)明client-B之前壓根沒(méi)有收到“你好”報(bào)文,超時(shí)與重傳機(jī)制十分有效

2)ack:N報(bào)文丟失,說(shuō)明client-B之前已經(jīng)收到了“你好”報(bào)文(只是client-A不知道而已),超時(shí)與重傳機(jī)制將導(dǎo)致client-B收到重復(fù)的消息,那怎么辦呢?


八、消息的去重

解決方法也很簡(jiǎn)單,由發(fā)送方client-A生成一個(gè)消息去重的msgid,保存在“等待ack隊(duì)列”里,同一條消息使用相同的msgid來(lái)重傳,供client-B去重,而不影響用戶(hù)體驗(yàn)。


九、其他

1)上述設(shè)計(jì)理念,由客戶(hù)端重傳,可以保證服務(wù)端無(wú)狀態(tài)性(架構(gòu)設(shè)計(jì)基本準(zhǔn)則)

2)如果client-B不在線(xiàn),im-server保存了離線(xiàn)消息后,要偽造ack:N發(fā)送給client-A


十、總結(jié)

1)im系統(tǒng)是通過(guò)超時(shí)、重傳、確認(rèn)、去重的機(jī)制來(lái)保證消息的可靠投遞,不丟不重

2)一個(gè)“你好”的發(fā)送,包含上半場(chǎng)msg:R/A/N與下半場(chǎng)ack:R/A/N的6個(gè)報(bào)文

3)im系統(tǒng)難以做到系統(tǒng)層面的不丟不重,只能做到業(yè)務(wù)層面的不丟不重


末了,微信的消息是不是這么發(fā)送的,偶不太清楚,清楚的同學(xué)可以說(shuō)一說(shuō)。


以上內(nèi)容是否對(duì)您有幫助:
在線(xiàn)筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)