從游擊隊到正規軍:馬蜂窩旅遊網的IM系統架構演進之路

本文引用自馬蜂窩公衆號,由馬蜂窩技術團隊原創分享。

一、引言

今天,越來越多的用戶被馬蜂窩持續積累的筆記、攻略、嗡嗡等優質的分享內容所吸引,在這裏激發了去旅行的熱情,同時也拉動了馬蜂窩交易的增長。在幫助用戶做出旅行決策、完成交易的過程中,IM 系統起到了重要的作用。

IM 系統爲用戶與商家建立了直接溝通的渠道,幫助用戶解答購買旅行產品中的問題,既促成了訂單交易,也幫用戶打消了疑慮,促成用戶旅行願望的實現。伴隨着業務的快速發展,幾年間,馬蜂窩 IM 系統也經歷了幾次比較重要的架構演化和轉型。

本文將分享馬蜂窩旅遊網的IM系統架構從零演進的整個過程,希望能給你的IM技術選型和方案確定帶來啓發。

關於馬蜂窩旅遊網:

馬蜂窩旅遊網是中國領先的自由行服務平臺,由陳罡和呂剛創立於2006年,從2010年正式開始公司化運營。馬蜂窩的景點、餐飲、酒店等點評信息均來自上億用戶的真實分享,每年幫助過億的旅行者制定自由行方案。

學習交流:

- 即時通訊/推送技術開發交流4羣:101279154[推薦]

- 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM

(本文同步發佈於:http://www.52im.net/thread-2675-1-1.html

二、相關文章

一套海量在線用戶的移動端IM架構設計實踐分享(含詳細圖文)

一套原創分佈式即時通訊(IM)系統理論架構方案

從零到卓越:京東客服即時通訊系統的技術架構演進歷程

蘑菇街即時通訊/IM服務器開發之架構選擇

以微博類應用場景爲例,總結海量社交系統的架構設計步驟

一套高可用、易伸縮、高併發的IM羣聊、單聊架構方案設計實踐

騰訊QQ1.4億在線用戶的技術挑戰和架構演進之路PPT

微信技術總監談架構:微信之道——大道至簡(演講全文)

如何解讀《微信技術總監談架構:微信之道——大道至簡》

快速裂變:見證微信強大後臺架構從0到1的演進歷程(一)

三、IM 1.0:初期階段

初期爲了支持業務快速上線,且當時版本流量較低,對併發要求不高,IM 系統的技術架構主要以簡單和可用爲目的,實現的功能也很基礎。

IM 1.0 使用 PHP 開發,實現了 IM 基本的用戶/客服接入、消息收發、諮詢列表管理功能。用戶諮詢時,會通過平均分配的策略分配給客服,記錄用戶和客服的關聯關係。用戶/客服發送消息時,通過調用消息轉發模塊,將消息投遞到對方的 Redis 阻塞隊列裏。收消息則通過 HTTP 長連接調用消息輪詢模塊,有消息時即刻返回,沒有消息則阻塞一段時間返回,這裏阻塞的目的是降低輪詢的間隔。

消息收發模型如下圖所示:

上圖模型中消息輪詢模塊的長連接請求是通過 php-fpm 掛載在阻塞隊列上,當該請求變多時,如果不能及時釋放 php-fpm 進程,會對服務器性能消耗較大,負載很高。

爲了解決這個問題,我們對消息輪詢模塊進行了優化,選用基於 OpenResty 框架,利用 Lua 協程的方式來優化 php-fmp 長時間掛載的問題。Lua 協程會通過對 Nginx 轉發的請求標記判斷是否攔截網絡請求,如果攔截,則會將阻塞操作交給 Lua 協程來處理,及時釋放 php-fmp,緩解對服務器性能的消耗。

優化的處理流程見下圖:

四、IM 2.0:需求定製階段

伴隨着業務的快速增長,IM 系統在短期內面臨着大量定製需求的增加,開發了許多新的業務模塊。面對大量的用戶諮詢,客服的服務能力已經招架不住。

因此,IM 2.0 將重心放在提升業務功能體驗上,比如:

1)在處理用戶的諮詢時,將從前單一的分配方式演變爲採用平均、權重、排隊等多種方式;

