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