移動互聯網技術改變了旅遊的世界,這個領域過去沉重的信息分銷成本被大大下降。用戶與服務供應商之間、用戶與用戶之間的溝通路徑逐漸打通,溝通的場景也在不斷擴展。這促使全部的移動應用開發者都要從用戶視角出發,更好地知足用戶需求。php
論壇時代的馬蜂窩,用戶之間的溝通形式比較單一,主要爲單純的回帖回覆等。爲了以較小的成本快速知足用戶需求,當時採用的是非實時性消息的方案來實現用戶之間的消息傳遞。html
隨着行業和公司的發展,馬蜂窩確立了「內容+交易」的獨特商業模式。在用戶規模不斷增加及業務形態發生變化的背景下,爲用戶和商家提供穩定可靠的售前和售後技術支持,成爲電商移動業務線的當務之急。算法
本文由馬蜂窩電商業務 IM 移動端研發團隊分享了馬蜂窩電商業務 IM 移動端的架構演進過程,以及在IM技術力量和資源有限的狀況下所踩過的坑等。數據庫
系列文章:瀏覽器
關於馬蜂窩旅遊網:緩存
馬蜂窩旅遊網是中國領先的自由行服務平臺,由陳罡和呂剛創立於2006年,從2010年正式開始公司化運營。馬蜂窩的景點、餐飲、酒店等點評信息均來自上億用戶的真實分享,每一年幫助過億的旅行者制定自由行方案。服務器
學習交流:微信
- 即時通信/推送技術開發交流5羣:215477170 [推薦]網絡
- 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM》數據結構
(本文同步發佈於:www.52im.net/thread-2796…)
咱們結合 B2C,C2B,C2C 不一樣的業務場景設計實現了馬蜂窩旅遊移動端中的私信、用戶諮詢、用戶反饋等即時通信業務;同時爲了更好地爲合做商家賦能,在馬蜂窩商家移動端中加入與會話相關的諮詢用戶管理、客服管理、運營資源統計等功能。
目前 IM 涉及到的業務以下:
爲了實現馬蜂窩旅遊 App 及商家 IM 業務邏輯、公共資源的整合複用及 UI 個性化定製,將問題拆解爲如下部分來解決:
1)IM 數據通道與異常重連機制:解決不一樣業務實時消息下發以及穩定性保障;
2)IM 實時消息訂閱分發機制:解決消息定向發送、業務訂閱消費,避免沒必要要的請求資源浪費;
3)IM 會話列表 UI 繪製通用解決方案:解決不一樣消息類型的快速迭代開發和管理複雜問題。
總體實現結構分爲 4 個部分進行封裝,分別爲下圖中的數據管理、消息註冊分發管理、通用 UI 封裝及業務管理。
對於常規業務展現數據的獲取,客戶端須要主動發起請求,請求和響應的過程是單向的,且對實時性要求不高。但對於 IM 消息來講,須要同時支持接收和發送操做,且對實時性要求高。爲支撐這種要求,客戶端和服務器之間須要建立一條穩定鏈接的數據通道,提供客戶端和服務端之間的雙向數據通訊。
3.1.1 數據通道基礎交互原理
爲了更好地提升數據通道對業務支撐的擴展性,咱們將全部通訊數據封裝爲外層結構相同的數據包,使多業務類型數據使用共同的數據通道下發通訊,統一分發處理,從而減小通道的建立數量,下降數據通道的維護成本。
常見的客戶端與服務端數據交互依賴於 HTTP 請求響應過程,只有客戶端主動發起請求才能夠獲得響應結果。結合馬蜂窩的具體業務場景,咱們但願創建一種可靠的消息通道來保障服務端主動通知客戶端,實現業務數據的傳遞。目前採用的是 HTTP 長連接輪詢的形式實現,各業務數據消息類型只需遵循約定的通用數據結構,便可實現經過數據通道下發給客戶端。數據通道沒必要關心數據的具體內容,只須要關注接收與發送。
3.1.2 客戶端數據通道實現原理
客戶端數據通道管理的核心是維護一個業務場景請求棧,在不一樣業務場景切換過程當中入棧不一樣的業務場景參數數據。每次 HTTP 長連接請求使用棧頂請求數據,能夠模擬在特定業務場景 (如與不一樣的用戶私信) 的不一樣處理。數據相關處理都集中封裝在數據通道管理中,業務層只需在數據通道管理中註冊對應的接收處理便可獲得須要的業務消息數據。
在軟件系統中,訂閱分發本質上是一種消息模式。非直接傳遞消息的一方被稱爲「發佈者」,接受消息處理稱爲「訂閱者」。發佈者將不一樣的消息進行分類後分發給對應類型的訂閱者,完成消息的傳遞。應用訂閱分發機制的優點爲便於統一管理,能夠添加不一樣的攔截器來處理消息解析、消息過濾、異常處理機制及數據採集工做。
3.2.1 消息訂閱
業務層只專一於消息處理,並不關心消息接收分發的過程。訂閱的意義在於更好地將業務處理和數據通道處理解耦,業務層只須要訂閱關注的消息類型,被動等待接收消息便可。
業務層訂閱須要處理的業務消息類型,在註冊後會自動監控當前頁面的生命週期,並在頁面銷燬後刪除對應的消息訂閱,從而避免手動編寫成對的訂閱和取消訂閱,下降業務層的耦合,簡化調用邏輯。訂閱分發管理會根據各業務類型維護訂閱者隊列用於消息接收的分發操做。
3.2.2 消息分發
數據通道的核心在於維護多消息類型各自對應的訂閱者集合,並將解析的消息分發到業務層。
數據通道由多業務消息共用,在每次請求收到新消息列表後,根據各自業務類型從新拆分紅多個消息列表,分發給各業務類型對應的訂閱處理器,最終傳遞至業務層交予對應頁面處理展現。
基於不一樣的場景,如社交爲主的私信、用戶服務爲主的諮詢反饋等,都須要會話列表的展現形式;但各場景又不徹底相同,須要分析當前會話列表的共通性及可封裝複用的部分,以更好地支撐後續業務的擴展。
3.3.1 消息在列表展現的組成結構
IM 消息列表的特色在於消息類型多、UI 展現多樣化,所以須要創建各種型消息和佈局的對應關係,在收到消息後根據消息類型匹配到對應的佈局添加至對應消息列表。
3.3.2 消息類型與展現佈局管理原理
對於不一樣消息類型及展現,問題的核心在於創建消息類型、消息數據結構、消息展現佈局管理的映射關係。以上三者在實現過程當中經過創建映射管理表來維護,各自創建列表存儲消息類型/消息體封裝結構/消息展現佈局管理,設置對應關係關聯 3 個列表來完成查找。
3.3.3 一次收發消息 UI 繪製過程
各種型消息在內容展現上各有不一樣,但總體會話消息展現樣式能夠分爲 3 種,分別是接收消息、發送消息和處於頁面中間的消息樣式,區別只在於內部的消息樣式。因此消息 UI 的繪製能夠拆分紅 2 個步驟,首先是建立通用的展現容器,而後再填充各消息具體的展現樣式。
拆分的目的在於使各種型消息 UI 處理只須要關注特有數據。而如通用消息如頭像、名稱、消息時間、是否可舉報、已讀未讀狀態、發送失敗/重試狀態等均可以統一處理,下降修改維護的成本,同時使各消息 UI 處理邏輯更少、更清晰,更利於新類型的擴展管理。
收發到消息後,根據消息類型判斷是「發送接收類型」仍是「居中展現類型」,找到外層的佈局樣式,再根據具體消息類型找到特有的 UI 樣式,拼接在外層佈局中,獲得完整的消息卡片,而後設置對應的數據渲染到列表中,完成整個消息的繪製。
在實現上述 IM 系統的過程當中,咱們遇到了不少問題,也作了不少細節優化。在這裏總結實現時須要考慮的幾點,以供你們借鑑。
在前面的架構中,咱們使用 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 會是相同的,服務端能夠根據該字段進行消息去重。
當咱們在會話頁或列表頁的環境下,能夠經過界面的變化很直觀地觀察到收取了新消息並更新未讀數。但從會話頁或者列表頁退出以後,就沒法單純地從界面上獲取這些信息,這時須要有其餘的機制,讓用戶獲知當前消息的狀態。
系統推送與第三方推送是一個可行的選擇,但本質上推送也是基於長連接提供的服務。爲彌補推送不穩定性與風險,咱們採用數據通道+本地通知的形式來完善消息通知機制。經過數據通道下發的消息如需達到推送的提示效果,則攜帶對應的 Push 展現數據。同時會對當前所處的頁面進行判斷,避免對當前頁面的消息內容進行重複提醒。
經過這種數據通道+本地通知展現的機制,能夠在應用處於運行狀態的時間內提升消息抵達率,減小對於遠程推送的依賴,下降推送系統的壓力,並提高用戶體驗。
當前數據通道經過 HTTP 長連接輪詢 (Polling) 實現。
不一樣業務場景下對 Polling 的影響以下圖所示:
因爲用戶手機所處網絡請求狀態不一,有時候會遇到網絡中斷或者服務端異常的狀況,從而終止 Polling 的請求。爲可以讓用戶在網絡恢復後繼續會話業務,須要引入重連機制。
在重試機制 1.0 版本中,對於可能出現較多重試請求的狀況,採起的是添加 60s 內連續 5 次報錯延遲重試的限制。
具體流程以下:
在實踐中發現如下問題:
1)當服務端忽然異常並持續超過 1 分鐘後,客戶端啓動執行重試機制,並每隔 1 分鐘重發一次重連請求。這對服務器而言就至關於遭受一次短暫集中的「攻擊」,甚至有可能拖垮服務器;
2)當客戶端斷網後馬上進行重試也並不合理,由於用戶恢復網絡也須要必定時間,這期間的重連請求是無心義的。
基於以上問題分析改進,咱們設計了第二版重試機制。這次將 5 次如下請求錯誤的延遲時間修改成 5 - 20 秒隨機重試,將客戶端重試請求分散在多個時間點避免同時請求造成對服務器對瞬時壓力。同時在客戶端斷網狀況下也進行延遲重試。
Polling 機制修改後請求量劃分,相對以前請求分佈比較均勻,再也不出現集中請求的問題。
4.4.1 爲什麼引入消息線 ID
消息線就是用來表示會話的聊天關係,不一樣消息線表明不一樣對象的會話,從 DB 層面來看須要一個張表來存儲這種關係 uid + object_id + busi_type = 消息線 ID。
在 IM 初期實現中,咱們使用會話配置參數(包含業務來源和會話參數)來標識會話 id,有三個做用:
1)查找商家 id,獲取諮詢來源,進行管家分配;
2)查找已存在的消息線;
3)判斷客戶端頁面狀態,決定要不要下發推送,進行消息提醒。
這種方式存在兩個問題:
1)經過業務來源和會話參數來解析對應的商家 id,兩個參數缺失一個都會致使商家 id 解析錯誤,還要各類查詢數據庫才能獲得商家 id,影響效率;
2)經過會話類型切換接口標識當前會話類型,切換頁面會頻繁觸發網絡請求;若是請求接口發生意外容易引起消息內容錯誤問題,嚴重依賴客戶端的健壯性。
用業務來源和會話參數幫助咱們進行管家分配是不可避免的,但咱們能夠經過引入消息線 ID 來綁定消息線的方式,替代業務來源和會話參數查找消息線的做用。另外針對下發推送的問題已經過上方講述的本地推送通知機制解決。
4.4.2 什麼時候建立消息線
1)當進入會話頁發消息時,檢查 DB 中是否存在對應消息線,不存在則將這條消息 id 看成消息線 id 使用,存在即複用;
2)當進入會話時,根據用戶 id 、業務類型 id 等檢查在 DB 中是否已存在對應消息線,不存在則建立消息線,存在即複用。
4.4.3 引入消息線目的
1)減小服務端查詢消息線的成本;
2)移除舊版狀態改變相關的接口請求,間接提升了推送觸達率;
3)下降移動端對於用戶消息匹配的複雜度。
WebSocket 是一種在單個 TCP 鏈接上進行全雙工通訊的協議。WebSocket 使得客戶端和服務器之間的數據交換變得更加簡單,容許服務端主動向客戶端推送數據。在 WebSocket API 中,瀏覽器和服務器只須要完成一次握手,二者之間就直接能夠建立持久性的鏈接,並進行雙向數據傳輸。
與目前的 HTTP 輪詢實現機制相比, Websocket 有如下優勢:
1)較少的控制開銷:在鏈接建立後,服務器和客戶端之間交換數據時,用於協議控制的數據包頭部相對較小。在不包含擴展的狀況下,對於服務器到客戶端的內容,此頭部大小隻有 2 至 10 字節(和數據包長度有關);對於客戶端到服務器的內容,此頭部還須要加上額外的 4 字節的掩碼。相對於 HTTP 請求每次都要攜帶完整的頭部,開銷顯著減小;
2)更強的實時性:因爲協議是全雙工的,服務器能夠隨時主動給客戶端下發數據。相對於 HTTP 須要等待客戶端發起請求服務端才能響應,延遲明顯更少;即便是和 Comet 等相似的長輪詢比較,其也能在短期內更屢次地傳遞數據;
3)保持鏈接狀態:與 HTTP 不一樣的是,Websocket 須要先建立鏈接,這就使其成爲一種有狀態的協議,在以後通訊時能夠省略部分狀態信息。而 HTTP 請求可能須要在每一個請求都攜帶狀態信息(如身份認證等);
4)更好的二進制支持:Websocket 定義了二進制幀,相對 HTTP,能夠更輕鬆地處理二進制內容;
5)支持擴展:Websocket 定義了擴展,用戶能夠擴展協議、實現部分自定義的子協議,如部分瀏覽器支持壓縮等;
6)更好的壓縮效果:相對於 HTTP 壓縮,Websocket 在適當的擴展支持下,能夠沿用以前內容的上下文,在傳遞相似的數據時,能夠顯著地提升壓縮率。
爲了進一步優化咱們的數據通道設計,咱們探索驗證了 Websocket 的可行性,並進行了調研和設計:
近期將對 HTTP 輪詢實現方案進行替換,進一步優化數據通道的效率。
計劃將 IM 移動端功能模塊打形成通用的即時通信組件,可以更容易地賦予各業務 IM 能力,使各業務快速在自有產品線上添加聊天功能,下降研發 IM 的成本和難度。目前的 IM 功能實現主要有兩個組成,分別是公用的數據通道與 UI 組件。
隨着馬蜂窩業務發展,在現有 IM 系統上還有不少能夠建設和升級的方向。好比消息類型的支撐上,擴展對短視頻、語音消息、快捷消息回覆等支撐,提升社交的便捷性和趣味性;對於多人場景但願增長羣組,興趣頻道,多人音視頻通訊等場景的支撐等。
相信將來經過對更多業務功能的擴展及應用場景的探索,馬蜂窩移動端 IM 將更好地提高用戶體驗,並持續爲商家賦能。
淺談IM系統的架構設計》
《簡述移動端IM開發的那些坑:架構設計、通訊協議和客戶端》
《一套海量在線用戶的移動端IM架構設計實踐分享(含詳細圖文)》
《一套原創分佈式即時通信(IM)系統理論架構方案》
《從零到卓越:京東客服即時通信系統的技術架構演進歷程》
《蘑菇街即時通信/IM服務器開發之架構選擇》
《騰訊QQ1.4億在線用戶的技術挑戰和架構演進之路PPT》
《微信後臺基於時間序的海量數據冷熱分級架構設計實踐》
《微信技術總監談架構:微信之道——大道至簡(演講全文)》
《如何解讀《微信技術總監談架構:微信之道——大道至簡》》
《快速裂變:見證微信強大後臺架構從0到1的演進歷程(一)》
《17年的實踐:騰訊海量產品的技術方法論》
《移動端IM中大規模羣消息的推送如何保證效率、實時性?》
《現代IM系統中聊天消息的同步和存儲方案探討》
《IM開發基礎知識補課(二):如何設計大量圖片文件的服務端存儲架構?》
《IM開發基礎知識補課(三):快速理解服務端數據庫讀寫分離原理及實踐建議》
《IM開發基礎知識補課(四):正確理解HTTP短鏈接中的Cookie、Session和Token》
《WhatsApp技術實踐分享:32人工程團隊創造的技術神話》
《微信朋友圈千億訪問量背後的技術挑戰和實踐總結》
《王者榮耀2億用戶量的背後:產品定位、技術架構、網絡方案等》
《IM系統的MQ消息中間件選型:Kafka仍是RabbitMQ?》
《騰訊資深架構師乾貨總結:一文讀懂大型分佈式系統設計的方方面面》
《以微博類應用場景爲例,總結海量社交系統的架構設計步驟》
《快速理解高性能HTTP服務端的負載均衡技術原理》
《子彈短信光鮮的背後:網易雲信首席架構師分享億級IM平臺的技術實踐》
《知乎技術分享:從單機到2000萬QPS併發的Redis高性能緩存實踐之路》
《IM開發基礎知識補課(五):通俗易懂,正確理解並用好MQ消息隊列》
《微信技術分享:微信的海量IM聊天消息序列號生成實踐(算法原理篇)》
《微信技術分享:微信的海量IM聊天消息序列號生成實踐(容災方案篇)》
《新手入門:零基礎理解大型分佈式架構的演進歷史、技術原理、最佳實踐》
《一套高可用、易伸縮、高併發的IM羣聊、單聊架構方案設計實踐》
《阿里技術分享:深度揭祕阿里數據庫技術方案的10年變遷史》
《阿里技術分享:阿里自研金融級數據庫OceanBase的艱辛成長之路》
《社交軟件紅包技術解密(一):全面解密QQ紅包技術方案——架構、技術實現等》
《社交軟件紅包技術解密(二):解密微信搖一搖紅包從0到1的技術演進》
《社交軟件紅包技術解密(三):微信搖一搖紅包雨背後的技術細節》
《社交軟件紅包技術解密(四):微信紅包系統是如何應對高併發的》
《社交軟件紅包技術解密(五):微信紅包系統是如何實現高可用性的》
《社交軟件紅包技術解密(六):微信紅包系統的存儲層架構演進實踐》
《社交軟件紅包技術解密(七):支付寶紅包的海量高併發技術實踐》
《社交軟件紅包技術解密(八):全面解密微博紅包技術方案》
《社交軟件紅包技術解密(九):談談手Q紅包的功能邏輯、容災、運維、架構等》
《即時通信新手入門:一文讀懂什麼是Nginx?它可否實現IM的負載均衡?》
《即時通信新手入門:快速理解RPC技術——基本概念、原理和用途》
《多維度對比5款主流分佈式MQ消息隊列,媽媽不再擔憂個人技術選型了》
《從游擊隊到正規軍(一):馬蜂窩旅遊網的IM系統架構演進之路》
《從游擊隊到正規軍(二):馬蜂窩旅遊網的IM客戶端架構演進和實踐總結》
《IM開發基礎知識補課(六):數據庫用NoSQL仍是SQL?讀這篇就夠了!》
>> 更多同類文章 ……
(本文同步發佈於:www.52im.net/thread-2796…)