馬蜂窩 IM 移動端架構的從 0 到 1

来源:https://www.cnblogs.com/mfwtech/archive/2019/10/22/11718380.html
-Advertisement-
Play Games

移動互聯網技術改變了旅游的世界,這個領域過去沉重的信息分銷成本被大大降低。用戶與服務供應商之間、用戶與用戶之間的溝通路徑逐漸打通,溝通的場景也在不斷擴展。這促使所有的移動應用開發者都要從用戶視角出發,更好地滿足用戶需求。 ...


(馬蜂窩技術原創內容,公眾號 ID:mfwtech)

移動互聯網技術改變了旅游的世界,這個領域過去沉重的信息分銷成本被大大降低。用戶與服務供應商之間、用戶與用戶之間的溝通路徑逐漸打通,溝通的場景也在不斷擴展。這促使所有的移動應用開發者都要從用戶視角出發,更好地滿足用戶需求。

論壇時代的馬蜂窩,用戶之間的溝通形式比較單一,主要為單純的回帖回覆等。為了以較小的成本快速滿足用戶需求,當時採用的是非實時性消息的方案來實現用戶之間的消息傳遞。

隨著行業和公司的發展,馬蜂窩確立了「內容+交易」的獨特商業模式。在用戶規模不斷增長及業務形態發生變化的背景下,為用戶和商家提供穩定可靠的售前和售後技術支持,成為電商移動業務線的當務之急。

 

一、設計思路與整體架構

我們結合 B2C,C2B,C2C 不同的業務場景設計實現了馬蜂窩旅游移動端中的私信、用戶咨詢、用戶反饋等即時通訊業務;同時為了更好地為合作商家賦能,在馬蜂窩商家移動端中加入與會話相關的咨詢用戶管理、客服管理、運營資源統計等功能。

目前 IM 涉及到的業務如下:

為了實現馬蜂窩旅游 App 及商家 IM 業務邏輯、公共資源的整合復用及 UI 個性化定製,將問題拆解為以下部分來解決:

  1. IM 數據通道與異常重連機制,解決不同業務實時消息下發以及穩定性保障;

  2. IM 實時消息訂閱分發機制,解決消息定向發送、業務訂閱消費,避免不必要的請求資源浪費;

  3. IM 會話列表 UI 繪製通用解決方案,解決不同消息類型的快速迭代開發和管理複雜問題;

整體實現結構分為 4 個部分進行封裝,分別為下圖中的數據管理、消息註冊分發管理、通用 UI 封裝及業務管理。

 

二、技術原理和實現過程

2.1 通用數據通道

對於常規業務展示數據的獲取,客戶端需要主動發起請求,請求和響應的過程是單向的,且對實時性要求不高。但對於 IM 消息來說,需要同時支持接收和發送操作,且對實時性要求高。為支撐這種要求,客戶端和伺服器之間需要創建一條穩定連接的數據通道,提供客戶端和服務端之間的雙向數據通信。

2.1.1 數據通道基礎交互原理

為了更好地提高數據通道對業務支撐的擴展性,我們將所有通信數據封裝為外層結構相同的數據包,使多業務類型數據使用共同的數據通道下發通信,統一分發處理,從而減少通道的創建數量,降低數據通道的維護成本。

常見的客戶端與服務端數據交互依賴於 HTTP 請求響應過程,只有客戶端主動發起請求才可以得到響應結果。結合馬蜂窩的具體業務場景,我們希望建立一種可靠的消息通道來保障服務端主動通知客戶端,實現業務數據的傳遞。目前採用的是 HTTP 長鏈接輪詢的形式實現,各業務數據消息類型只需遵循約定的通用數據結構,即可實現通過數據通道下發給客戶端。數據通道不必關心數據的具體內容,只需要關註接收與發送。

2.1.2 客戶端數據通道實現原理

客戶端數據通道管理的核心是維護一個業務場景請求棧,在不同業務場景切換過程中入棧不同的業務場景參數數據。每次 HTTP 長鏈接請求使用棧頂請求數據,可以模擬在特定業務場景 (如與不同的用戶私信) 的不同處理。數據相關處理都集中封裝在數據通道管理中,業務層只需在數據通道管理中註冊對應的接收處理即可得到需要的業務消息數據。

 

2.2 消息訂閱與分發

在軟體系統中,訂閱分發本質上是一種消息模式。非直接傳遞消息的一方被稱為「發佈者」,接受消息處理稱為「訂閱者」。發佈者將不同的消息進行分類後分發給對應類型的訂閱者,完成消息的傳遞。應用訂閱分發機制的優勢為便於統一管理,可以添加不同的攔截器來處理消息解析、消息過濾、異常處理機制及數據採集工作。

