Web & Electron 平台即時通訊產品的技術選型

語言: CN / TW / HK

微信圖片_20220909191613.png (點擊報名融雲 2022 社交泛娛樂出海嘉年華)

8 月底,“IM 進階實戰高手課·第二講”圍繞“Web & Electron 平台即時通訊產品的技術選型”進行了詳細拆解。關注【融雲全球互聯網通信雲】瞭解更多

融雲講師巧用比喻等方法,生動而又邏輯清晰地對 IM 場景前端技術方案進行了分析對比,並分享了融雲的最佳實踐。下期聚焦 IM 全能力 ,就在 9 月 20 日 關注【融雲全球互聯網通信雲】瞭解更多


IM 常見業務形態及核心功能

即時通訊產品常見的業務形態有以下幾種:聊天室、單羣聊、超級羣、實時通知、在線廣播。而底層功能就像基礎零件,可以用不同的方法拼接出上層的不同業務形態。

微信圖片_20220909191624.png

基礎功能單元模塊,大概就分為三類:\ 最基礎的是連接管理類的需求,這是即時通訊業務的基礎。接着是兩端基於連接的數據傳輸,這裏我們要關注的是前後端通訊時的數據傳輸協議,也就是對於數據的序列化和反序列化的過程管理。

最後就是基於既有數據的查詢功能,我們主要分享前端的持久化存儲技術。\ 即時通訊場景下,對於這三個技術點的一些技術要求各不相同。

  • 連接管理 - 持續、穩定、及時的雙向網絡連接
  • 數據傳輸 - 安全、高效、易拓展的前後端數據傳輸協議
  • 記錄查詢 - 前端數據持久化存儲方案

網絡連接方案對比

我們通過五個指標來橫向對比連接方案,分別是:連接速度、傳輸效率、即時性、安全性、兼容性

WebSocket 是前端的首選方案,它也是 Web 平台上構建長連接業務的原生技術方案。

因為瀏覽器安全沙箱的存在,我們不能在 Web 瀏覽器內直接訪問傳輸層協議,但是 Electron 主進程內是百無禁忌的,我們可以在 Electron 場景下選用 TCP基於 HTTP 的模擬雙工協議解決方案,並不是單純的 HTTP 協議本身,因為在長連接業務中,短連接特性的 HTTP 協議並不匹配需求場景。

連接速度

連接速度是從發起連接到連接建立的耗時。\ TCP 的連接需要進行三次握手,對於發起端是發兩次收一次,對於應答端就是發一次收兩次。

微信圖片_20220909191629.png

為什麼要做三次握手?其實這也是一個很常見的面試題。打個比方來説,兩個人要達成一次有效對話,就需要確定兩個信息:一是自己的耳朵和嘴巴沒有問題;二是對方的耳朵和嘴巴沒有問題。只有在這個基礎之上,兩者之間的對話才是有效的。三次握手就是要完成這麼一個能力確認過程。嘴巴=發送能力,耳朵=接收能力。

當然這個比喻也不嚴謹,雙方語言不通時也溝通不來。這是第二個技術方向數據封裝協議要解決的問題,也就是對對端意圖理解問題。

WebSocket 連接要建立在 TCP 連接基礎之上,因為 WebSocket 是一個應用層協議,也就是運維們經常説的 7 層協議,而 TCP 是一個傳輸層協議,是 4 層協議。

微信圖片_20220909191633.png

TCP 握手完成之後,通過 HTTP 報文向服務器協議提出升級申請,服務通過 HTTP response 報文來響應申請,至此 WebSocket 協商完成。比 TCP 多了兩個動作,所以 WebSocket 的連接速度是慢於 TCP 的。HTTP 是短連接協議,連接無法穩定保持,每次通訊要重建連接,所以連接速度對它沒有意義。

傳輸效率

我們把傳輸效率定義為同一塊數據在傳輸過程中產生的流量消耗、時間消耗、算力消耗,以輔助橫向對比不同網絡協議,消耗越大,效率越低。

