重新手到專家:如何設計一套億級消息量的分佈式IM系統

本文原做者Chank,原題「如何設計一個億級消息量的 IM 系統」,爲了提高內容質量,本次有修訂和改動。php

一、寫有前面

本文將在億級消息量、分佈式IM系統這個技術前提下,分析和總結實現這套系統所須要掌握的知識點,內容沒有高深的技術概念,儘可能作到新手老手皆能讀懂。html

本文不會給出一套通用的IM方案,也不會評判某種架構的好壞,而是討論設計IM系統的常見難題跟業界的解決方案。前端

由於也沒有所謂的通用IM架構方案,不一樣的解決方案都各有其優缺點,只有最知足業務的系統纔是一個好的系統。程序員

在人力、物力、時間資源有限的前提下,一般須要作出不少權衡,此時,一個可以支持快速迭代、方便擴展的IM系統纔是最優解。算法

二、相關文章

與本文相似,如下兩篇也很是適合同時閱讀,有興趣能夠一併學習。數據庫

一套億級用戶的IM架構技術乾貨(上篇):總體架構、服務拆分等後端

一套億級用戶的IM架構技術乾貨(下篇):可靠性、有序性、弱網優化等服務器

三、IM常見術語

0)用戶:系統的使用者。微信

1)消息:是指用戶之間的溝通內容(一般在IM系統中,消息會有如下幾類:文本消息、表情消息、圖片消息、視頻消息、文件消息等等)。網絡

2)會話:一般指兩個用戶之間因聊天而創建起的關聯。

3)羣:一般指多個用戶之間因聊天而創建起的關聯。

4)終端:指用戶使用IM系統的機器(一般有Android端、iOS端、Web端等等)。

5)未讀數:指用戶還沒讀的消息數量。

6)用戶狀態:指用戶當前是在線、離線仍是掛起等狀態。

7)關係鏈:是指用戶與用戶之間的關係,一般有單向的好友關係、雙向的好友關係、關注關係等等(這裏須要注意與會話的區別:用戶只有在發起聊天時才產生會話,但關係並不須要聊天才能創建。對於關係鏈的存儲,可使用圖數據庫(Neo4j等等),能夠很天然地表達現實世界中的關係,易於建模)。

8)單聊:一對一聊天。

9)羣聊:多人聊天。

10)客服:在電商領域,一般須要對用戶提供售前諮詢、售後諮詢等服務(這時,就須要引入客服來處理用戶的諮詢)。

11)消息分流:在電商領域,一個店鋪一般會有多個客服,此時決定用戶的諮詢由哪一個客服來處理就是消息分流(一般消息分流會根據一系列規則來肯定消息會分流給哪一個客服,例如客服是否在線(客服不在線的話須要從新分流給另外一個客服)、該消息是售前諮詢仍是售後諮詢、當前客服的繁忙程度等等)。

12)信箱:本文的信箱咱們指一個Timeline、一個收發消息的隊列。

四、讀擴散 vs 寫擴散

IM系統裏常常會涉及到讀擴散和寫擴散這兩個技術概念,咱們來看看。

4.1 讀擴散

如上圖所示:A與每一個聊天的人跟羣都有一個信箱(有些博文會叫Timeline,見《現代IM系統中聊天消息的同步和存儲方案探討),A在查看聊天信息的時候須要讀取全部有新消息的信箱。

須要注意與Feeds系統的區別:在Feeds系統中,每一個人都有一個寫信箱,寫只須要往本身的寫信箱裏寫一次就行了,讀須要從全部關注的人的寫信箱裏讀。但IM系統裏的讀擴散一般是每兩個相關聯的人就有一個信箱,或者每一個羣一個信箱。

讀擴散的優勢:

  • 1)寫操做(發消息)很輕量,無論是單聊仍是羣聊,只須要往相應的信箱寫一次就行了;
  • 2)每個信箱自然就是兩我的的聊天記錄,能夠方便查看聊天記錄跟進行聊天記錄的搜索。

