IM 已經成為當下 App 的必備模塊,在不同垂直領域,技術實現不盡相同。究該如何選型?技術實現過程中,又該如何進行性能調優?本篇文章分為應用場景、技術實現細節、針對移動網絡特點的性能調優三個部分,具體講解IM即時通訊技術在社交、直播、紅包等不同場景下的技術實現與性能調優。
需要注意,本文中所涉及到的所有 iOS 相關代碼,均已 100% 開源(不存在 framework ),便于學習參考;本文側重移動端的設計與實現,會展開講,服務端僅僅屬于概述,不展開;本文還將為大家在設計或改造優化 IM 模塊,提供一些參考。
首先,思考幾個問題:
IM 服務的大價值在于什么?可復用的長連接。一切高實時性的場景,都適合使用 IM 來做,比如:視頻會議、聊天、私信、彈幕、抽獎、互動游戲、協同編輯、股票基金實時報價、體育實況更新;基于位置的應用(Uber、滴滴司機位置)、在線教育、智能家居等。
接下來,我們會挑一些典型場景進行介紹,并分析其中具體的技術細節。
IM 基本的發展歷程是:輪詢、長輪詢、長連接。
下面挑選一些代表性的技術進行介紹:
一種輪詢方式是否為長輪詢,是根據服務端的處理方式來決定的,與客戶端沒有關系。
短輪詢很容易理解,那么,什么叫長輪詢?與短輪詢有何區別?長輪詢和短輪詢大的區別是,短輪詢去服務端查詢時,不管服務端有沒有變化,服務器就立即返回結果了。而在長輪詢中,服務器如果檢測到庫存量沒有變化話,將會把當前請求掛起一段時間(這個時間也叫作超時時間,一般是幾十秒)。在這個時間內,服務器會檢測庫存量有沒有變化,變化就立即返回,否則就等到超時為止。
我們可以看到,發展歷史是這樣:從長短輪詢與長短連接,使用 WebSocket 來替代 HTTP。長短輪詢到長短連接的區別主要有:
在移動端上長連接是趨勢,其大的特點是節省 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 倍,如圖 4 所示。
接下來,探討下長連接實現方式的協議選擇。
筆者近做了兩個 IM 問卷,累計產生了 900 多條的投票數據,見圖5、圖6。
注:本數據只能反映出 IM 技術在 iOS 領域的使用情況,并不能反映出整個 IT 行業的情況。
下文會對投票結果進行下分析。
協議如何選擇?
IM 協議選擇原則一般是:易于拓展,方便覆蓋各種業務邏輯,同時又比較節約流量。后一點需求在移動端 IM 上尤其重要。常見的協議有:XMPP、SIP、MQTT、私有協議。我們這里只關注前三名,見表1。
一個好協議需要滿足如下條件:高效、簡潔、可讀性好、節約流量、易于拓展,同時又能匹配當前團隊的技術堆棧?;谝陨显瓌t,我們可以得出:如果團隊小,在 IM 上技術積累不夠可以考慮使用 XMPP 或者 MQTT+HTTP 短連接的實現。反之可以考慮自己設計和實現私有協議,這里建議團隊有計劃地遷移到私有協議上。
在此特別提一下排名第二的 WebSocket,區別于上面的聊天協議,這是一個傳輸通訊協議,那為什么會有這么多人在即時通訊領域運用了這一協議?除了上文說的長連接特性外,這個協議 Web 原生支持,有很多第三方語言實現,可以搭配 XMPP、MQTT 等多種聊天協議進行使用,被廣泛地應用于即時通訊領域。
社交場景
在當前社交場景下,大的特點在于:模式成熟,界面類似。我們團隊專門為社交場景開發了一款開源組件—— ChatKit-OC,Star 數1000+。
ChatKit-OC 在協議選擇上使用的是 WebSocket 搭配私有聊天協議的方式,在數據傳輸上選擇的是 Protobuf(Google Protocol Buffer)搭配JSON的方式。
直播場景
在此分享一個演示如何為直播集成 IM 的開源直播 Demo:LiveKit-iOS。LiveKit 相較社交場景的特點有:無人數限制的聊天室、自定義消息、打賞機制的服務端配合。
數據自動更新場景
這些場景比聊天要簡單許多,僅僅涉及到監聽對象的訂閱、取消訂閱。正如上文所提到的,使用 MQTT 實現為經濟。用社交類、直播類的思路來做,也可以實現,但略顯冗余。
電梯場景(假在線狀態處理)
iOS 端的假在線狀態有雙向 ping pong 機制和 iOS 端只走 APNs 兩種方案。其中,雙向 ping-pong 機制原理如下:
Message 在發送后,在服務端維護一個表,一段時間內,比如 15 秒內沒有收到 ack,就認為應用處于離線狀態,先將用戶踢下線,轉而進行推送。此處如果出現重復推送,客戶端要負責去重。將 Message 消息當作服務端發送的 Ping 消息,App 的 ack 作為 pong,如圖7所示。
使用 APNs 來進行聊天的優缺點具體如下:
APNs 不保證消息的到達率,消息會被折疊,你可能見過圖8的推送消息。
這中間發生了什么?
當 APNs 向你發送了4條推送,但是你的設備網絡狀況不好,在 APNs 那里下線了。這時 APNs 到你的手機鏈路上有4條任務堆積,APNs 的處理方式是,只保留后一條消息推送給你,然后告知推送數。那么其他三條消息呢?會被 APNs 丟棄。
有一些 App 的 IM 功能沒有維持長連接,是完全通過推送來實現的。通常情況下,這些 App 也已經考慮到這種丟推送的情況,它們的做法都是,每次收到推送后,向自己的服務器查詢當前用戶的未讀消息。但是 APNs 也同樣無法保證這四條推送能至少有一條到達你的 App。很遺憾地告訴這些 App,此次更新對你們所遭受的這些坑,沒有改善。
為什么這么設計?APNs 的存儲-轉發能力太弱,大量的消息存儲和轉發將消耗 Apple 服務器資源,可能是出于存儲成本考慮,也可能是因為 Apple 轉發能力太弱。總之結果就是 APNs 從來不保證消息的達到率,并且設備上線之后也不會向服務器上傳信息。
現在我們可以保證消息一定能推送到 APNs 那里,但是 APNs 不保證幫我們把消息投遞給用戶。即使搭配了這樣的策略:每次收到推送就拉歷史記錄的消息,一旦消息被APNs丟棄,能會在幾天之后收到新推送后才被查詢到。
讓服務端負載過重:APNs的實現原理決定了必須每次收到消息后,拉取歷史消息。
結論:如果面向的目標用戶對消息的及時性并不敏感,可以采用這種方案,比如社交場景。
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 三端同步的特性。
注:本次投票是發布在微博@iOS程序犭袁 ,鑒于微博關注機制,本數據只能反映出 IM 技術在 iOS 領域的使用情況,并不能反映出整個IT行業的情況。
使用 Protocol Buffer 減少 Payload,微信也同樣使用定制后的 Protobuf 協議。
攜程是采用新的 Protocol Buffer 數據格式 + Gzip 壓縮后的 Payload 大小降低了 15%-45%。數據序列化耗時下降了80%-90%。
采用高效安全的私有協議,支持長連接的復用,穩定省電省流量:
如何測試:
對數據分別操作 100、1000、10000和100000進行了測試,如圖11所示,縱坐標是完成時間,單位是毫秒。
圖12為 thrift-protobuf-compare,測試項為 Total Time,也就是指一個對象操作的整個時間,包括創建對象,將對象序列化為內存中的字節序列,然后再反序列化的整個過程。從測試結果可以看到Protobuf的成績很好。
缺點:不能表示復雜的數據結構,但 IM 服務已經足夠使用。
另外,可能會造成 App 的包體積增大,通過 Google 提供的腳本生成的 Model,會非常“龐大”,Model 一多,包體積也就會跟著變大。但在我們 SDK 中只使用了一個 Model,所以這個問題并不明顯。
防止 DNS 污染
DNS 出問題的概率其實比大家感覺要大,首先是 DNS 被劫持或者失效,2015 年初業內比較知名的就有 Apple 內部 DNS 問題導致 App Store、iTunes Connect 賬戶無法登錄;京東因為 CDN 域名付費問題導致服務停擺。
另一個常見問題就是 DNS 解析慢或者失敗,例如中國運營商網絡的 DNS 就很慢,一次 DNS 查詢甚至都能趕上一次連接的耗時,尤其 2G 網絡情況下,DNS 解析失敗是很常見的。因此如果直接使用 DNS,對于首次網絡服務請求耗時和整體服務成功率都有非常大的影響。
賬戶安全
IM 服務賬號密碼一旦泄露,危害更加嚴峻,尤其是對于消息可以漫游的類型。在此,分享下我們的團隊是如何保障賬號安全的。
1. 帳號安全
無侵入的權限控制:與用戶的用戶帳號體系完全隔離,只需要提供一個 ID 就可以通信,接入方可以對該 ID 進行 MD5 加密后再進行傳輸和存儲,保證開發者用戶數據的私密性及安全。
2. 簽名機制
對關鍵操作,支持第三方服務器鑒權,保護你的信息安全。
3. 單點登錄
讓 App 支持單點登錄,能有限減少盜號造成的安全問題。在 ChatKit-OC 中,我們就默認開啟了單點登錄功能,以此來提升 App 的安全性。
重連機制
這樣靈活的策略也同樣決定了,只能在 App 層進行心跳 ping。
這里有必要提一下重連機制的必要性,我們知道 TCP 也有?;顧C制,但與我們在這里討論的“心跳保活”機制是有區別的,見表2。
比如:考慮一種情況,某臺服務器因為某些原因導致負載超高,CPU 100%,無法響應任何業務請求,但是使用 TCP 探針則仍舊能夠確定連接狀態,這就是典型的連接活著但業務提供方已死的狀態。對客戶端而言,這時的好選擇就是斷線后重新連接其他服務器,而不是一直認為當前服務器是可用狀態,總是向當前服務器發送些必然會失敗的請求。
大多數移動網絡(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 使用。分片上傳、斷點續傳、秒傳技術。
使用緩存
微信不用考慮消息同步問題,因為它是不存儲歷史記錄的,卸載重裝消息記錄就會丟失。所以我們可以采用一個類似 E-Tag、Last-Modified 的本地消息緩存校驗機制。具體做法是,當我們想加載近10條聊天記錄時,先將本地緩存的近10條做一個 hash 值,將其發送給服務端,服務端將近十條做 hash,如果一致就返回304。理想的情況是服務端一直返回304,一直加載本地記錄,這樣做的好處是消息同步、節省流量。
本站文章版權歸原作者及原出處所有 。內容為作者個人觀點, 并不代表本站贊同其觀點和對其真實性負責,本站只提供參考并不構成任何投資及應用建議。本站是一個個人學習交流的平臺,網站上部分文章為轉載,并不用于任何商業目的,我們已經盡可能的對作者和來源進行了通告,但是能力有限或疏忽,造成漏登,請及時聯系我們,我們將根據著作權人的要求,立即更正或者刪除有關內容。本站擁有對此聲明的最終解釋權。