2)爲了提升客服的效率,客服的諮詢回覆也增加了可選配置,例如自動回覆、FAQ 等。

以一個典型的用戶諮詢場景爲例,當用戶打開 App 或者網頁時,會通過連接層建立長連接,之後在諮詢入口發起諮詢時,會攜帶着消息線索初始化消息鏈路,建立一條可複用、可檢索的消息線;發送消息時,通過消息服務將消息存儲到 DB 中,同時會根據消息線檢索當前諮詢是否被分配到客服,調用分配服務的目的是爲當前諮詢完善客服信息;最後將客服信息更新到鏈路關係中。

這樣,一條完整的消息鏈路就建立完畢,之後用戶/客服發出的消息通過轉發服務傳輸給對方。

以上處理流程如下圖所示:

五、IM 3.0:服務拆分階段

5.1、概述

業務量在不斷積累,隨着模塊增加,IM 系統的代碼膨脹得很快。由於代碼規範沒有統一、接口職責不夠單一、模塊間耦合較多等種原因,改動一個需求很可能會影響到其它模塊,使新需求的開發和維護成本都很高。

爲了解決這種局面,IM 系統必須要進行架構升級,首要任務就是服務的拆分。目前,經過拆分後的 IM 系統整體分爲 4 塊大的服務,包括客服服務、用戶服務、IM 服務、數據服務。

如下圖所示:

如上圖,我們來進行一下解讀:

1)客服服務:圍繞提升客服效率和用戶體驗提供多種方式,如提供羣組管理、成員管理、質檢服務等來提升客服團隊的運營和管理水平;通過分配服務、轉接服務來使用戶的接待效率更靈活高效;支持自動回覆、FAQ、知識庫服務等來提升客服諮詢的回覆效率等;

2)用戶服務:分析用戶行爲,爲用戶做興趣推薦及用戶畫像,以及統計用戶對馬蜂窩商家客服的滿意度;

3)IM 服務:支持單聊和羣聊模式,提供實時消息通知、離線消息推送、歷史消息漫遊、聯繫人、文件上傳與存儲、消息內容風控檢測等;

4)數據服務:通過採集用戶諮詢的來源入口、是否諮詢下單、是否有客服接待、用戶諮詢以及客服回覆的時間信息等,定義數據指標,通過數據分析進行離線數據運算,最終對外提供數據統計信息。主要的指標信息有 30 秒、1 分鐘回覆率、諮詢人數、無應答次數、平均應答時間、諮詢銷售額、諮詢轉化率、推薦轉化率、分時接待壓力、值班情況、服務評分等。

5.2、用戶狀態流轉

現有的 IM 系統 中,用戶諮詢時一個完整的用戶狀態流轉如下圖所示:

如上圖所示:

1)用戶點擊諮詢按鈕觸發事件,此時用戶狀態進入初始態;

2)發送消息時,系統更改用戶狀態爲待分配,通過調用分配服務分配了對應的客服後,用戶狀態更改爲已分配、未解決;

3)當客服解決了用戶或者客服回覆後用戶長時間未說話,觸發系統自動解決的操作,此時用戶狀態更改爲已解決,一個諮詢流程結束。

5.3、IM 服務的重構

在服務拆分的過程中,我們需要考慮特定服務的通用性、可用性和降級策略,同時需要儘可能地降低服務間的依賴,避免由於單一服務不可用導致整體服務癱瘓的風險。

在這期間,公司其它業務線對 IM 服務的使用需求也越來越多,使用頻次和量級也開始加大。初期階段的 IM 服務當連接量大時,只能通過修改代碼實現水平擴容;新業務接入時,還需要在業務服務器上配置 Openresty 環境及 Lua 協程代碼,業務接入非常不便,IM 服務的通用性也很差。