讀擴散的缺點:讀操做(讀消息)很重,在複雜業務下,一條讀擴散消息源須要複雜的邏輯才能擴散成目標消息。

4.2 寫擴散

接下來看看寫擴散。

如上圖所示:在寫擴散中,每一個人都只從本身的信箱裏讀取消息。

但寫(發消息)的時候,對於單聊跟羣聊處理以下:

  • 1)單聊:往本身的信箱跟對方的信箱都寫一份消息,同時,若是須要查看兩我的的聊天曆史記錄的話還須要再寫一份(固然,若是從我的信箱也能回溯出兩我的的全部聊天記錄,但這樣效率會很低);
  • 2)羣聊:須要往全部的羣成員的信箱都寫一份消息,同時,若是須要查看羣的聊天曆史記錄的話還須要再寫一份。能夠看出,寫擴散對於羣聊來講大大地放大了寫操做。

PS:實際上羣聊中消息擴散是IM開發中的技術痛點,有興趣建議詳細閱讀:《有關IM羣聊技術實現的文章彙總》。

寫擴散優勢:

  • 1)讀操做很輕量;
  • 2)能夠很方便地作消息的多終端同步。

寫擴散缺點:寫操做很重,尤爲是對於羣聊來講(由於若是羣成員不少的話,1條消息源要擴散寫成「成員數-1」條目標消息,這是很恐怖的)。

在Feeds系統中:

  • 1)寫擴散也叫:Push、Fan-out或者Write-fanout;
  • 2)讀擴散也叫:Pull、Fan-in或者Read-fanout。

五、惟一ID的技術方案

5.1 基礎知識

一般狀況下,ID設計主要有如下幾大類:

  • 1)UUID;
  • 2)基於Snowflake算法的ID生成方式;
  • 3)基於申請DB步長的生成方式;
  • 4)基於Redis或者DB的自增ID生成方式;
  • 5)特殊的規則生成惟一ID。
  • ... ...

具體的實現方法跟優缺點能夠參考如下IM消息ID的專題文章:

IM消息ID技術專題(一):微信的海量IM聊天消息序列號生成實踐(算法原理篇)

IM消息ID技術專題(二):微信的海量IM聊天消息序列號生成實踐(容災方案篇)

IM消息ID技術專題(三):解密融雲IM產品的聊天消息ID生成策略

IM消息ID技術專題(四):深度解密美團的分佈式ID生成算法

IM消息ID技術專題(五):開源分佈式ID生成器UidGenerator的技術實現

IM消息ID技術專題(六):深度解密滴滴的高性能ID生成器(Tinyid)

在IM系統中須要惟一Id的地方主要是:

  • 1)聊天會話ID;
  • 2)聊天消息ID。

5.2 消息ID

咱們來看看在設計消息ID時須要考慮的三個問題。

5.2.1)消息ID不遞增能夠嗎?

咱們先看看不遞增的話會怎樣:

  • 1)使用字符串,浪費存儲空間,並且不能利用存儲引擎的特性讓相鄰的消息存儲在一塊兒,下降消息的寫入跟讀取性能;
  • 2)使用數字,但數字隨機,也不能利用存儲引擎的特性讓相鄰的消息存儲在一塊兒,會加大隨機IO,下降性能;並且隨機的ID很差保證ID的惟一性。

所以,消息ID最好是遞增的。

5.2.3)全局遞增 vs 用戶級別遞增 vs 會話級別遞增:

全局遞增:指消息ID在整個IM系統隨着時間的推移是遞增的。全局遞增的話通常可使用Snowflake(固然,Snowflake也只是worker級別的遞增)。此時,若是你的系統是讀擴散的話爲了防止消息丟失,那每一條消息就只能帶上上一條消息的ID,前端根據上一條消息判斷是否有丟失消息,有消息丟失的話須要從新拉一次。

