IM 即時通訊技術在多應用場景下的技術實現,以及性能調優(iOS視角)
(視頻見評論區)
2016年9月份我參加了 MDCC2016(中國移動開發者大會),
在 MDCC2016 上我做了關于 IM 相關分享,會上因為有50分鐘的時間限制 ,所以有很多東西都沒有展開,這篇是演講稿的博文版本,比會上講得更為詳細。有些演講時一筆帶過的部分,在文中就可以展開講講。
注:
- 本文中所涉及到的所有 iOS 端相關代碼,均已100%開源(不存在 framework ),便于學習參考。
- 本文側重移動端的設計與實現,會展開講,服務端僅僅屬于概述,不展開。
- 為大家在設計或改造優化 IM 模塊時,提供一些參考。
我現在任職于 LeanCloud(原名 AVOS
) 。LeanCloud 是國內較早提供 IM 服務的 Paas 廠商,提供 IM 相關的 SDK 供開發者使用,現在采納我們 IM 方案的 APP 有:知乎Live、掌上鏈家、懂球帝等等,在 IM 方面也積累了一些經驗,這次就在這篇博文分享下。
IM系列文章
IM 系列文章分為下面這幾篇:
- 《IM 即時通訊技術在多應用場景下的技術實現,以及性能調優(iOS視角)》 (本文)
- 《技術實現細節》
- 《有一種 Block 叫 Callback,有一種 Callback 做 CompletionHandler》
- 《防 DNS 污染方案》
本文是第一篇。
提綱
大規模即時通訊技術上的難點
思考幾個問題:
- 如何在移動網絡環境下優化電量,流量,及長連接的健壯性?現在移動網絡有2G、3G、4G各種制式,并且隨時可能切換和中斷,移動網絡優化可以說是面向移動服務的共同問題。
- 如何確保IM系統的整體安全?因為用戶的消息是個人隱私,因此要從多個層面來保證IM系統的安全性。
- 如何降低開發者集成門檻?
- 如何應對新的iOS生態下的政策以及結合新技術:比如HTTP/2、IPv6、新的APNs協議等。
應用場景
一個 IM 服務最大的價值在于什么?
可復用的長連接。一切高實時性的場景,都適合使用IM來做。
比如:
- 視頻會議、聊天、私信
- 彈幕、抽獎
- 互動游戲
- 協同編輯
- 股票基金實時報價、體育實況更新、
- 基于位置的應用:Uber、滴滴司機位置
- 在線教育
- 智能家居
下文會挑一些典型的場景進行介紹,并涉及到技術細節。
IM 發展史
基本的發展歷程是:輪詢、長輪詢、長連接。
挑一些代表性的技術做下介紹:
一般的網絡請求:一問一答
輪詢:頻繁的一問一答
長輪詢:耐心地一問一答
一種輪詢方式是否為長輪詢,是根據服務端的處理方式來決定的,與客戶端沒有關系。
短輪詢很容易理解,那么什么叫長輪詢?與短輪詢有什么區別。
舉個例子:
比如中秋節我們要做一個秒殺月餅的頁面,要求我們要實時地展示剩余的月餅數量,也就是庫存量。這時候如果要求你只能用短輪詢或長輪詢去做,怎么做呢?
長輪詢和短輪詢最大的區別是,短輪詢去服務端查詢的時候,不管服務端有沒有變化,服務器就立即返回結果了。而長輪詢則不是,在長輪詢中,服務器如果檢測到庫存量沒有變化的話,將會把當前請求掛起一段時間(這個時間也叫作超時時間,一般是幾十秒)。在這個時間里,服務器會去檢測庫存量有沒有變化,檢測到變化就立即返回,否則就一直等到超時為止,這就是區別。
(實際開發中不會使用長短輪詢來做這種需求,這里僅僅是為了說明兩者區別而做的一個例子。)
長輪詢曾被 Facebook 早起版本采納,示意圖如下:
HTML5 WebSocket: 雙向
參考: What are Long-Polling, Websockets, Server-Sent Events (SSE) and Comet?
我們可以看到,發展歷史是這樣:從長短輪詢到長連接,使用 WebSocket 來替代 HTTP。
其中長短輪詢與長短連接的區別主要有:
- 概念范疇不同:長短輪詢是應用層概念、長短連接是傳輸層概念
- 協商方式不同:一個 TCP 連接是否為長連接,是通過設置 HTTP 的 Connection Header 來決定的,而且是需要兩邊都設置才有效。而一種輪詢方式是否為長輪詢,是根據服務端的處理方式來決定的,與客戶端沒有關系。
- 實現方式不同:連接的長短是通過協議來規定和實現的。而輪詢的長短,是服務器通過編程的方式手動掛起請求來實現的。
在移動端上長連接是趨勢。
其最大的特點是節省 Header。
輪詢與 WebSocket 所花費的Header流量對比:
讓我們來作一個測試:
假設 Header 是871字節,
我們以相同的頻率 10W/s 去做網絡請求, 對比下輪詢與 WebSocket 所花費的 Header 流量:
Header 包括請求和響應頭信息。
出于兼容性考慮,一般建立 WebSocket 連接也采用 HTTP 請求的方式,那么從這個角度講:無論請求如何頻繁,都只需要一個 Header。
并且 Websocket 的數據傳輸是 frame 形式傳輸的,幀傳輸更加高效,對比輪詢的2個 Header,這里只有一個 Header 和一個 frame。
而 Websocket 的frame 僅僅用2個字節就代替了輪詢的871字節!
相同的每秒客戶端輪詢的次數,當次數高達 10W/s 的高頻率次數的時候,Polling 輪詢需要消耗665Mbps,而 WebSocket 僅僅只花費了1.526Mbps,將近435倍!!
數據參考:
下面探討下長連接實現方式里的協議選擇:
大家都在使用什么技術
最近做了兩個 IM 相關的問卷,累計產生了900多條的投票數據:
注:本次投票是發布在微博@iOS程序犭袁 ,鑒于微博關注機制,本數據只能反映出 IM 技術在 iOS 領域的使用情況,并不能反映出整個IT行業的情況。
下文會對這個投票結果進行下分析。
協議如何選擇?
IM 協議選擇原則一般是:易于拓展,方便覆蓋各種業務邏輯,同時又比較節約流量。后一點的需求在移動端 IM 上尤其重要。常見的協議有:XMPP、SIP、MQTT、私有協議。
我們這里只關注前三名,
名稱 | 優點 | 缺點 |
---|---|---|
XMPP | 優點:協議開源,可拓展性強,在各個端(包括服務器)有各種語言的實現,開發者接入方便; | 缺點:缺點也是不少,XML表現力弱、有太多冗余信息、流量大,實際使用時有大量天坑。 |
MQTT | 優點:協議簡單,流量少;訂閱+推送模式,非常適合Uber、滴滴的小車軌跡的移動。 | 缺點:它并不是一個專門為 IM 設計的協議,多使用于推送。IM 情景要復雜得多,pub、sub,比如:加入對話、創建對話等等事件。 |
私有協議 | 市面上幾乎所有主流IM APP都是是使用私有協議,一個被良好設計的私有協議優點非常明顯。優點:高效,節約流量(一般使用二進制協議),安全性高,難以破解; | 缺點:在開發初期沒有現有樣列可以參考,對于設計者的要求比較高。 |
一個好的協議需要滿足如下條件:高效,簡潔,可讀性好,節約流量,易于拓展,同時又能夠匹配當前團隊的技術堆棧。基于如上原則,我們可以得出: 如果團隊小,團隊技術在 IM 上積累不夠可以考慮使用 XMPP 或者 MQTT+HTTP 短連接的實現。反之可以考慮自己設計和實現私有協議,這里建議團隊有計劃地遷移到私有協議上。
這里特別提一下排名第二的 WebSocket ,區別于上面的聊天協議,這是一個傳輸通訊協議,那為什么會有這么多人在即時通訊領域運用了這一協議?除了上文說的長連接特性外,這個協議 web 原生支持,有很多第三方語言實現,可以搭配 XMPP、MQTT 等多種聊天協議進行使用,被廣泛地應用于即時通訊領。
社交場景
最大的特點在于:模式成熟,界面類似。
我們專門為社交場景開發的開源組件:ChatKit-OC,star數,1000+。
ChatKit-OC 在協議選擇上使用的是 WebSocket 搭配私有聊天協議的方式,在數據傳輸上選擇的是 Protobuf 搭配 JSON 的方式。
項目地址:ChatKit-OC
下文會專門介紹下技術實現細節。
直播場景
一個演示如何為直播集成 IM 的開源直播 Demo:
項目地址:LiveKit-iOS
(這個庫,我最近也在優化,打算做成 Lib,支持下 CocoaPods 。希望能幫助大家快速集成直播模塊。有興趣的也歡迎參與進來提 PR)
LiveKit 相較社交場景的特點:
- 無人數限制的聊天室
- 自定義消息
- 打賞機制的服務端配合
有人可能有這樣的疑問:
(叫我Elon(讀:一龍)就好了)
那么可以看下 Demo 的實現:我們可以看到里面的彈幕、禮物、點贊出心這些都是 IM 系統里的自定義消息。
數據自動更新場景
- 打車應用場景(Uber、滴滴等 APP 首頁的移動小車)
- 朋友圈狀態的實施更新,朋友圈自己發送的消息無需刷新,自動更新
這些場景比聊天要簡單許多,僅僅涉及到監聽對象的訂閱、取消訂閱。
正如上文所提到的,使用 MQTT 實現最為經濟。用社交類、直播類的思路來做,也可以實現,但略顯冗余。
電梯場景(假在線狀態處理)
iOS端的假在線的狀態,有兩種方案:
- 雙向ping pong機制
- iOS端只走APNs
雙向 ping-pong 機制:
Message 在發送后,在服務端維護一個表,一段時間內,比如15秒內沒有收到 ack,就認為應用處于離線狀態,先將用戶踢下線,然后轉而進行推送。這里如果出現,重復推送,客戶端要負責去重。將 Message 消息相當于服務端發送的 Ping 消息,APP 的 ack 作為 pong。
使用 APNs 來作聊天
優缺點:
優點:
- 解決了,iOS端假在線的問題。
缺點:(APNs的缺點)
- 無法保證消息的及時性。
- 讓服務端負載過重
APNs不保證消息的到達率,消息會被折疊:
你可能見過這種推送消息:
這中間發生了什么?
當 APNs 向你發送了4條推送,但是你的設備網絡狀況不好,在 APNs 那里下線了,這時 APNs 到你的手機的鏈路上有4條任務堆積,APNs 的處理方式是,只保留最后一條消息推送給你,然后告知你推送數。那么其他三條消息呢?會被APNs丟棄。
有一些 App 的 IM 功能沒有維持長連接,是完全通過推送來實現的,通常情況下,這些 App 也已經考慮到了這種丟推送的情況,這些 App 的做法都是,每次收到推送之后,然后向自己的服務器查詢當前用戶的未讀消息。但是 APNs 也同樣無法保證這四條推送能至少有一條到達你的 App。
為什么這么設計?APNs的存儲-轉發能力太弱,大量的消息存儲和轉發將消耗 Apple 服務器的資源,可能是出于存儲成本考慮,也可能是因為 Apple 轉發能力太弱。總之結果就是 APNs 從來不保證消息的達到率。并且設備上線之后也不會向服務器上傳信息。
現在我們可以保證消息一定能推送到 APNs 那里,但是 APNs 不保證幫我們把消息投遞給用戶。
即使搭配了這樣的策略:每次收到推送就拉歷史記錄的消息,一旦消息被 APNs 丟棄,這條消息可能會在幾天之后受到了新推送后才被查詢到。
讓服務端負載過重:
APNs 的實現原理決定了:必須每次收到消息后,拉取歷史消息。這意味著你無法控制 APP 請求服務端的頻率,同一時間十萬、百萬的請求量都是可能的,這帶來的負載以及風險,有時甚至會比輪詢還要大。
結論:如果面向的目標用戶對消息的及時性并不敏感,可以采用這種方案。比如社交場景。(對消息較為敏感的APP則并不適合,比如:專門為情侶間使用的APP。。。)
技術實現細節
基于 WebSocket 的 IM 系統
WebSocket簡介
WebSocket 是 HTML5 開始提供的一種瀏覽器與服務器間進行全雙工通訊的網絡技術。 WebSocket 通信協定于2011年被 IETF 定為標準 RFC 6455,WebSocket API 被 W3C 定為標準。
在 WebSocket API 中,瀏覽器和服務器只需要要做一個握手的動作,然后,瀏覽器和服務器之間就形成了一條快速通道。兩者之間就直接可以數據互相傳送。
只從 RFC 發布的時間看來,WebSocket要晚很多,HTTP 1.1是1999年,WebSocket 則是12年之后了。WebSocket 協議的開篇就說,本協議的目的是為了解決基于瀏覽器的程序需要拉取資源時必須發起多個HTTP請求和長時間的輪訓的問題而創建的。可以達到支持 iOS,Android,Web 三端同步的特性。
更多
技術實現細節的部分較長,單獨成篇。: 《技術實現細節》
下面是文章的第二部分:
性能調優 -- 針對移動網絡特點的性能調優
極簡協議,傳輸協議 Protobuf
目錄如下:
- 極簡協議,傳輸協議 Protobuf
- 在安全上做了哪些事情?
- 防止 DNS 污染
- 賬戶安全
- 重連機制
- 使用 HTTP/2 減少不必要的網絡連接
- 設置合理的超時時間
- 圖片視頻等文件上傳
- 使用緩存:基于 Hash 的本地緩存校驗
首先讓我們來看下:
IM 即時通訊中你會選用什么數據傳輸格式?
之前做的調研數據如下:
注:本次投票是發布在微博@iOS程序犭袁 ,鑒于微博關注機制,本數據只能反映出 IM 技術在 iOS 領域的使用情況,并不能反映出整個IT行業的情況。
排名前三的分別的JSON 、ProtocolBuffer、XML;
這里重點推薦下 ProtocolBuffer:
該協議已經在業內有很多應用,并且效果顯著:
使用 ProtocolBuffer 減少 Payload
- 滴滴打車40%;
- 攜程之前分享過,說是采用新的Protocol Buffer數據格式+Gzip壓縮后的Payload大小降低了15%-45%。數據序列化耗時下降了80%-90%。
采用高效安全的私有協議,支持長連接的復用,穩定省電省流量
- 【高效】提高網絡請求成功率,消息體越大,失敗幾率隨之增加。
- 【省流量】流量消耗極少,省流量。一條消息數據用Protobuf序列化后的大小是 JSON 的1/10、XML格式的1/20、是二進制序列化的1/10。同 XML 相比, Protobuf 性能優勢明顯。它以高效的二進制方式存儲,比 XML 小 3 到 10 倍,快 20 到 100 倍。
- 【省電】省電
- 【高效心跳包】同時心跳包協議對IM的電量和流量影響很大,對心跳包協議上進行了極簡設計:僅 1 Byte 。
- 【易于使用】開發人員通過按照一定的語法定義結構化的消息格式,然后送給命令行工具,工具將自動生成相關的類,可以支持java、c++、python、Objective-C等語言環境。通過將這些類包含在項目中,可以很輕松的調用相關方法來完成業務消息的序列化與反序列化工作。語言支持:原生支持c++、java、python、Objective-C等多達10余種語言。 2015-08-27 Protocol Buffers v3.0.0-beta-1中發布了Objective-C(Alpha)版本, 2016-07-28 3.0 Protocol Buffers v3.0.0正式版發布,正式支持 Objective-C。
- 【可靠】微信和手機 QQ 這樣的主流 IM 應用也早已在使用它(采用的是改造過的Protobuf協議)
如何測試驗證 Protobuf 的高性能?
對數據分別操作100次,1000次,10000次和100000次進行了測試,
縱坐標是完成時間,單位是毫秒,
反序列化 | 序列化 | 字節長度 |
---|---|---|
數據來源。
數據來自:項目 thrift-protobuf-compare,測試項為 Total Time,也就是 指一個對象操作的整個時間,包括創建對象,將對象序列化為內存中的字節序列,然后再反序列化的整個過程。從測試結果可以看到 Protobuf 的成績很好.
缺點:
可能會造成 APP 的包體積增大,通過 Google 提供的腳本生成的 Model,會非常“龐大”,Model 一多,包體積也就會跟著變大。
如果 Model 過多,可能導致 APP 打包后的體積驟增,但 IM 服務所使用的 Model 非常少,比如在 ChatKit-OC 中只用到了一個 Protobuf 的 Model:Message對象,對包體積的影響微乎其微。
在使用過程中要合理地權衡包體積以及傳輸效率的問題,據說去哪兒網,就曾經為了減少包體積,進而減少了 Protobuf 的使用。
在安全上需要做哪些事情?
防止 DNS 污染
文章較長,單獨成篇。: 《防 DNS 污染方案.md》
賬戶安全
IM 服務賬號密碼一旦泄露,危害更加嚴峻。尤其是對于消息可以漫游的類型。比如:
介紹下我們是如何做到,即使是我們的服務器被攻破,你的用戶系統依然不會受到影響:
- 帳號安全:
無侵入的權限控制:
與用戶的用戶帳號體系完全隔離,只需要提供一個ID就可以通信,接入方可以對該 ID 進行 MD5 加密后再進行傳輸和存儲,保證開發者用戶數據的私密性及安全。
- 簽名機制
對關鍵操作,支持第三方服務器鑒權,保護你的信息安全。
參考: 《實時通信服務總覽-權限和認證》
- 單點登錄
讓 APP 支持單點登錄,能有限減少盜號造成的安全問題。在 ChatKit-OC 中,我們就默認開啟了單點登錄功能,以此來提升 APP 的安全性。
重連機制
- 精簡心跳包,保證一個心跳包大小在10字節之內;
- 減少心跳次數:心跳包只在空閑時發送;從收到的最后一個指令包進行心跳包周期計時而不是固定時間。
- 重連冷卻
2的指數級增長2、4、8,消息往來也算作心跳。類似于 iPhone 密碼的 錯誤機制,冷卻單位是5分鐘,依次是5分鐘后、10分鐘后、15分鐘后,10次輸錯,清除數據。
當然,這樣靈活的策略也同樣決定了,只能在 APP 層進行心跳ping。
這里有必要提一下重連機制的必要性,我們知道 TCP 也有保活機制,但這個與我們在這里討論的“心跳保活”機制是有區別的。
TCP 保活(TCP KeepAlive 機制)和心跳保活區別:
TCP保活 | 心跳保活 |
---|---|
在定時時間到后,一般是 7200 s,發送相應的 KeepAlive 探針。,失敗后重試 10 次,每次超時時間 75 s。(詳情請參見《TCP/IP詳解》中第23章) | 通常可以設置為3-5分鐘發出 Ping |
檢測連接的死活(對應于下圖中的1) | 檢測通訊雙方的存活狀態(對應于下圖中的2) |
保活,究竟保的是誰?
比如:考慮一種情況,某臺服務器因為某些原因導致負載超高,CPU 100%,無法響應任何業務請求,但是使用 TCP 探針則仍舊能夠確定連接狀態,這就是典型的連接活著但業務提供方已死的狀態,對客戶端而言,這時的最好選擇就是斷線后重新連接其他服務器,而不是一直認為當前服務器是可用狀態,一直向當前服務器發送些必然會失敗的請求。
使用 HTTP/2 減少不必要的網絡連接
大多數的移動網絡(3G)并不允許一個給定 IP 地址超過兩個的并發 HTTP 請求,既當你有兩個針對同一個地址的連接時,再發起的第三個連接總是會超時。而2G網絡下這個限定為1個。同一時間發起過多的網絡請求不僅不會起到加速的效果,反而有副作用。
另一方面,由于網絡連接很是費時,保持和共享某一條連接就是一個不錯的選擇:比如短時間內多次的HTTP請求。
使用 HTTP/2 就可以達到這樣的目的。
HTTP/2 是 HTTP 協議發布后的首個更新,于2015年2月17日被批準。它采用了一系列優化技術來整體提升 HTTP 協議的傳輸性能,如異步連接復用、頭壓縮等等,可謂是當前互聯網應用開發中,網絡層次架構優化的首選方案之一。
HTTP/2 也以高復用著稱,而且如果我們要使用 HTTP/2,那么在網絡庫的選擇上必然要使用 NSURLSession。所以 AFN2.x 也需要升級到AFN3.x.
設置合理的超時時間
過短的超時容易導致連接超時的事情頻頻發生,甚至一直無法連接,而過長的超時則會帶來等待時間過長,體驗差的問題。就目前來看,對于普通的TCP連接30秒是個不錯的超時值,而Http請求可以按照重要性和當前網絡情況動態調整超時,盡量將超時控制在一個合理的數值內,以提高單位時間內網絡的利用率。
圖片視頻等文件上傳
圖片格式優化在業界已有成熟的方案,例如 Facebook 使用的 WebP 圖片格式,已經被國內眾多 App 使用。
分片上傳、斷點續傳、秒傳技術、
- 文件分塊上傳:因為移動網絡丟包嚴重,將文件分塊上傳可以使得一個分組包含合理數量的TCP包,使得重試概率下降,重試代價變小,更容易上傳到服務器;
- 提供文件秒傳的方式:服務器根據MD5、SHA進行文件去重;
- 支持斷點續傳。
- 上傳失敗,合理的重連,比如3次。
使用緩存:基于 Hash 的本地緩存校驗
微信是不用考慮消息同步問題,因為微信是不存儲歷史記錄的,卸載重裝消息記錄就會丟失。
所以我們可以采用一個類似 E-Tag、Last-Modified 的本地消息緩存校驗機制,具體做法就是,當我們想加載最近10條的聊天記錄時,先將本地緩存的最近10條做一個 hash 值,將 hash 值發送給服務端,服務端將服務端的最近十條做一個 hash ,如果一致就返回304。最理想的情況是服務端一直返回304,一直加載本地記錄。這樣做的好處:
- 消息同步
- 節省流量
IM系列文章
IM 系列文章分為下面這幾篇:
- 《IM 即時通訊技術在多應用場景下的技術實現,以及性能調優(iOS視角)》 (本文)
- 《技術實現細節》
- 《有一種 Block 叫 Callback,有一種 Callback 做 CompletionHandler》
- 《防 DNS 污染方案》
本文是第一篇。
Posted by 微博@iOS程序犭袁
原創文章,版權聲明:自由轉載-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0