這裏我們主要看流量消耗和時間消耗,算力消耗在 Web 和 Electron 幾乎可以忽略不計。先看一下 OSI 參考模型,TCP 是 4 層協議,WebSocekt 是 7 層協議,這些其實就是 OSI 參考模型裏的層級概念。

微信圖片_20220909191637.png

OSI 參考模型裏,數據從兩個網絡節點之間傳遞,是一個 U 型傳遞過程:發送端從上到下傳遞,每層需要在數據包中增加不同的協議頭信息,以確保接收端同層可以解析;接收端從下往上傳遞,傳遞過程逐層剝離數據中的頭信息,並將數據向上傳遞。所以數據包從上到下的傳遞過程中,數據體積是不斷加碼的。同一段數據,直接通過 TCP 發送,流量消耗低於 WebSocket 協議。

我們看一下通過 WebSocket 協議傳遞數據的額外流量有哪些。\ 首先 WebSocket 數據傳遞的最小單元是數據幀。一段數據會被分割組裝為最少一個數據幀寫入到 TCP 的緩衝區,如果數據比較大,就會被分拆為多個數據幀,然後對端接收到之後再進行數據幀還原。

下圖的二進制序列結構就是數據幀中的數據組成。

微信圖片_20220909191641.png

HTTP 是文本型協議,沒有最小發送單元,或者説 HTTP 的報文數據的最小發送單元,就是 TCP 協議的最小發送單元。它不像 WebSocket 會將數據分裝為 N 個數據幀,每個數據幀增加 WebSocket 頭信息後再交給 TCP。它不主動分割數據,只在數據首部增加首行和 Headers 信息,並最終把完整報文信息寫入 TCP 緩衝區,交由 TCP 去管理傳輸過程。

那麼,HTTP 的傳輸效率是否就優於 WebSocket 呢?並不一定。\ 首先,一個 WebSocket 數據幀最多額外增加 2 - 14 個字節的頭信息,但是 HTTP 協議本身的首行和 Headers 信息消耗的空間是遠大於 14 字節的,這也是因為 HTTP 協議的特性導致的。它是文本型協議,一個字符最少需要一個字節的容量來存儲。

其次,HTTP 協議的拓展,會額外增加報文 Headers 信息,這些信息也是字符型的,且是鍵值對形式。另外,HTTP 本身是短連接,意味着服務在收到請求時首先要確認數據發送者的身份,所以報文數據中不可避免地在每次的請求中攜帶鑑權信息,但是長連接協議是不需要這些額外開銷的。

所以,WebSocket 的總體傳輸效率是優於 HTTP 的,除非待傳輸的數據大到了使 WebSocket 數據幀數量的頭信息空間總和超過了 HTTP 報文頭的程度,但是這種情況一般發生在文件上傳等低頻場景。大部分業務數據往來中,單次發送的數據都不會很大。

即時性

即時性是數據準備完成,到被寫入到 TCP 緩衝區可能經歷哪些等待時長。

對於即時通訊場景,數據的上下行是同時在發生的,這也意味着 HTTP 的短特性很吃虧,因為下行數據會受阻,服務器無法通過短連接的 HTTP 協議完成數據的主動推送。

這裏我們先普及一下網絡協議的一些基礎概念,因為它跟我們要去比較的即時性是息息相關。