用戶級別遞增:指消息ID只保證在單個用戶中是遞增的,不一樣用戶之間不影響而且可能重複。典型表明:微信(見《微信的海量IM聊天消息序列號生成實踐(算法原理篇)》)。若是是寫擴散系統的話信箱時間線ID跟消息ID須要分開設計,信箱時間線ID用戶級別遞增,消息ID全局遞增。若是是讀擴散系統的話感受使用用戶級別遞增必要性不是很大。

會話級別遞增:指消息ID只保證在單個會話中是遞增的,不一樣會話之間不影響而且可能重複。典型表明:QQ。

5.2.3)連續遞增 vs 單調遞增:

連續遞增是指ID按 1,2,3...n 的方式生成;而單調遞增是指只要保證後面生成的ID比前面生成的ID大就能夠了,不須要連續。

據我所知:QQ的消息ID就是在會話級別使用的連續遞增,這樣的好處是,若是丟失了消息,當下一條消息來的時候發現ID不連續就會去請求服務器,避免丟失消息。

此時,可能有人會想,我不能用定時拉的方式看有沒有消息丟失嗎?固然不能,由於消息ID只在會話級別連續遞增的話那若是一我的有上千個會話,那得拉多少次啊,服務器確定是抗不住的。

對於讀擴散來講,消息ID使用連續遞增就是一種不錯的方式了。若是使用單調遞增的話當前消息須要帶上前一條消息的ID(即聊天消息組成一個鏈表),這樣,才能判斷消息是否丟失。

5.2.4)小結一下:

寫擴散:信箱時間線ID使用用戶級別遞增,消息ID全局遞增,此時只要保證單調遞增就能夠了。

讀擴散:消息ID可使用會話級別遞增而且最好是連續遞增。

5.3 會話ID

咱們來看看設計會話ID須要注意的問題。

其中,會話ID有種比較簡單的生成方式——特殊的規則生成惟一ID:即拼接 from_user_id 跟 to_user_id。

拼接邏輯能夠像下面這樣:

  • 1)若是 from_user_id 跟 to_user_id 都是32位整形數據的話能夠很方便地用位運算拼接成一個64位的會話ID,即: conversation_id = ${from_user_id} << 32 | ${to_user_id} (在拼接前須要確保值比較小的用戶ID是 from_user_id,這樣任意兩個用戶發起會話能夠很方便地知道會話ID);
  • 2)若是from_user_id 跟 to_user_id 都是64位整形數據的話那就只能拼接成一個字符串了,拼接成字符串的話就比較傷了,浪費存儲空間性能又很差。

前東家就是使用的上面第1種方式,第1種方式有個硬傷:隨着業務在全球的擴展,32位的用戶ID若是不夠用須要擴展到64位的話那就須要大刀闊斧地改了。32位整形ID看起來可以容納21億個用戶,但一般咱們爲了防止別人知道真實的用戶數據,使用的ID一般不是連續的,這時32位的用戶ID就徹底不夠用了。該設計徹底依賴於用戶ID,不是一種可取的設計方式。

所以:會話ID的設計可使用全局遞增的方式,加一個映射表:保存from_user_id、to_user_id跟conversation_id的關係。

六、新息的「推模式 vs 拉模式 vs 推拉結合模式」

在IM系統中,新消息的獲取一般會有三種可能的作法:

  • 1)推模式:有新消息時服務器主動推給全部端(iOS、Android、PC等);
  • 2)拉模式:由前端主動發起拉取消息的請求,爲了保證消息的實時性,通常採用推模式,拉模式通常用於獲取歷史消息;
  • 3)推拉結合模式:有新消息時服務器會先推一個有新消息的通知給前端,前端接收到通知後就向服務器拉取消息。

推模式簡化圖以下:

如上圖所示:正常狀況下,用戶發的消息通過服務器存儲等操做後會推給接收方的全部端。

但推是有可能會丟失的:最多見的狀況就是用戶可能會僞在線(是指若是推送服務基於長鏈接,而長鏈接可能已經斷開,即用戶已經掉線,但通常須要通過一個心跳週期後服務器才能感知到,這時服務器會錯誤地覺得用戶還在線;僞在線是本人本身想的一個概念,沒想到合適的詞來解釋)。所以若是單純使用推模式的話,是有可能會丟失消息的。