2.2.1 消息訂閱

業務層只專註於消息處理,並不關心消息接收分發的過程。訂閱的意義在於更好地將業務處理和數據通道處理解耦,業務層只需要訂閱關註的消息類型,被動等待接收消息即可。

 

業務層訂閱需要處理的業務消息類型,在註冊後會自動監控當前頁面的生命周期,併在頁面銷毀後刪除對應的消息訂閱,從而避免手動編寫成對的訂閱和取消訂閱,降低業務層的耦合,簡化調用邏輯。訂閱分發管理會根據各業務類型維護訂閱者隊列用於消息接收的分發操作。

2.2.2 消息分發

數據通道的核心在於維護多消息類型各自對應的訂閱者集合,並將解析的消息分發到業務層。


 

數據通道由多業務消息共用,在每次請求收到新消息列表後,根據各自業務類型重新拆分成多個消息列表,分發給各業務類型對應的訂閱處理器,最終傳遞至業務層交予對應頁面處理展示。

2.3 會話消息列表繪製

基於不同的場景,如社交為主的私信、用戶服務為主的咨詢反饋等,都需要會話列表的展示形式;但各場景又不完全相同,需要分析當前會話列表的共通性及可封裝復用的部分,以更好地支撐後續業務的擴展。

2.3.1 消息在列表展示的組成結構

IM 消息列表的特點在於消息類型多、UI 展示多樣化,因此需要建立各類型消息和佈局的對應關係,在收到消息後根據消息類型匹配到對應的佈局添加至對應消息列表。

 

2.3.2 消息類型與展示佈局管理原理

對於不同消息類型及展示,問題的核心在於建立消息類型、消息數據結構、消息展示佈局管理的映射關係。以上三者在實現過程中通過建立映射管理表來維護,各自建立列表存儲消息類型/消息體封裝結構/消息展示佈局管理,設置對應關係關聯 3 個列表來完成查找。 

 

2.3.3 一次收發消息 UI 繪製過程

各類型消息在內容展示上各有不同,但整體會話消息展示樣式可以分為 3 種,分別是接收消息、發送消息和處於頁面中間的消息樣式,區別隻在於內部的消息樣式。所以消息 UI 的繪製可以拆分成 2 個步驟,首先是創建通用的展示容器,然後再填充各消息具體的展示樣式。

 

拆分的目的在於使各類型消息 UI 處理只需要關註特有數據。而如通用消息如頭像、名稱、消息時間、是否可舉報、已讀未讀狀態、發送失敗/重試狀態等都可以統一處理,降低修改維護的成本,同時使各消息 UI 處理邏輯更少、更清晰,更利於新類型的擴展管理。

收發到消息後,根據消息類型判斷是「發送接收類型」還是「居中展示類型」,找到外層的佈局樣式,再根據具體消息類型找到特有的 UI 樣式,拼接在外層佈局中,得到完整的消息卡片,然後設置對應的數據渲染到列表中,完成整個消息的繪製。

 

三、細節優化 & 踩坑經驗

在實現上述 IM 系統的過程中,我們遇到了很多問題,也做了很多細節優化。在這裡總結實現時需要考慮的幾點,以供大家借鑒。

3.1 消息去重

在前面的架構中,我們使用 msg_id 來標記消息列表中的每一條消息,msg_id 是根據客戶端上傳的數據,進行存儲後生成的。

 

客戶端 A 請求 IM 伺服器之後生成 msg_id,再通過請求返回和 Polling 分發到客戶端 A 和客戶端 B。當流程成立的時候,客戶端 A 和客戶端 B 通過服務端分發的 msg_id 來進行本地去重。但這種方案存在以下問題:

 

當客戶端 A 因為網路出現問題,無法接受對應發送消息的請求返回的時候,會觸發重發機制。此時雖然 IM 伺服器已經接受過一次客戶端 A 的消息發送請求,但是因為無法確定兩個請求是否來自同一條原始消息,只能再次接受,這就導致了重覆消息的產生。解決的方法是引入客戶端消息標識 id。因為我們已經依附舊有的 msg_id 做了很多工作,不打算讓客戶端的消息 id 代替 msg_id 的職能,因此重新定義一個 random_id。


 

random_id = random + time_stamp。random_id 標識了唯一的消息體,由一個隨機數和生成消息體的時間戳生成。當觸發重試的時候,兩次請求的 random_id 會是相同的,服務端可以根據該欄位進行消息去重。

3.2 本地化 Push

