本文引用自馬蜂窩公衆號,由馬蜂窩技術團隊原創分享。php
今天,愈來愈多的用戶被馬蜂窩持續積累的筆記、攻略、嗡嗡等優質的分享內容所吸引,在這裏激發了去旅行的熱情,同時也拉動了馬蜂窩交易的增加。在幫助用戶作出旅行決策、完成交易的過程當中,IM 系統起到了重要的做用。html
IM 系統爲用戶與商家創建了直接溝通的渠道,幫助用戶解答購買旅行產品中的問題,既促成了訂單交易,也幫用戶打消了疑慮,促成用戶旅行願望的實現。伴隨着業務的快速發展,幾年間,馬蜂窩 IM 系統也經歷了幾回比較重要的架構演化和轉型。算法
本文將分享馬蜂窩旅遊網的IM系統架構從零演進的整個過程,但願能給你的IM技術選型和方案肯定帶來啓發。數據庫
關於馬蜂窩旅遊網:後端
馬蜂窩旅遊網是中國領先的自由行服務平臺,由陳罡和呂剛創立於2006年,從2010年正式開始公司化運營。馬蜂窩的景點、餐飲、酒店等點評信息均來自上億用戶的真實分享,每一年幫助過億的旅行者制定自由行方案。瀏覽器
學習交流:緩存
- 即時通信/推送技術開發交流4羣:101279154[推薦]服務器
- 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM》微信
(本文同步發佈於:http://www.52im.net/thread-2675-1-1.html)網絡
初期爲了支持業務快速上線,且當時版本流量較低,對併發要求不高,IM 系統的技術架構主要以簡單和可用爲目的,實現的功能也很基礎。
IM 1.0 使用 PHP 開發,實現了 IM 基本的用戶/客服接入、消息收發、諮詢列表管理功能。用戶諮詢時,會經過平均分配的策略分配給客服,記錄用戶和客服的關聯關係。用戶/客服發送消息時,經過調用消息轉發模塊,將消息投遞到對方的 Redis 阻塞隊列裏。收消息則經過 HTTP 長鏈接調用消息輪詢模塊,有消息時即刻返回,沒有消息則阻塞一段時間返回,這裏阻塞的目的是下降輪詢的間隔。
消息收發模型以下圖所示:
上圖模型中消息輪詢模塊的長鏈接請求是經過 php-fpm 掛載在阻塞隊列上,當該請求變多時,若是不能及時釋放 php-fpm 進程,會對服務器性能消耗較大,負載很高。
爲了解決這個問題,咱們對消息輪詢模塊進行了優化,選用基於 OpenResty 框架,利用 Lua 協程的方式來優化 php-fmp 長時間掛載的問題。Lua 協程會經過對 Nginx 轉發的請求標記判斷是否攔截網絡請求,若是攔截,則會將阻塞操做交給 Lua 協程來處理,及時釋放 php-fmp,緩解對服務器性能的消耗。
優化的處理流程見下圖:
伴隨着業務的快速增加,IM 系統在短時間內面臨着大量定製需求的增長,開發了許多新的業務模塊。面對大量的用戶諮詢,客服的服務能力已經招架不住。
所以,IM 2.0 將重心放在提高業務功能體驗上,好比:
1)在處理用戶的諮詢時,將從前單一的分配方式演變爲採用平均、權重、排隊等多種方式;
2)爲了提高客服的效率,客服的諮詢回覆也增長了可選配置,例如自動回覆、FAQ 等。
以一個典型的用戶諮詢場景爲例,當用戶打開 App 或者網頁時,會經過鏈接層創建長鏈接,以後在諮詢入口發起諮詢時,會攜帶着消息線索初始化消息鏈路,創建一條可複用、可檢索的消息線;發送消息時,經過消息服務將消息存儲到 DB 中,同時會根據消息線檢索當前諮詢是否被分配到客服,調用分配服務的目的是爲當前諮詢完善客服信息;最後將客服信息更新到鏈路關係中。
這樣,一條完整的消息鏈路就創建完畢,以後用戶/客服發出的消息經過轉發服務傳輸給對方。
以上處理流程以下圖所示:
業務量在不斷積累,隨着模塊增長,IM 系統的代碼膨脹得很快。因爲代碼規範沒有統1、接口職責不夠單1、模塊間耦合較多等種緣由,改動一個需求極可能會影響到其它模塊,使新需求的開發和維護成本都很高。
爲了解決這種局面,IM 系統必需要進行架構升級,首要任務就是服務的拆分。目前,通過拆分後的 IM 系統總體分爲 4 塊大的服務,包括客服服務、用戶服務、IM 服務、數據服務。
以下圖所示:
如上圖,咱們來進行一下解讀:
1)客服服務:圍繞提高客服效率和用戶體驗提供多種方式,如提供羣組管理、成員管理、質檢服務等來提高客服團隊的運營和管理水平;經過分配服務、轉接服務來使用戶的接待效率更靈活高效;支持自動回覆、FAQ、知識庫服務等來提高客服諮詢的回覆效率等;
2)用戶服務:分析用戶行爲,爲用戶作興趣推薦及用戶畫像,以及統計用戶對馬蜂窩商家客服的滿意度;
3)IM 服務:支持單聊和羣聊模式,提供實時消息通知、離線消息推送、歷史消息漫遊、聯繫人、文件上傳與存儲、消息內容風控檢測等;
4)數據服務:經過採集用戶諮詢的來源入口、是否諮詢下單、是否有客服接待、用戶諮詢以及客服回覆的時間信息等,定義數據指標,經過數據分析進行離線數據運算,最終對外提供數據統計信息。主要的指標信息有 30 秒、1 分鐘回覆率、諮詢人數、無應答次數、平均應答時間、諮詢銷售額、諮詢轉化率、推薦轉化率、分時接待壓力、值班狀況、服務評分等。
現有的 IM 系統 中,用戶諮詢時一個完整的用戶狀態流轉以下圖所示:
如上圖所示:
1)用戶點擊諮詢按鈕觸發事件,此時用戶狀態進入初始態;
2)發送消息時,系統更改用戶狀態爲待分配,經過調用分配服務分配了對應的客服後,用戶狀態更改成已分配、未解決;
3)當客服解決了用戶或者客服回覆後用戶長時間未說話,觸發系統自動解決的操做,此時用戶狀態更改成已解決,一個諮詢流程結束。
在服務拆分的過程當中,咱們須要考慮特定服務的通用性、可用性和降級策略,同時須要儘量地下降服務間的依賴,避免因爲單一服務不可用致使總體服務癱瘓的風險。
在這期間,公司其它業務線對 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 直接發送。
初始化消息線及分配客服過程由 PHP 業務完成。須要消息轉發時,PHP 業務調用 Dispatcher 服務的發消息接口,Dispatcher 服務經過共享的 Dispatcher Table 數據,檢索出接收者所在的 ChannelManager 實例,將消息經過 RPC 的方式發送到實例上,ChannelManager 經過 WebSocket 將消息推送給客戶端。
IM 服務調用流程以下圖所示:
當鏈接數超過當前 ChannelManager 集羣承載的上限時,只需擴展 ChannelManager 實例,由 ETCD 動態的通知到監聽側,從而作到平滑擴容。目前瀏覽器版本的 JS-SDK 已經開發完畢,其它業務線經過接入文檔,就能方便的集成 IM 服務。
在 Exchange 層的設計中,有 3 個問題須要考。
1)多端消息同步:
如今客戶端有 PC 瀏覽器、Windows 客戶端、H五、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 確認失敗,觸發重試,致使消息重複。
上文提到過 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)