第一類概念,是對於連接持續性的描述。\ 長連接,通俗點講就是連接在建立後是持續存在的,雙端可以通過已存在的連接互發數據,只有當一端主動終止連接,連接才會被關閉。TCP 和 WebSocket 都屬於長連接協議。(關於長連接的更多分享,點此瞭解

短連接,是説當我需要與對端通信時建立連接,通信完畢後立即關閉連接。HTTP 就是一個短連接協議。發起請求的時候建立連接,收到響應之後連接就會關閉。當然,也可以利用 KeepAlive 去保持 TCP 連接複用,不過它還是不能保證連接不能被關閉。因為連接的持續有效,長連接的即時性是優於短連接的。因為它避免了數據發送時要建立連接的等待過程。

第二類概念,是對於字節流數據流向控制的定義。\ 全雙工協議,是説字節流可以在連接中雙向自由流動,因為這種自由流動,所以這類協議的即時性是最好的,也通常是長連接協議。只要緩衝區夠大,就基本沒有等待過程。TCP、WebSocket 都是屬於全雙工協議。

半雙工協議,是説字節流可以在兩個方向上流動,但同一時刻只能存在一個方向上的流動數據。半雙工協議就像一條路上只有一個車道,對向有來車時,本方向的車就不能進車道,否則路就堵死了。HTTP 協議就是一個典型的半雙工協議,它實際上是允許數據雙向流動的,但是它的響應必須在請求數據接收完成之後,同一時刻不存在雙向流動的字節流。半雙工協議的即時性要低於全雙工,因為它有對連接的使用等待過程,當有對向的數據流時,數據要延遲發送。

單工協議,就是數據只能單向流動,比如 HTTP 協議中的 SSE 功能。因為單工協議不能獨立完成雙向數據流動,不符合即時通訊的需求,所以我們就不考慮了。

歸類來看,WebScoekt 和 TCP 的即時性基本屬於同一級別的,HTTP 則比他們要弱

當然,HTTP 單獨拎出來一個請求是不能和長連接協議比的,我們還要看一下通過 HTTP 協議的併發多連接請求能不能彌補它自身的不足。這裏,我們再深入分析一下基於 HTTP 協議的長連接模擬方案。

我們先想一下,基於 HTTP 協議構建的解決方案,要解決的核心問題是什麼?\ 第一點,客户端發送數據時,等待連接建立造成的發送延遲。 因為 HTTP 的短連接性質,所以在上行數據傳輸過程中,需要等 TCP 連接建立才能發送 HTTP 報文。針對這一點,就 HTTP 協議來説,目前是沒有解決方案的,HTTP 的 KeepAlive 特性可以緩解,但不能徹底解決。

第二點,服務器數據無法主動推送到端造成的下行數據延遲。 這也是 HTTP 的短連接特性造成的。當服務器有下行數據時,並沒有一個持續的有效連接能夠讓它把數據推下去,所以只能等待客户端來主動建立連接,順道把下行數據帶下去。我們要介紹的方案就是圍繞解決第二點展開的。市面上比較流行的前端基於 HTTP 協議構建的解決方案,主要有三種。

Comet 部分緩解了下行延遲問題。\ HTTP + SSE 方案與 Comet 類似,只是把下行通道從 HTTP 請求變成了 SSE 實現。

SSE 的特性是長連接、單工協議,它的整體效果優於 Comet,因為沒有額外的連接等待時間。它作為下行數據的通道,單工協議也完全符合要求。可以説 HTTP + SSE 的下行即時性基本是與 WebSocekt 等同的,基本解決了下行延遲問題。

Long-Pulling,定時向服務器去發送請求,以此來把下行數據帶回來,基本屬於常規操作,兩個核心問題,基本一個也沒有解決。\ 總結而言,HTTP + SSE > Comet > Long-Pulling\ 這個結論有一個前提,是拋開了兼容性的。

SSE 方案雖好,但僅限於瀏覽器,如果我們想把 JS 代碼複用到其他環境比如小程序,該方案就無法實現了。目前各小程序 Runtime 對於 SSE 的支持幾乎是 0。

安全性

我們看一下 OSI 參考模型,着重看一下應用層和傳輸層之間的部分,這裏是 SSL/TLS 所處的位置,也就是我們常説的 HTTPS 中的那個 S。

微信圖片_20220909191644.png

OSI 模型中定義中,會話層負責兩端的會話維持、身份鑑別等,表示層負責對數據的加解密。在此之上,應用層協議的安全性是等同的,HTTPS 和 WSS 協議就是安全版的 HTTP 和 WebSocket 協議。

TCP 是比 SSL/TLS 更底層的協議,所以直接經由 TCP 發送的數據是可以有更多的安全選擇的,TLS 只是備選項之一。使用 HTTPS 或 WSS 協議時,由 Runtime 提供 TLS/SSL 支持,開發者無需關注數據傳輸過程中的安全問題。

使用 TCP 協議時,需要由開發者自行保證數據傳輸過程中的安全性(對接 TLS/SSL 或其他自定義安全方案)。

微信圖片_20220909191649.png


數據傳輸協議方案 & 前端持久化存儲

數據傳輸協議方案

我們通過信息密度、拓展性、安全性、多端一致、兼容性五個指標來做對比數據傳輸協議。除了我們並不推薦的自研方案,常用的數據傳輸協議有兩種:Protocol Buffer(PB) ,TLV 格式二進制數據;JSON,純文本鍵值對數據。

微信圖片_20220909191652.png

五個指標對比來看,信息密度指 A 向 B 傳達信息需要消耗的流量,密度越高,消耗得越少,PB 的信息密度比 JSON 高。傳輸結構上,PB 是一個 TLV 格式二進制數據序列,JSON 是純字符串系列鍵值對,JSON 描述數據結構要遠大於 PB 描述數據結構。

安全性與可讀性相反,我們要讀一個二進制的 PB 數據,需要知道它序列化過程中的 PB 數據定義的結構。我們常説的 PB 文件定義的可讀性文件,這是前後端的一個約定,基於這個文件我們才能去序列化和反序列化這個二進制數據。而 JSON 是純字符串,可讀性良好,可以比較直觀去了解數據裏的信息。

兼容性方面,JSON 有很多原生語言庫可選,PB 的兼容性在前端來看也就是 JS 對 ArrayBuffer 的支持,目前也都是支持的。\ 拓展性上,雙方是等同的,PB 有一個優點,因為他傳輸的數據不包含鍵信息,所以兩端的鍵信息可以不同。JSON 的傳輸信息包含鍵信息,意味着鍵信息是不可以隨意變更的。\ 多端一致與兼容性一致,即時通訊場景涉及很多平台,多端就要去對數據做多端傳輸,數據的序列化和反序列化的實現的過程要保持一致,PB 跟 JSON 這方面都比較好,JSON 是原生支持的,PB 可以使用 Google 提供的相應三方庫。\ 總之,PB 比 JSON 更優。

前端持久化存儲對比

最後是前端的可持久化存儲技術:LocalStorage、IndexDB 和 Sqlite。持久化存儲本身可選方案不多,需要考慮容量、兼容性、數據一致性等方面。

  • LocalStorage,最常用的方案,容量比較低
  • IndexDB,瀏覽器器上唯一可用的持久化大容量存儲方案
  • Sqlite - Electron Only,常用前端數據庫
  • Sqlite - WebAssembly,研發成本比較高

除了 LocalStorage,其他三種容量相當。\ 就數據一致性而言,除了 Electron 主進程內使用 Sqlite,其他三種方案都不太好處理數據競爭問題,很難保證數據一致性。在 Electron 平台下,把數據庫操作放到主進程去完成,當渲染進程需要操控本地數據庫時,依賴 IPC 去跟主進程通信,由主進程去處理真實的數據庫事務。主進程是唯一一個數據庫的訪問點,因此可以妥善完成對數據競爭和數據一致性的保障。


融雲的落地實踐分享

經過三大項的方案對比,融雲在落地的時候按照前面所分享的原則,在可用的方案下儘可能選擇最優解,比如,Electron 平台上最優解就是 TCP+Sqlite ,數據封裝用 PB。

Web 平台上沒有 TCP+Sqlite 可用,就用 WebSocket 作為解決方案,把 HTTP 作為次優解做相應的技術落地。

小程序上,PB、WebSocket 都用不了,則採用 HTTP 方案備選,選型用的是 Comet。這個降級過程對於集成的開發者來説是無感的,但在業務落地過程中是需要關注的,比如消息查詢,有沒有數據庫就是兩種應用體驗。