當我們在會話頁或列表頁的環境下,可以通過界面的變化很直觀地觀察到收取了新消息並更新未讀數。但從會話頁或者列表頁退出之後,就無法單純地從界面上獲取這些信息,這時需要有其他的機制,讓用戶獲知當前消息的狀態。

系統推送與第三方推送是一個可行的選擇,但本質上推送也是基於長鏈接提供的服務。為彌補推送不穩定性與風險,我們採用數據通道+本地通知的形式來完善消息通知機制。通過數據通道下發的消息如需達到推送的提示效果,則攜帶對應的 Push 展示數據。同時會對當前所處的頁面進行判斷,避免對當前頁面的消息內容進行重覆提醒。

 

通過這種數據通道+本地通知展示的機制,可以在應用處於運行狀態的時間內提高消息抵達率,減少對於遠程推送的依賴,降低推送系統的壓力,並提升用戶體驗。

3.3 數據通道異常重連機制

當前數據通道通過 HTTP 長鏈接輪詢 (Polling) 實現。不同業務場景下對 Polling 的影響如下圖所示:

 

由於用戶手機所處網路請求狀態不一,有時候會遇到網路中斷或者服務端異常的情況,從而終止 Polling 的請求。為能夠讓用戶在網路恢復後繼續會話業務,需要引入重連機制。

在重試機制 1.0 版本中,對於可能出現較多重試請求的情況,採取的是添加 60s 內連續 5 次報錯延遲重試的限制。具體流程如下:

 

 

在實踐中發現以下問題:

  • 當服務端突然異常並持續超過 1 分鐘後,客戶端啟動執行重試機制,並每隔 1 分鐘重發一次重連請求。這對伺服器而言就相當於遭受一次短暫集中的「攻擊」,甚至有可能拖垮伺服器。

  • 當客戶端斷網後立刻進行重試也並不合理,因為用戶恢復網路也需要一定時間,這期間的重連請求是無意義的。

基於以上問題分析改進,我們設計了第二版重試機制。此次將 5 次以下請求錯誤的延遲時間修改為 5 - 20 秒隨機重試,將客戶端重試請求分散在多個時間點避免同時請求形成對伺服器對瞬時壓力。同時在客戶端斷網情況下也進行延遲重試。

 

Polling 機制修改後請求量劃分,相對之前請求分佈比較均勻,不再出現集中請求的問題。

 

3.4 唯一會話標識

3.4.1 為何引入消息線 ID

消息線就是用來表示會話的聊天關係,不同消息線代表不同對象的會話,從 DB 層面來看需要一個張表來存儲這種關係 uid + object_id + busi_type = 消息線 ID。

 

在 IM 初期實現中,我們使用會話配置參數(包含業務來源和會話參數)來標識會話 id,有三個作用:

  • 查找商家 id,獲取咨詢來源,進行管家分配

  • 查找已存在的消息線

  • 判斷客戶端頁面狀態,決定要不要下發推送,進行消息提醒

這種方式存在兩個問題:

  • 通過業務來源和會話參數來解析對應的商家 id,兩個參數缺失一個都會導致商家 id 解析錯誤,還要各種查詢資料庫才能得到商家 id,影響效率;

  • 通過會話類型切換介面標識當前會話類型,切換頁面會頻繁觸髮網絡請求;如果請求介面發生意外容易引發消息內容錯誤問題,嚴重依賴客戶端的健壯性

用業務來源和會話參數幫助我們進行管家分配是不可避免的,但我們可以通過引入消息線 ID 來綁定消息線的方式,替代業務來源和會話參數查找消息線的作用。另外針對下發推送的問題已通過上方講述的本地推送通知機制解決。

3.4.2 何時創建消息線

  • 當進入會話頁發消息時,檢查 DB 中是否存在對應消息線,不存在則將這條消息 id 當作消息線 id 使用,存在即復用。

  • 當進入會話時,根據用戶 id 、業務類型 id 等檢查在 DB 中是否已存在對應消息線,不存在則創建消息線,存在即復用。

3.4.3 引入消息線目的

  • 減少服務端查詢消息線的成本。

  • 移除舊版狀態改變相關的介面請求,間接提高了推送觸達率。

  • 降低移動端對於用戶消息匹配的複雜度。

 

四、展望及近期優化

4.1 數據通道實現方式升級為 Websocket

WebSocket 是一種在單個 TCP 連接上進行全雙工通信的協議。WebSocket 使得客戶端和伺服器之間的數據交換變得更加簡單,允許服務端主動向客戶端推送數據。在 WebSocket API 中,瀏覽器和伺服器只需要完成一次握手,兩者之間就直接可以創建持久性的連接,併進行雙向數據傳輸。