PS:爲何會出現做者所述「僞在線」這個問題,能夠讀一下《爲何說基於TCP的移動端IM仍然須要心跳保活?》。

推拉結合模式簡化圖以下:

可使用推拉結合模式解決推模式可能會丟消息的問題:即在用戶發新消息時服務器推送一個通知,而後前端請求最新消息列表,爲了防止有消息丟失,能夠再每隔一段時間主動請求一次。能夠看出,使用推拉結合模式最好是用寫擴散,由於寫擴散只須要拉一條時間線的我的信箱就行了,而讀擴散有N條時間線(每一個信箱一條),若是也定時拉取的話性能會不好。

七、業界的IM解決方案參考

前面瞭解了IM系統的常見設計問題,接下來咱們再看看業界是怎麼設計IM系統的。

研究業界的主流方案有助於咱們深刻理解IM系統的設計。如下研究都是基於網上已經公開的資料,不必定正確,你們僅做參考就行了。

7.1 微信

雖然微信不少基礎框架都是自研,但這並不妨礙咱們理解微信的架構設計。

從微信公開的《快速裂變:見證微信強大後臺架構從0到1的演進歷程(二)》這篇文章能夠看出,微信採用的主要是:寫擴散 + 推拉結合。因爲羣聊使用的也是寫擴散,而寫擴散很消耗資源,所以微信羣有人數上限(目前是500)。因此這也是寫擴散的一個明顯缺點,若是須要萬人羣就比較難了。

從文中還能夠看出,微信採用了多數據中心架構:

▲ 圖片引用自《快速裂變:見證微信強大後臺架構從0到1的演進歷程(二)

微信每一個數據中心都是自治的,每一個數據中心都有全量的數據,數據中心間經過自研的消息隊列來同步數據。

爲了保證數據的一致性,每一個用戶都只屬於一個數據中心,只能在本身所屬的數據中心進行數據讀寫,若是用戶連了其它數據中心則會自動引導用戶接入所屬的數據中心。而若是須要訪問其它用戶的數據那隻須要訪問本身所屬的數據中心就能夠了。

同時,微信使用了三園區容災的架構,使用Paxos來保證數據的一致性。

從微信公開的《微信的海量IM聊天消息序列號生成實踐(容災方案篇)》這篇文章能夠看出,微信的ID設計採用的是:基於申請DB步長的生成方式 + 用戶級別遞增。

以下圖所示:

▲ 圖片引用自《微信的海量IM聊天消息序列號生成實踐(容災方案篇)

微信的序列號生成器由仲裁服務生成路由表(路由表保存了uid號段到AllocSvr的全映射),路由表會同步到AllocSvr跟Client。若是AllocSvr宕機的話會由仲裁服務從新調度uid號段到其它AllocSvr。

PS:微信團隊分享了大量的技術資料,有興趣能夠看看《QQ、微信技術分享 - 彙總》。

7.2 釘釘

釘釘公開的資料很少,從《阿里釘釘技術分享:企業級IM王者——釘釘在後端架構上的過人之處》這篇文章咱們只能知道,釘釘最開始使用的是寫擴散模型,爲了支持萬人羣,後來貌似優化成了讀擴散。

但聊到阿里的IM系統,不得不提的是阿里自研的Tablestore:通常狀況下,IM系統都會有一個自增ID生成系統,但Tablestore創造性地引入了主鍵列自增,即把ID的生成整合到了DB層,支持了用戶級別遞增(傳統MySQL等DB只能支持表級自增,即全局自增),具體能夠參考:《如何優化高併發IM系統架構》。

PS:釘釘團隊公開的技術不多,這是另外一篇:《釘釘——基於IM技術的新一代企業OA平臺的技術挑戰(視頻+PPT)》,有興趣能夠研究研究。

7.3 Twitter

什麼?Twitter不是Feeds系統嗎?這篇文章不是討論IM的嗎?