考慮到以上問題,我們對 IM 服務進行了全面重構,目標是將 IM 服務抽取成獨立的模塊,不依賴其它業務,對外提供統一的集成和調用方式。考慮到 IM 服務對併發處理高和損耗低的要求,選擇了 Go 語言來開發此模塊。

新的 IM 服務設計如下圖:

其中,比較重要的 Proxy 層和 Exchange 層提供了以下服務:

1)路由規則:例如 ip-hash、輪詢、最小連接數等,通過規則將客戶端散列到不同的 ChannelManager 實例上;

2)對客戶端接入的管理:接入後的連接信息會同步到 DispatchTable 模塊,方便 Dispatcher 進行檢索;

3)ChannelManager 與客戶端間的通信協議:包括客戶端請求建立連接、斷線重連、主動斷開、心跳、通知、收發消息、消息的 QoS 等;

4)對外提供單發、羣發消息的 REST 接口:這裏需要根據場景來決定是否使用,例如用戶諮詢客服的場景就需要通過這個接口下發消息。

針對上述第“4)”點,主要原因在以下 3 點:

1)發消息時會有創建消息線、分配管家等邏輯,這些邏輯目前是 PHP 實現,IM 服務需要知道 PHP 的執行結果,一種方式是使用 Go 重新實現,另外一種方式是通過 REST 接口調用 PHP 返回,這樣會帶來 IM 服務和 PHP 業務過多的網絡交互,影響性能;

2)轉發消息時,ChannelManager 多個實例間需要互相通信,例如 ChannelManager1 上的用戶 A 給 ChannelManager2 上的客服 B 發消息,如果實例間無通信機制,消息無法轉發。當要再擴展 ChannelManager 實例時,新增實例需要和其它已存在實例分別建立通信,增加了系統擴展的複雜度;

3)如果客戶端不支持 WebSocket 協議,作爲降級方案的 HTTP 長連接輪循只能用來收消息,發消息需要通過短連接來處理。其它場景不需要消息轉發,只用來給 ChannelManager 傳輸消息的場景,可通過 WebSocket 直接發送。

5.4、改造後的 IM 服務調用流程

初始化消息線及分配客服過程由 PHP 業務完成。需要消息轉發時,PHP 業務調用 Dispatcher 服務的發消息接口,Dispatcher 服務通過共享的 Dispatcher Table 數據,檢索出接收者所在的 ChannelManager 實例,將消息通過 RPC 的方式發送到實例上,ChannelManager 通過 WebSocket 將消息推送給客戶端。

IM 服務調用流程如下圖所示:

當連接數超過當前 ChannelManager 集羣承載的上限時,只需擴展 ChannelManager 實例,由 ETCD 動態的通知到監聽側,從而做到平滑擴容。目前瀏覽器版本的 JS-SDK 已經開發完畢,其它業務線通過接入文檔,就能方便的集成 IM 服務。

在 Exchange 層的設計中,有 3 個問題需要考。

1)多端消息同步:

現在客戶端有 PC 瀏覽器、Windows 客戶端、H5、iOS/Android,如果一個用戶登錄了多端,當有消息過來時,需要查找出這個用戶的所有連接,當用戶的某個端斷線後,需要定位到這一個連接。

上面提到過,連接信息都是存儲在 DispatcherTable 模塊中,因此 DispatcherTable 模塊要能根據用戶信息快速檢索出連接信息。DispatcherTable 模塊的設計用到了 Redis 的 Hash 存儲,當客戶端與 ChannelManager 建立連接後,需要同步的元數據有 uid(用戶信息)、uniquefield(唯一值,一個連接對應的唯一值)、wsid(連接標示符)、clientip(客戶端 ip)、serverip(服務端 ip)、channel(渠道),對應的結構大致如下:

這樣通過 key(uid) 能找到一個用戶多個端的連接,通過 key+field 能定位到一條連接。連接信息的默認過期時間爲 2 小時,目的是避免因客戶端連接異常中斷導致服務端沒有捕獲到,從而在 DispatcherTable 中存儲了一些過期數據。

2)用戶在線狀態同步:

比如一個用戶先後和 4 個客服諮詢過,那麼這個用戶會出現在 4 個客服的諮詢列表裏。當用戶上線時,要保證 4 個客服看到用戶都是在線狀態。

要做到這一點有兩種方案:

一種是客服通過輪詢獲取用戶的狀態,但這樣當用戶在線狀態沒有變化時,會發起很多無效的請求;

另外一種是用戶上線時,給客服推送上線通知,這樣會造成消息擴散,每一個諮詢過的客服都需要擴散通知。

我們最終採取的是第二種方式,在推送的過程中,只給在線的客服推送用戶狀態。

3)消息的不丟失,不重複:

爲了避免消息丟失,對於採用長連接輪詢方式的我們會在發起請求時,帶上客戶端已讀消息的 ID,由服務端計算出差值消息然後返回;使用 WebSocket 方式的,服務端會在推送給客戶端消息後,等待客戶端的 ACK,如果客戶端沒有 ACK,服務端會嘗試多次推送。

這時就需要客戶端根據消息 ID 做消息重複的處理,避免客戶端可能已收到消息,但是由於其它原因導致 ACK 確認失敗,觸發重試,導致消息重複。

5.5、IM 服務的消息流

上文提到過 IM 服務需要支持多終端,同時在角色上又分爲用戶端和商家端,爲了能讓通知、消息在輸出時根據域名、終端、角色動態輸出差異化的內容,引入了 DDD (領域驅動設計)的建模方法來對消息進行處理。

處理過程如下圖所示:

六、小結和展望

伴隨着馬蜂窩「內容+交易」模式的不斷深化,IM 系統架構也經歷着演化和升級的不同階段,從初期粗曠無序的模式走向統一管理,逐漸規範、形成規模。 

我們取得了一些進步,當然,還有更長的路要走。未來,結合公司業務的發展腳步和團隊的技術能力,我們將不斷進行 IM 系統的優化。

目前我們正在計劃將消息輪詢模塊中的服務端代碼用 Go 替換,使其不再依賴 PHP 及 OpenResty 環境,實現更好地解耦;另外,我們將基於 TensorFlow 實現向智慧客服的探索,通過訓練數據模型、分析數據,進一步提升人工客服的解決效率,提升用戶體驗,更好地爲業務賦能。

附錄:更多架構設計文章

[1] 有關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系統架構演進之路

>> 更多同類文章 ……

[2] 更多其它架構設計相關文章:

騰訊資深架構師乾貨總結:一文讀懂大型分佈式系統設計的方方面面

快速理解高性能HTTP服務端的負載均衡技術原理

子彈短信光鮮的背後:網易雲信首席架構師分享億級IM平臺的技術實踐

知乎技術分享:從單機到2000萬QPS併發的Redis高性能緩存實踐之路

新手入門:零基礎理解大型分佈式架構的演進歷史、技術原理、最佳實踐

阿里技術分享:深度揭祕阿里數據庫技術方案的10年變遷史

阿里技術分享:阿里自研金融級數據庫OceanBase的艱辛成長之路

達達O2O後臺架構演進實踐:從0到4000高併發請求背後的努力

優秀後端架構師必會知識:史上最全MySQL大表優化方案總結

小米技術分享:解密小米搶購系統千萬高併發架構的演進和實踐

一篇讀懂分佈式架構下的負載均衡技術:分類、原理、算法、常見方案等

通俗易懂:如何設計能支撐百萬併發的數據庫架構?

多維度對比5款主流分佈式MQ消息隊列,媽媽再也不擔心我的技術選型了

從新手到架構師,一篇就夠:從100到1000萬高併發的架構演進之路

>> 更多同類文章 ……

(本文同步發佈於:http://www.52im.net/thread-2675-1-1.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章