與目前的 HTTP 輪詢實現機制相比, Websocket 有以下優點:

  • 較少的控制開銷。在連接創建後,伺服器和客戶端之間交換數據時,用於協議控制的數據包頭部相對較小。在不包含擴展的情況下,對於伺服器到客戶端的內容,此頭部大小隻有 2 至 10 位元組(和數據包長度有關);對於客戶端到伺服器的內容,此頭部還需要加上額外的 4 位元組的掩碼。相對於 HTTP 請求每次都要攜帶完整的頭部,開銷顯著減少。

  • 更強的實時性。由於協議是全雙工的,伺服器可以隨時主動給客戶端下發數據。相對於 HTTP 需要等待客戶端發起請求服務端才能響應,延遲明顯更少;即使是和 Comet 等類似的長輪詢比較,其也能在短時間內更多次地傳遞數據。

  • 保持連接狀態。與 HTTP 不同的是,Websocket 需要先創建連接,這就使其成為一種有狀態的協議,在之後通信時可以省略部分狀態信息。而 HTTP 請求可能需要在每個請求都攜帶狀態信息(如身份認證等)。

  • 更好的二進位支持。Websocket 定義了二進位幀,相對 HTTP,可以更輕鬆地處理二進位內容。

  • 支持擴展。Websocket 定義了擴展,用戶可以擴展協議、實現部分自定義的子協議,如部分瀏覽器支持壓縮等。

  • 更好的壓縮效果。相對於 HTTP 壓縮,Websocket 在適當的擴展支持下,可以沿用之前內容的上下文,在傳遞類似的數據時,可以顯著地提高壓縮率。

為了進一步優化我們的數據通道設計,我們探索驗證了 Websocket 的可行性,併進行了調研和設計:

 

近期將對 HTTP 輪詢實現方案進行替換,進一步優化數據通道的效率。

4.2 業務功能的擴展

計劃將 IM 移動端功能模塊打造成通用的即時通訊組件,能夠更容易地賦予各業務 IM 能力,使各業務快速在自有產品線上添加聊天功能,降低研發 IM 的成本和難度。目前的 IM 功能實現主要有兩個組成,分別是公用的數據通道與 UI 組件。

隨著馬蜂窩業務發展,在現有 IM 系統上還有很多可以建設和升級的方向。比如消息類型的支撐上,擴展對短視頻、語音消息、快捷消息回覆等支撐,提高社交的便捷性和趣味性;對於多人場景希望增加群組,興趣頻道,多人音視頻通信等場景的支撐等。

相信未來通過對更多業務功能的擴展及應用場景的探索,馬蜂窩移動端 IM 將更好地提升用戶體驗,並持續為商家賦能。

本文作者:馬蜂窩電商業務 IM 移動端研發團隊。

(馬蜂窩技術原創內容)


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ``` import someComponent from './components/someComponent' export default { install(Vue) { let componentList=[someComponent ] componentList.forEach(it... ...
  • npm svn + 清除失敗的事務 cmd管理員運行,執行 `svn cleanup` ...
  • 定義 height指的是塊級別元素的高度; line-height指的是元素內容的高度。 height和line-height的聯繫 CSS中起高度作用的應該就是height以及line-height。height是用來設置元素的高度,比如img的高度、div的高度等。 如果不設置div的高度時,是 ...
  • 1. 先在computed中,用需要監聽的兩個值(start、end)定義一個對象(dateRange) 2. 然後在watch中監聽這個對象(dateRange) ...
  • 問題,對象屬性名排序,如: var data = { A:[], D:[], B:{} } 調整為=》 var data = { A:[], B:[], D:{} } 方法一: for,in,把fieldname保存到一個數組中,sort,然後根據sort順序把原對象的值保存到新對象中 方法二-ES ...
  • 騰訊搶金達人活動中使用了node同構直出渲染方案,那麼其中遇到了什麼問題,最終帶來了什麼收益呢? ...
  • 本篇文章將詳細介紹 Optional 類,以及如何用它消除代碼中的 null 檢查。在開始之前首先來看下什麼是 NPE,以及在 Java 8 之前是如何處理 NPE 問題的。 空指針異常(NullPointException,簡稱 NPE)可以說是所有 Java 程式員都遇到過的一個異常,雖然 Ja ...
  • 中介者模式: 定義一個對象來封裝一系列對象的交互. 中介者的四個角色: 1.Mediator(抽象中介者) 2.ConcreteMediator(具體中介者) 3.Colleague(抽象同事類) 4.ConcreteColleague(具體同事類) 舉例說明: 例如公司開發一套CRM系統,該系統負 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...