是的,Twitter是Feeds系統,但Feeds系統跟IM系統其實有不少設計上的共性,研究下Feeds系統有助於咱們在設計IM系統時進行參考。再說了,研究下Feeds系統也沒有壞處,擴展下技術視野嘛。

Twitter的自增ID設計估計你們都耳熟能詳了,即大名鼎鼎的 Snowflake,所以ID是全局遞增的。

從這個視頻分享《How We Learned to Stop Worrying and Love Fan-In at Twitter》能夠看出,Twitter一開始使用的是寫擴散模型,Fanout Service負責擴散寫到Timelines Cache(使用了Redis),Timeline Service負責讀取Timeline數據,而後由API Services返回給用戶。

但因爲寫擴散對於大V來講寫的消耗太大,所以後面Twitter又使用了寫擴散跟讀擴散結合的方式。

以下圖所示:

對於粉絲數很少的用戶若是發Twitter使用的仍是寫擴散模型,由Timeline Mixer服務將用戶的Timeline、大V的寫Timeline跟系統推薦等內容整合起來,最後再由API Services返回給用戶。

7.4 58到家

58到家實現了一個通用的實時消息平臺:

▲ 圖片引用自《58到家實時消息系統的架構設計及技術選型經驗總結

能夠看出:msg-server保存了應用跟MQ主題之間的對應關係,msg-server根據這個配置將消息推到不一樣的MQ隊列,具體的應用來消費就能夠了。所以,新增一個應用只須要修改配置就能夠了。

58到家爲了保證消息投遞的可靠性,還引入了確認機制:消息平臺收到消息先落地數據庫,接收方收到後應用層ACK再刪除。使用確認機制最好是隻能單點登陸,若是多端可以同時登陸的話那就比較麻煩了,由於須要全部端都確認收到消息後才能刪除。

PS:58到家平臺部負責人任桃術還分享過《58到家實時消息系統的協議設計等技術實踐分享》一文,有興趣能夠一併閱讀。

看到這裏,估計你們已經明白了,設計一個IM系統頗有挑戰性。咱們仍是繼續來看設計一個IM系統須要考慮的問題吧。

7.5 其它業界方案

即時通信網也收錄了大量其它的業界IM或類IM系統的設計方案,限於篇幅緣由這裏就不一一列出,有興趣能夠選擇性地閱讀,一下是文章彙總。

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

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

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

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

現代IM系統中聊天消息的同步和存儲方案探討

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

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

從游擊隊到正規軍(三):基於Go的馬蜂窩旅遊網分佈式IM系統技術實踐

瓜子IM智能客服系統的數據架構設計(整理自現場演講,有配套PPT)

阿里技術分享:電商IM消息平臺,在羣聊、直播場景下的技術實踐

一套億級用戶的IM架構技術乾貨(上篇):總體架構、服務拆分等

八、IM須要解決的技術痛點

8.1 如何保證消息的實時性

PS:若是你還不瞭解IM裏的消息實時性是什麼,務必先讀這篇《零基礎IM開發入門(二):什麼是IM系統的實時性?》;

在通訊協議的選擇上,咱們主要有如下幾個選擇:

無論使用哪一種方式,咱們都可以作到消息的實時通知,但影響咱們消息實時性的可能會在咱們處理消息的方式上。

例如:假如咱們推送的時候使用MQ去處理並推送一個萬人羣的消息,推送一我的須要2ms,那麼推完一萬人須要20s,那麼後面的消息就阻塞了20s。若是咱們須要在10ms內推完,那麼咱們推送的併發度應該是:人數:10000 / (推送總時長:10 / 單我的推送時長:2) = 2000

所以:咱們在選擇具體的實現方案的時候必定要評估好咱們系統的吞吐量,系統的每個環節都要進行評估壓測。只有把每個環節的吞吐量評估好了,才能保證消息推送的實時性。

IM消息實時性中羣聊消息和單聊消息的處理又有很大區別,有興趣能夠深刻閱讀:

IM消息送達保證機制實現(一):保證在線實時消息的可靠投遞

移動端IM中大規模羣消息的推送如何保證效率、實時性?

8.2 如何保證消息時序

在IM的技術實現中,如下狀況下消息可能會亂序(提示:若是你還不瞭解什麼是IM的消息時序,務必先閱讀《零基礎IM開發入門(四):什麼是IM系統的消息時序一致性?》)。

8.2.1)發送消息若是使用的不是長鏈接,而是使用HTTP的話可能會出現亂序:

由於後端通常是集羣部署,使用HTTP的話請求可能會打到不一樣的服務器,因爲網絡延遲或者服務器處理速度的不一樣,後發的消息可能會先完成,此時就產生了消息亂序。

解決方案:

  • 1)前端依次對消息進行處理,發送完一個消息再發送下一個消息。這種方式會下降用戶體驗,通常狀況下不建議使用;
  • 2)帶上一個前端生成的順序ID,讓接收方根據該ID進行排序。這種方式前端處理會比較麻煩一點,並且聊天的過程當中接收方的歷史消息列表中可能會在中間插入一條消息,這樣會很奇怪,並且用戶可能會漏讀消息。但這種狀況能夠經過在用戶切換窗口的時候再進行重排來解決,接收方每次收到消息都先往最後面追加。

8.2.2)一般爲了優化體驗,有的IM系統可能會採起異步發送確認機制(例如:QQ):

即消息只要到達服務器,而後服務器發送到MQ就算髮送成功。若是因爲權限等問題發送失敗的話後端再推一個通知下去。

這種狀況下MQ就要選擇合適的Sharding策略了:

  • 1)按to_user_id進行Sharding:使用該策略若是須要作多端同步的話發送方多個端進行同步可能會亂序,由於不一樣隊列的處理速度可能會不同。例如發送方先發送m1而後發送m2,但服務器可能會先處理完m2再處理m1,這裏其它端會先收到m2而後是m1,此時其它端的會話列表就亂了;
  • 2)按conversation_id進行Sharding:使用該策略一樣會致使多端同步會亂序;
  • 3)按from_user_id進行Sharding:這種狀況下使用該策略是比較好的選擇。

一般爲了優化性能,推送前可能會先往MQ推,這種狀況下使用to_user_id纔是比較好的選擇。

PS:實際上,致使IM消息亂序的可能性還有不少,這裏就不一一展開,如下幾篇值得深刻閱讀。

如何保證IM實時消息的「時序性」與「一致性」?

一個低成本確保IM消息時序的方法探討

一套億級用戶的IM架構技術乾貨(下篇):可靠性、有序性、弱網優化等

8.3 用戶在線狀態如何作

不少IM系統都須要展現用戶的狀態:是否在線,是否忙碌等。

要實現用戶在線狀態的存儲,主要可使用:

  • 1)Redis;
  • 2)分佈式一致性哈希來。

Redis存儲用戶在線狀態:

看上面的圖可能會有人疑惑:爲何每次心跳都須要更新Redis?

若是我使用的是TCP長鏈接那是否是就不用每次心跳都更新了?

確實:正常狀況下服務器只須要在新建鏈接或者斷開鏈接的時候更新一下Redis就行了。但因爲服務器可能會出現異常,或者服務器跟Redis之間的網絡會出現問題,此時基於事件的更新就會出現問題,致使用戶狀態不正確。所以,若是須要用戶在線狀態準確的話最好經過心跳來更新在線狀態。

因爲Redis是單機存儲的,所以,爲了提升可靠性跟性能,咱們可使用Redis Cluster或者Codis。

分佈式一致性哈希存儲用戶在線狀態:

使用分佈式一致性哈希須要注意在對Status Server Cluster進行擴容或者縮容的時候要先對用戶狀態進行遷移,否則在剛操做時會出現用戶狀態不一致的狀況。同時還須要使用虛擬節點避免數據傾斜的問題。

