WebIM原理解析
什么是IM
IM(Instant Messaging)即時(shí)通信,是一種通過網(wǎng)絡(luò)進(jìn)行實(shí)時(shí)通信的系統(tǒng),允許兩人或多人使用網(wǎng)絡(luò)即時(shí)的傳遞文字消息、文件、語音與視頻交流,通常以網(wǎng)站、軟件或者移動(dòng)app的方式提供服務(wù)。自從互聯(lián)網(wǎng)的興起,IM就一直和我們的生活息息相關(guān),日常聊天、工作、打車、外賣、購(gòu)物等等,可以說,我們現(xiàn)在的生活已經(jīng)幾乎離不開IM。
IM歷史
最早人們的通信靠的是郵件,需要人去郵局寄信,然后郵遞員再經(jīng)過漫長(zhǎng)的旅程送達(dá)對(duì)方。從前車馬很慢,一生只夠愛一個(gè)人,咳咳。從郵件到傳呼機(jī)再到有線電話,無線電話,最后隨著互聯(lián)網(wǎng)的發(fā)展IM迎來了它的新生。
最早的即時(shí)通信軟件叫做ICQ,他是四名以色列青年于1996年7月成立的Mirabilis公司推出的產(chǎn)品。然后騰訊接著推出了OICQ。
圖片取自網(wǎng)絡(luò)以及現(xiàn)在主流的聊天應(yīng)用
Whatsapp 美國(guó)
Line 日本
Kakao Talk 韓國(guó)
WeChat 中國(guó)
Facebook Messenger 美國(guó)
IM特性
IM的四大特性,有效性、實(shí)時(shí)性、一致性以及安全性,這四點(diǎn)可以總結(jié)為兩個(gè)字,可靠,那么如何實(shí)現(xiàn)一個(gè)可靠的Web IM應(yīng)用呢?業(yè)界其實(shí)已有很成熟的IM的方案,我們會(huì)從通信協(xié)議、應(yīng)用層、通信數(shù)據(jù)格式、以及相應(yīng)策略方面給大家闡述一個(gè)可靠的IM應(yīng)用都需要哪些東西。
IM通信協(xié)議
為了保證可靠,傳輸層我們一般使用TCP協(xié)議,它是面向連接,可靠的流協(xié)議,實(shí)行“順序控制”和“重發(fā)機(jī)制”,還有“流(流量)控制”、“擁塞控制”、提高網(wǎng)絡(luò)利用率等眾多功能。
在PC的早期時(shí)代,IM采用的是http短輪詢的模式(圖1),它會(huì)定期、高頻地輪詢服務(wù)器端消息。
圖1它的缺點(diǎn)也很明顯,會(huì)有大量無用的請(qǐng)求,用戶端也會(huì)非常耗電耗流量,而服務(wù)端面對(duì)高頻QPS,內(nèi)存資源壓力也會(huì)非常大
對(duì)于短輪詢的優(yōu)化,就出現(xiàn)了長(zhǎng)輪詢(圖2)。相對(duì)短輪詢,它大幅降低了無用輪詢導(dǎo)致的網(wǎng)絡(luò)與功耗開銷,但是服務(wù)端懸掛住請(qǐng)求,只是降低了入口請(qǐng)求的QPS,并沒有降低服務(wù)器的資源開銷,假如有1000個(gè)請(qǐng)求在等待,那就意味著有1000個(gè)線程掛起,被輪詢占用消息存儲(chǔ)資源。
圖2為了更好的解決實(shí)時(shí)性問題,IM領(lǐng)域經(jīng)歷過幾次技術(shù)的迭代升級(jí),從簡(jiǎn)單、低效的短輪詢逐步升級(jí)到相對(duì)效率可控的長(zhǎng)輪詢,然后隨著h5的出現(xiàn),全雙工(Full-duplex)的websocket(圖3)徹底解決了服務(wù)端推送的問題。用戶側(cè)和服務(wù)端利用websocket建立長(zhǎng)連接后,雙方就可以同時(shí)進(jìn)行雙向的數(shù)據(jù)傳輸了。
圖3服務(wù)器的壓力也不再是連接數(shù),而是每一條消息事物。
應(yīng)用層可靠
在底層協(xié)議的保障后,我們的消息就完全可靠了嗎?那肯定不是,我們的服務(wù)大致是這樣的,用戶發(fā)送消息給服務(wù)端,服務(wù)端存儲(chǔ)消息,再返回給用戶,并把該條消息推給另一個(gè)用戶,在下圖(圖4)流程中,每個(gè)環(huán)節(jié)都可能存在消息丟失的風(fēng)險(xiǎn)。
用戶1發(fā)送到IM服務(wù)的過程中
IM服務(wù)器存儲(chǔ)失敗
用戶1等待服務(wù)器響應(yīng)超時(shí)
服務(wù)器往用戶2推送消息時(shí)超時(shí)、錯(cuò)誤
ack機(jī)制
為了解決用戶1到服務(wù)端的可靠性問題,我們參考TCP協(xié)議的握手、重傳機(jī)制,來保障應(yīng)用層消息的可靠性,在發(fā)送消息后會(huì)有一個(gè)定時(shí)超時(shí),在超時(shí)后根據(jù)需要,從ack隊(duì)列中取出消息重推(圖5)。一般情況顯示發(fā)送失敗,交由用戶手動(dòng)重發(fā)(比如消息左邊一個(gè)紅色感嘆號(hào))。
圖5用戶發(fā)送消息,服務(wù)端收到消息后,生成該條消息的唯一id,以ack的形式回傳給用戶側(cè),用戶側(cè)再更新該條消息id值,后續(xù)IM功能中的撤回,去重 ,重發(fā)等邏輯都會(huì)用到該id。
重發(fā)與去重機(jī)制
在服務(wù)端推送消息時(shí),如出錯(cuò)或超時(shí),會(huì)有相應(yīng)的重發(fā)機(jī)制。比如,設(shè)置錯(cuò)誤或超時(shí)重試三次。
有時(shí)因?yàn)橐恍┚W(wǎng)絡(luò)或其他情況。服務(wù)端會(huì)有相應(yīng)的重發(fā)邏輯,在推送消息出現(xiàn)重發(fā)時(shí),用戶端設(shè)置對(duì)應(yīng)的去重邏輯。我們會(huì)對(duì)消息列表的最新的5條消息進(jìn)行排序和去重。只取最新5條主要考慮到排序與去重的效率,用戶的焦點(diǎn)主要在最新的幾條消息,如果因?yàn)橐恍┚W(wǎng)絡(luò)原因在消息列表較遠(yuǎn)處插入消息,會(huì)造成用戶的困惑與遺漏,另外5條消息的時(shí)間差基本滿足大多數(shù)異常情況的消息丟失場(chǎng)景。如果還有消息遺漏的情況,用戶在刷新消息列表時(shí)會(huì)以http的形式拉取歷史消息(當(dāng)前會(huì)話的消息)。
斷線重連(Qos機(jī)制)
websocket 有error和close事件,我們?cè)诒O(jiān)聽這兩個(gè)事件后進(jìn)行相應(yīng)的重連邏輯,其中在close事件里不對(duì)狀態(tài)碼1000(正常關(guān)閉)做重連處理。
具體邏輯如下
??/***?reconnect*?@param?{String}?wsurl?websocket?server?location*/reconnect(wsurl)?{if?(this._reconnectingLock)?returnthis._reconnectingLock?=?truethis._ws.close()let?delayTime?=?Math.pow(2,?this._reconnectCount++)?-?1delayTime?=?delayTime?>?30???30?:?delayTimeconsole.info(`delay?${delayTime}s...`)setTimeout(()?=>?{console.info(`try?${this._reconnectCount}?time?reconnect...`)this.create(wsurl)this._reconnectingLock?=?false},?delayTime?*?1000?+?100)}首先我們有個(gè)重連鎖,在正在進(jìn)行重連時(shí)不重復(fù)觸發(fā)重連邏輯,在保證ws完全關(guān)閉的情況下,會(huì)以重連次數(shù)的二次冪作為重連的時(shí)間間隔,并且在重試時(shí)間達(dá)到30s后不再遞增。這樣處理的邏輯一是為了保證在斷線或者異常時(shí)能馬上進(jìn)行重連的嘗試,但是會(huì)逐漸減緩重連嘗試,假如是服務(wù)器負(fù)載等問題造成的斷開,也避免一直頻繁連接給服務(wù)器造成壓力。
消息就不會(huì)丟了嗎
我們的ack+超時(shí)重傳+消息去重,能解決大部分消息推送丟失的問題,但比如服務(wù)器宕機(jī),電腦手機(jī)息屏,手機(jī)切換后臺(tái)等等造成連接斷開,通道不可用(圖6)。服務(wù)端在這個(gè)期間推送消息,那如何保證用戶能收到完整的消息?
圖6一般在這個(gè)時(shí)候,我們會(huì)在重連或者用戶窗口可視時(shí)對(duì)消息進(jìn)行完整性檢查,會(huì)以http的形式拉取這段時(shí)間的消息,以最后一條消息的時(shí)間戳作為參數(shù)拉取這個(gè)時(shí)間段的消息,或者拉取后端會(huì)話(session)維度的消息。
心跳機(jī)制
websocket的連接是無感知的虛擬連接,中間鏈路出現(xiàn)一些異常情況斷開時(shí)兩邊不會(huì)感知到,為了保證服務(wù)的可靠性,以及降低服務(wù)器的開銷,我們會(huì)有對(duì)應(yīng)的心跳機(jī)制(圖7),來檢測(cè)連接是否正常,從而保持連接高可用。
圖7在心跳的基礎(chǔ)上,能及時(shí)支持客戶端的心跳斷線重連,比如兩次心跳沒有ack,或者心跳超時(shí)沒有收到ack(圖8)。
圖8心跳除了用于重連,還可用于及時(shí)釋放服務(wù)器以及業(yè)務(wù)資源,取決于IM的場(chǎng)景與策略。比如一些客服聊天場(chǎng)景,客服要盡可能接待更多的用戶,為及時(shí)釋放客服資源,服務(wù)端在用戶達(dá)到固定未收到心跳時(shí)間,及時(shí)斷開客服聊天,釋放相應(yīng)資源。
除此之外,心跳還有連接保活的功能。有時(shí)會(huì)遇到NAT(Network Address Translator)超時(shí)的情況。運(yùn)營(yíng)商維護(hù)NAT映射表時(shí),為了節(jié)約資源和降低 自身網(wǎng)關(guān)壓力,會(huì)定時(shí)清除沒有數(shù)據(jù)收發(fā)的連接,具體不在這里詳情闡述。但這個(gè)過程服務(wù)端和用戶端都無法感知,從而會(huì)影響消息收發(fā)。下圖(圖9)是一些運(yùn)營(yíng)商的NAT超時(shí)時(shí)間
圖9常用心跳方案
TCP keepalive
應(yīng)用層心跳
智能心跳
TCP的keepalive 作為系統(tǒng)層TCP/IP協(xié)議的已有實(shí)現(xiàn),操作系統(tǒng)默認(rèn)是關(guān)閉的,需要應(yīng)用層開啟,默認(rèn)配置項(xiàng)周期是2小時(shí),失敗后重試9次,超時(shí)75s,但是靈活性較差,所以我們一般不采用。應(yīng)用層心跳能靈活控制,更能結(jié)合業(yè)務(wù),具體策略如下圖(圖10)
圖10心跳的發(fā)送間隔,最簡(jiǎn)單的就是采用固定心跳時(shí)間,另外由于NAT超時(shí)時(shí)間以及網(wǎng)絡(luò)環(huán)境切換的不確定性,會(huì)有一些智能心跳方案,這里分享一下安卓版微信的智能心跳方案。
[MinHeat, MaxHeart]--心跳可選區(qū)間
successHeart--當(dāng)前成功心跳
curHeart--當(dāng)前心跳,初始值successHeart
heartStep—心跳增加步長(zhǎng)
successStep—穩(wěn)定期后的探測(cè)步長(zhǎng)
消息協(xié)議
好,通信上已經(jīng)基本沒有問題了,有了上述的策略后,IM的消息通信就基本能滿足大多數(shù)場(chǎng)景了,現(xiàn)在是消息協(xié)議的選型,也就是通信的數(shù)據(jù)格式,我們需要考慮的點(diǎn)有以下。
網(wǎng)絡(luò)數(shù)據(jù)大小:占用帶寬,傳輸效率
網(wǎng)絡(luò)數(shù)據(jù)安全性:敏感數(shù)據(jù)的網(wǎng)絡(luò)安全
編碼復(fù)雜度
協(xié)議通用性、大眾規(guī)范
業(yè)界常用的數(shù)據(jù)格式有以下
XMPP
Protobuf(Protocol Buffer)
JSON
私有二進(jìn)制
MQTT
定制化XML
一般我們會(huì)選擇Protobuf,它是Google公司內(nèi)部的混合語言數(shù)據(jù)標(biāo)準(zhǔn),用pb序列化后的大小是json的10分之一,xml格式的20分之一,是二進(jìn)制序列化的10分之一,并且基本上主流語言都已支持。不過考慮到上手的簡(jiǎn)單以及易調(diào)試,json也不是一個(gè)很壞的選擇,畢竟現(xiàn)在http的數(shù)據(jù)基本都是json,明文的數(shù)據(jù)包在調(diào)試上也會(huì)方便許多。
創(chuàng)建websocekt實(shí)例
websocket現(xiàn)在主流瀏覽器早已支持很久,并且在移動(dòng)端也基本沒有兼容性問題,官方提供的API十分簡(jiǎn)單,具體不在這里闡述,基本就四個(gè)事件,我們上述說到的功能都可以基于這四個(gè)事件去做。
展望
IM需要考慮的還有很多,不僅是前端,還有更多的是與服務(wù)端配合的策略。其他的還有一些IM里面常用功能,比如撤回、已讀未讀,以及自定義消息類型,多媒體消息之類的實(shí)現(xiàn),ws降級(jí),群組消息推送等問題,受篇幅限制,這里不再仔細(xì)展開。
升華一下,人類社會(huì)發(fā)展,需要協(xié)作產(chǎn)出,需要溝通交流。隨著5g的發(fā)展以及普及,即時(shí)通訊必然會(huì)往更廣的方向延伸,并且并不局限于簡(jiǎn)單的聊天。
總結(jié)
- 上一篇: 【C++】什么是对象?什么是类?
- 下一篇: BD 之 逻辑题 赛马