PS:用戶狀態在客戶端的更新也是個頗有挑戰性的問題,有興趣能夠讀一下《IM單聊和羣聊中的在線狀態同步應該用「推」仍是「拉」?》。

8.4 多端同步怎麼作

8.4.1)讀擴散:

前面也提到過:對於讀擴散,消息的同步主要是以推模式爲主,單個會話的消息ID順序遞增,前端收到推的消息若是發現消息ID不連續就請求後端從新獲取消息。

但這樣仍然可能丟失會話的最後一條消息。

爲了加大消息的可靠性:能夠在歷史會話列表的會話裏再帶上最後一條消息的ID,前端在收到新消息的時候會先拉取最新的會話列表,而後判斷會話的最後一條消息是否存在,若是不存在,消息就可能丟失了,前端須要再拉一次會話的消息列表;若是會話的最後一條消息ID跟消息列表裏的最後一條消息ID同樣,前端就再也不處理。

這種作法的性能瓶頸會在拉取歷史會話列表那裏,由於每次新消息都須要拉取後端一次,若是按微信的量級來看,單是消息就可能會有20萬的QPS,若是歷史會話列表放到MySQL等傳統DB的話確定抗不住。

所以,最好將歷史會話列表存到開了AOF(用RDB的話可能會丟數據)的Redis集羣。這裏只能感慨性能跟簡單性不能兼得。

8.4.2)寫擴散:

對於寫擴散來講,多端同步就簡單些了。前端只須要記錄最後同步的位點,同步的時候帶上同步位點,而後服務器就將該位點後面的數據所有返回給前端,前端更新同步位點就能夠了。

PS:多端同步這也是IM裏比較坑爹的技術痛點,有興趣請移步《淺談移動端IM的多點登陸和消息漫遊原理》。

8.5 如何處理未讀數

在IM系統中,未讀數的處理很是重要。

未讀數通常分爲會話未讀數跟總未讀數,若是處理不當,會話未讀數跟總未讀數可能會不一致,嚴重下降用戶體驗。

8.5.1)讀擴散:

對於讀擴散來講,咱們能夠將會話未讀數跟總未讀數都存在後端,但後端須要保證兩個未讀數更新的原子性跟一致性。

通常能夠經過如下兩種方法來實現:

  • 1)使用Redis的multi事務功能,事務更新失敗能夠重試。但要注意若是你使用Codis集羣的話並不支持事務功能;
  • 2)使用Lua嵌入腳本的方式。使用這種方式須要保證會話未讀數跟總未讀數都在同一個Redis節點(Codis的話可使用Hashtag)。這種方式會致使實現邏輯分散,加大維護成本。

8.5.2)寫擴散:

對於寫擴散來講,服務端一般會弱化會話的概念,即服務端不存儲歷史會話列表。

未讀數的計算可由前端來負責,標記已讀跟標記未讀能夠只記錄一個事件到信箱裏,各個端經過重放該事件的形式來處理會話未讀數。

使用這種方式可能會形成各個端的未讀數不一致,至少微信就會有這個問題(Jack Jiang 注:實際上QQ也一樣有這個問題,在分佈式和多端IM中這確實是個很頭痛的問題,你們都不會例外,哈哈 ~~)。

若是寫擴散也經過歷史會話列表來存儲未讀數的話那用戶時間線服務跟會話服務緊耦合,這個時候須要保證原子性跟一致性的話那就只能使用分佈式事務了,會大大下降系統的性能。

8.6 如何存儲歷史消息

讀擴散:對於讀擴散,只須要按會話ID進行Sharding存儲一份就能夠了。

寫擴散:對於寫擴散,須要存儲兩份,一份是以用戶爲Timeline的消息列表,一份是以會話爲Timeline的消息列表。以用戶爲Timeline的消息列表能夠用用戶ID來作Sharding,以會話爲Timeline的消息列表能夠用會話ID來作Sharding。

PS:若是你對Timeline這個概念不熟悉,請讀這篇《現代IM系統中聊天消息的同步和存儲方案探討》。

8.7 數據冷熱分離

對於IM來講,歷史消息的存儲有很強的時間序列特性,時間越久,消息被訪問的機率也越低,價值也越低。

若是咱們須要存儲幾年甚至是永久的歷史消息的話(電商IM中比較常見),那麼作歷史消息的冷熱分離就很是有必要了。

數據的冷熱分離通常是HWC(Hot-Warm-Cold)架構。

對於剛發送的消息能夠放到Hot存儲系統(能夠用Redis)跟Warm存儲系統,而後由Store Scheduler根據必定的規則定時將冷數據遷移到Cold存儲系統。

獲取消息的時候須要依次訪問Hot、Warm跟Cold存儲系統,由Store Service整合數據返回給IM Service。

微信團隊分享的這篇《微信後臺基於時間序的海量數據冷熱分級架構設計實踐》,或許能夠有些啓發。

8.8 接入層怎麼作

對於分佈於IM來講,接入層是必需要考慮的的。

實現接入層的負載均衡主要有如下幾個方法:

  • 1)硬件負載均衡:例如F五、A10等等。硬件負載均衡性能強大,穩定性高,但價格很是貴,不是土豪公司不建議使用;
  • 2)使用DNS實現負載均衡:使用DNS實現負載均衡比較簡單,但使用DNS實現負載均衡若是須要切換或者擴容那生效會很慢,並且使用DNS實現負載均衡支持的IP個數有限制、支持的負載均衡策略也比較簡單;
  • 3)DNS + 4層負載均衡 + 7層負載均衡架構:例如 DNS + DPVS + Nginx 或者 DNS + LVS + Nginx;
  • 4)DNS + 4層負載均衡:4層負載均衡通常比較穩定,不多改動,比較適合於長鏈接。

對於第 3)點:有人可能會疑惑爲何要加入4層負載均衡呢?

這是由於7層負載均衡很耗CPU,而且常常須要擴容或者縮容,對於大型網站來講可能須要不少7層負載均衡服務器,但只須要少許的4層負載均衡服務器便可。所以,該架構對於HTTP等短鏈接大型應用頗有用。

固然,若是流量不大的話只使用DNS + 7層負載均衡便可。但對於長鏈接來講,加入7層負載均衡Nginx就不大好了。由於Nginx常常須要改配置而且reload配置,reload的時候TCP鏈接會斷開,形成大量掉線。

對於長鏈接的接入層,若是咱們須要更加靈活的負載均衡策略或者須要作灰度的話,那咱們能夠引入一個調度服務。

以下圖所示:

Access Schedule Service能夠實現根據各類策略來分配Access Service。

例如:

  • 1)根據灰度策略來分配;
  • 2)根據就近原則來分配;
  • 3)根據最少鏈接數來分配。

九、寫在最後

看完上面的內容你應該能深入體會到,要實現一個穩定可靠的大用戶量分佈式IM系統難度是至關大的,所謂路漫漫其修遠兮。。。

在不斷追求體驗更好、性能更高、負載更多、成本更低的動力下,IM架構優化這條路是沒有盡頭的,因此爲了延緩程序員髮量較少的焦慮,你們代碼必定要悠着點擼,頭涼是很難受滴 ~~

PS:本篇主要是從IM架構設計這個角度來說的,對於IM初學者來講是不容易看的明白,建議初學者從這篇開始:《新手入門一篇就夠:從零開發移動端IM》。

十、參考資料

[1] 58到家實時消息系統的架構設計及技術選型經驗總結

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

[3] 現代IM系統中聊天消息的同步和存儲方案探討

[4] 一套億級用戶的IM架構技術乾貨(上篇):總體架構、服務拆分等

[5] IM消息ID技術專題(二):微信的海量IM聊天消息序列號生成實踐(容災方案篇)

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

本文已同步發佈於「即時通信技術圈」公衆號。

▲ 本文在公衆號上的連接是:點此進入。同步發佈連接是:http://www.52im.net/thread-3472-1-1.html

相關文章
相關標籤/搜索