1. 程式人生 > 其它 >現代IM系統中的訊息系統架構 - 架構篇

現代IM系統中的訊息系統架構 - 架構篇

前言

IM全稱是『Instant Messaging』,中文名是即時通訊。在這個高度資訊化的移動網際網路時代,生活中IM類產品已經成為必備品,比較有名的如釘釘、微信、QQ等以IM為核心功能的產品。當然目前微信已經成長為一個生態型產品,但其核心功能還是IM。還有一些非以IM系統為核心的應用,最典型的如一些線上遊戲、社交應用,IM也是其重要的功能模組。可以說,IM系統已經是任何一個帶有社交屬性的應用需要具備的基礎功能,網路上對於這類系統的設計與實現的討論也越來越多。

IM系統在網際網路初期即存在,其基礎技術架構在這十幾年的發展中更新迭代多次,從早期的CS、P2P架構,到現在後臺已經演變為一個複雜的分散式系統,涉及移動端、網路通訊、協議、安全、儲存和搜尋等技術的方方面面。IM系統中最核心的部分是訊息系統,訊息系統中最核心的功能是訊息的同步、儲存和檢索:

  • 訊息的同步:將訊息完整的、快速的從傳送方傳遞到接收方,就是訊息的同步。訊息同步系統最重要的衡量指標就是訊息傳遞的實時性、完整性以及能支撐的訊息規模。從功能上來說,一般至少要支援線上和離線推送,高階的IM系統還支援『多端同步』。
  • 訊息的儲存:訊息儲存即訊息的持久化儲存,傳統訊息系統通常只能支援訊息在接收端的本地儲存,資料基本不具備可靠性。現代訊息系統能支援訊息在服務端的線上儲存,功能上對應的就是『訊息漫遊』,訊息漫遊的好處是可以實現賬號在任意端登陸檢視所有歷史訊息。
  • 訊息的檢索:訊息一般是文字,所以支援全文檢索也是必備的能力之一。傳統訊息系統通常來說也是隻能支援訊息的本地檢索,基於本地儲存的訊息資料來構建。而現在訊息系統在能支援訊息的線上儲存後,也具備了訊息的『線上檢索』能力。

本篇文章內容主要涉及IM系統中的訊息系統架構,會介紹一種基於阿里雲表格儲存Tablestore的Timeline模型構建的訊息系統。基於Tablestore Timeline構建的現代訊息系統,能夠同時支援訊息系統的眾多高階特性,包括『多端同步』、『訊息漫遊』和『線上檢索』。在效能和規模上,能夠做到全量訊息雲端儲存和索引,百萬TPS寫入以及毫秒級延遲的訊息同步和檢索能力。

之後我們會繼續發表兩篇文章,來更詳細介紹Tablestore Timeline模型概念及使用:

  • 模型篇:詳細介紹Tablestore Timeline模型的基本概念和基礎資料結構,並結合IM系統進行基本的建模。
  • 實現篇
    :會基於Tablestore Timeline實現一個具備『多端同步』、『訊息漫遊』和『線上檢索』這些高階功能的簡易IM系統,並共享我們的原始碼。

傳統架構 vs 現代架構

傳統架構下,訊息是先同步後儲存。對於線上的使用者,訊息會直接實時同步到線上的接收方,訊息同步成功後,並不會在服務端持久化。而對於離線的使用者或者訊息無法實時同步成功時,訊息會持久化到離線庫,當接收方重新連線後,會從離線庫拉取所有未讀訊息。當離線庫中的訊息成功同步到接收方後,訊息會從離線庫中刪除。傳統的訊息系統,服務端的主要工作是維護髮送方和接收方的連線狀態,並提供線上訊息同步和離線訊息快取的能力,保證訊息一定能夠從傳送方傳遞到接收方。服務端不會對訊息進行持久化,所以也無法支援訊息漫遊。訊息的持久化儲存及索引同樣只能在接收端本地實現,資料可靠性極低。

現代架構下,訊息是先儲存後同步。先儲存後同步的好處是,如果接收方確認接收到了訊息,那這條訊息一定是已經在雲端儲存了。並且訊息會有兩個庫來儲存,一個是訊息儲存庫,用於全量儲存所有會話的訊息,主要用於支援訊息漫遊。另一個是訊息同步庫,主要用於接收方的多端同步。訊息從傳送方發出後,經過服務端轉發,服務端會先將訊息儲存到訊息儲存庫,後儲存到訊息同步庫。完成訊息的持久化儲存後,對於線上的接收方,會直接選擇線上推送。但線上推送並不是一個必須路徑,只是一個更優的訊息傳遞路徑。對於線上推送失敗或者離線的接收方,會有另外一個統一的訊息同步方式。接收方會主動的向服務端拉取所有未同步訊息,但接收方何時來同步以及會在哪些端來同步訊息對服務端來說是未知的,所以要求服務端必須儲存所有需要同步到接收方的訊息,這是訊息同步庫的主要作用。對於新的同步裝置,會有訊息漫遊的需求,這是訊息儲存庫的主要作用,在訊息儲存庫中,可以拉取任意會話的全量歷史訊息。訊息檢索的實現依賴於對訊息儲存庫內訊息的索引,通常是一個近實時(NRT,near real time)的索引構建過程,這個索引同樣是線上的。

以上就是傳統架構和現代架構的一個簡單的對比,現代架構上整個訊息的同步、儲存和索引流程,並沒有變複雜太多。現代架構的實現本質上是把傳統架構內本地儲存和索引都搬到雲上,最大挑戰是需要集中管理全量訊息的儲存和索引,帶來的好處是能實現多端同步、訊息漫遊以及線上檢索。可以看到現代架構中最核心的就是兩個訊息庫『訊息同步庫』和『訊息儲存庫』,以及對『訊息儲存庫』的『訊息索引』的實現,接下來我們逐步拆解這幾個核心的設計和實現。

基礎模型

在深入講解訊息系統的設計和實現之前,需要對訊息系統內的幾個基本概念和基礎模型有一個理解。網上分析的很多的不同型別的訊息系統實現,實現差異上主要在訊息同步和儲存的方案上,在訊息的資料模型上其實有很大的共性。圍繞資料同步模型的討論主要在『讀擴散』、『寫擴散』和『混合模式』這三種方案,目前還沒有更多的選擇。而對於資料模型的抽象,還沒有一個標準的定義。

本章節會介紹下表格儲存Tablestore提出的Timeline模型,這是一個對訊息系統內訊息模型的一個抽象,能簡化和更好的讓開發者理解訊息系統內的訊息同步和儲存模型,基於此模型我們會再深入探討訊息的同步和儲存的選擇和實現。

Timeline模型

Timeline是一個對訊息抽象的邏輯模型,該模型會幫助我們簡化對訊息同步和儲存模型的理解,而訊息同步庫和儲存庫的設計和實現也是圍繞Timeline的特性和需求來展開。

如圖是Timeline模型的一個抽象表述,Timeline可以簡單理解為是一個訊息佇列,但這個訊息佇列有如下特性:

  • 每條訊息對應一個順序ID:每個訊息擁有一個唯一的順序ID(SequenceId),佇列訊息按SequenceId排序。
  • 新訊息寫入能自動分配遞增的順序ID,保證永遠插入隊尾:Timeline中是根據同步位點也就是順序ID來同步訊息,所以需要保證新寫入的訊息資料的順序ID絕對不能比已同步的訊息的順序ID還小,否則會導致資料漏同步,所以需要支援對新寫入的資料自動分配比當前已儲存的所有訊息的順序ID更大的順序ID。
  • 新訊息寫入也能自定義順序ID,滿足自定義排序需求:上面提到的自動分配順序ID,主要是為了滿足訊息同步的需求,訊息同步要求訊息是根據『已同步』或是『已寫入』的順序來排序。而訊息的儲存,通常要求訊息能根據會話順序來排序,會話順序通常由端的會話來決定,而不是服務端的同步順序來定,這是兩種順序要求。
  • 支援根據順序ID的隨機定位:可根據SequenceId隨機定位到Timeline中的某個位置,從這個位置開始正序或逆序的讀取訊息,也可支援讀取指定順序ID的某條訊息。
  • 支援對訊息的自定義索引:訊息體內資料根據業務不同會包含不同的欄位,Timeline需要支援對不同欄位的自定義索引,來支援對訊息內容的全文索引,或者是任意欄位的靈活條件組合查詢。

訊息同步可以基於Timeline很簡單的實現,圖中的例子中,訊息傳送方是A,訊息接收方是B,同時B存在多個接收端,分別是B1、B2和B3。A向B傳送訊息,訊息需要同步到B的多個端,待同步的訊息通過一個Timeline來進行交換。A向B傳送的所有訊息,都會儲存在這個Timeline中,B的每個接收端都是獨立的從這個Timeline中拉取訊息。每個接收端同步完畢後,都會在本地記錄下最新同步到的訊息的SequenceId,即最新的一個位點,作為下次訊息同步的起始位點。服務端不會儲存各個端的同步狀態,各個端均可以在任意時間從任意點開始拉取訊息。

訊息儲存也是基於Timeline實現,和訊息同步唯一的區別是,訊息儲存要求服務端能夠對Timeline內的所有資料進行持久化,並且訊息採用會話順序來儲存,需要自定義順序ID。

訊息檢索基於Timeline提供的訊息索引來實現,能支援比較靈活的多欄位索引,根據業務的不同可有自由度較高的定製。

訊息儲存模型


如圖是基於Timeline的訊息儲存模型,訊息儲存要求每個會話都對應一個獨立的Timeline。如圖例子所示,A與B/C/D/E/F均發生了會話,每個會話對應一個獨立的Timeline,每個Timeline記憶體有這個會話中的所有訊息,訊息根據會話順序排序,服務端會對每個Timeline進行持久化儲存,也就擁有了訊息漫遊的能力。

訊息同步模型

訊息同步模型會比訊息儲存模型稍複雜一些,訊息的同步一般有讀擴散(也叫拉模式)和寫擴散(也叫推模式)兩種不同的方式,分別對應不同的Timeline物理模型。

如圖是讀擴散和寫擴散兩種不同同步模式下對應的不同的Timeline模型,按圖中的示例,A作為訊息接收者,其與B/C/D/E/F發生了會話,每個會話中的新的訊息都需要同步到A的某個端,看下讀擴散和寫擴散兩種模式下訊息如何做同步。

  • 讀擴散:訊息儲存模型中,每個會話的Timeline中儲存了這個會話的全量訊息。讀擴散的訊息同步模式下,每個會話中產生的新的訊息,只需要寫一次到其用於儲存的Timeline中,接收端從這個Timeline中拉取新的訊息。優點是訊息只需要寫一次,相比寫擴散的模式,能夠大大降低訊息寫入次數,特別是在群訊息這種場景下。但其缺點也比較明顯,接收端去同步訊息的邏輯會相對複雜和低效。接收端需要對每個會話都拉取一次才能獲取全部訊息,讀被大大的放大,並且會產生很多無效的讀,因為並不是每個會話都會有新訊息產生。
  • 寫擴散:寫擴散的訊息同步模式,需要有一個額外的Timeline來專門用於訊息同步,通常是每個接收端都會擁有一個獨立的同步Timeline(或者叫收件箱),用於存放需要向這個接收端同步的所有訊息。每個會話中的訊息,會產生多次寫,除了寫入用於訊息儲存的會話Timeline,還需要寫入需要同步到的接收端的同步Timeline。在個人與個人的會話中,訊息會被額外寫兩次,除了寫入這個會話的儲存Timeline,還需要寫入參與這個會話的兩個接收者的同步Timeline。而在群這個場景下,寫入會被更加的放大,如果這個群擁有N個參與者,那每條訊息都需要額外的寫N次。寫擴散同步模式的優點是,在接收端訊息同步邏輯會非常簡單,只需要從其同步Timeline中讀取一次即可,大大降低了訊息同步所需的讀的壓力。其缺點就是訊息寫入會被放大,特別是針對群這種場景。
    Timeline模型不會對選擇讀擴散還是寫擴散做約束,而是能同時支援兩種模式,因為本質上兩種模式的邏輯資料模型並無差別,只是訊息資料是用一個Timeline來支援多端讀還是複製到多個Timeline來支援多端讀的問題。

針對IM這種應用場景,訊息系統通常會選擇寫擴散這種訊息同步模式。IM場景下,一條訊息只會產生一次,但是會被讀取多次,是典型的讀多寫少的場景,訊息的讀寫比例大概是10:1。若使用讀擴散同步模式,整個系統的讀寫比例會被放大到100:1。一個優化的好的系統,必須從設計上去平衡這種讀寫壓力,避免讀或寫任意一維觸碰到天花板。所以IM系統這類場景下,通常會應用寫擴散這種同步模式,來平衡讀和寫,將100:1的讀寫比例平衡到30:30。當然寫擴散這種同步模式,還需要處理一些極端場景,例如萬人大群。針對這種極端寫擴散的場景,會退化到使用讀擴散。一個簡單的IM系統,通常會在產品層面限制這種大群的存在,而對於一個高階的IM系統,會採用讀寫擴散混合的同步模式,來滿足這類產品的需求。採用混合模式,會根據資料的不同型別和不同的讀寫負載,來決定用寫擴散還是讀擴散。

典型架構設計

如圖是一個典型的訊息系統架構,架構中包含幾個重要元件:

  • 端:作為訊息的傳送和接收端,通過連線訊息伺服器來發送和接收訊息。
  • 訊息伺服器:一組無狀態的伺服器,可水平擴充套件,處理訊息的傳送和接收請求,連線後端訊息系統。
  • 訊息佇列:新寫入訊息的緩衝佇列,訊息系統的前置訊息儲存,用於削峰填谷以及非同步消費。
  • 訊息處理:一組無狀態的消費處理伺服器,用於非同步消費訊息佇列中的訊息資料,處理訊息的持久化和寫擴散同步。
  • 訊息儲存和索引庫:持久化儲存訊息,每個會話對應一個Timeline進行訊息儲存,儲存的訊息建立索引來實現訊息檢索。
  • 訊息同步庫:寫擴散形式同步訊息,每個使用者的收件箱對應一個Timeline,同步庫內訊息不需要永久儲存,通常對訊息設定一個生命週期。
    新訊息會由端發出,通常訊息體中會攜帶訊息ID(用於去重)、邏輯時間戳(用於排序)、訊息型別(控制訊息、圖片訊息或者文字訊息等)、訊息體等內容。訊息會先寫入訊息佇列,作為底層儲存的一個臨時緩衝區。訊息佇列中的訊息會由訊息處理伺服器消費,可以允許亂序消費。訊息處理伺服器對訊息先儲存後同步,先寫入發件箱Timeline(儲存庫),後寫擴散至各個接收端的收件箱(同步庫)。訊息資料寫入儲存庫後,會被近實時的構建索引,索引包括文字訊息的全文索引以及多欄位索引(傳送方、訊息型別等)。

對於線上的裝置,可以由訊息伺服器主動推送至線上裝置端。對於離線裝置,登入後會主動向服務端同步訊息。每個裝置會在本地保留有最新一條訊息的順序ID,向服務端同步該順序ID後的所有訊息。

模型篇

前言

架構篇中我們介紹了現代IM訊息系統的架構,介紹了Timeline的抽象模型以及基於Timeline模型構建的一個支援『訊息漫遊』、『多端同步』和『訊息檢索』多種高階功能的訊息系統的典型架構。架構篇中為了簡化讀者對Tablestore Timeline模型的理解,概要性的對Timeline的基本邏輯模型做了介紹,以及對訊息系統中訊息的多種同步模式、儲存和索引的基本概念做了一個科普。 

本篇文章是對架構篇的一個補充,會對Tablestore的Timeline模型做一個非常詳盡的解讀,讓讀者能夠深入到實現層面瞭解Timeline的基本功能以及核心元件。最後我們還是會基於IM訊息系統這個場景,來看如何基於Tablestore Timeline實現IM場景下訊息同步、儲存和索引等基本功能。

Timeline模型

Timeline模型以『簡單』為設計目標,核心模組構成比較清晰明瞭,主要包括:

  • Store:Timeline儲存庫,類似資料庫的表的概念。
  • Identifier:用於區分Timeline的唯一標識。
  • Meta:用於描述Timeline的元資料,元資料描述採用free-schema結構,可自由包含任意列。
  • Queue:一個Timeline內所有Message儲存在Queue內。
  • Message:Timeline內傳遞的訊息體,也是一個free-schema的結構,可自由包含任意列。
  • Index:包含Meta Index和Message Index,可對Meta或Message內的任意列自定義索引,提供靈活的多條件組合查詢和搜尋。

Timeline Store

Timeline Store是Timeline的儲存庫,對應於資料庫內表的概念。上圖是Timeline Store的結構圖,Store內會儲存所有的Timeline資料。Timeline是一個面向海量訊息的資料模型,同時用於訊息儲存庫和同步庫,需要滿足多種要求:

  • 支撐海量資料儲存:對於訊息儲存庫來說,如果需要訊息永久儲存,則隨著時間的積累,資料規模會越來越大,需要儲存庫能應對長時間積累的海量訊息資料儲存,需要能達到PB級容量。
  • 低儲存成本:訊息資料的冷熱區分是很明顯的,大部分查詢都會集中在熱資料,所以對於冷資料需要有一個比較低成本的儲存方式,否則隨著時間的積累資料量不斷膨脹,儲存成本會非常大。
  • 資料生命週期管理:不管是對於訊息資料的儲存還是同步,資料都需要定義生命週期。儲存庫是用於線上儲存訊息資料本身,通常需要設定一個較長週期的儲存時間。而同步庫是用於寫擴散模式的線上或離線推送,通常設定一個較短的儲存時間。
  • 極高的寫入吞吐:各類場景下的訊息系統,除了類似微博、頭條這種型別的Feeds流系統,像絕大部分即時通訊或朋友圈這類訊息場景,通常是採用寫擴散的訊息同步模式,寫擴散要求底層儲存具備極高的寫入吞吐能力,以應對訊息洪峰。
  • 低延遲的讀:訊息系統通常是應用在線上場景,所以對於查詢要求低延遲。

Tablestore Timeline的底層是基於LSM儲存引擎的分散式資料庫,LSM的最大優勢就是對寫入非常友好,天然適合訊息寫擴散的模式。同時對查詢也做了極大優化,例如熱資料進快取、bloom filter等等。資料表採用Range Partition的分割槽模式,能提供水平擴充套件的服務能力,以及能自動探測並處理熱點分割槽的負載均衡策略。為了滿足同步庫和儲存庫對儲存的不同要求,也提供了一些靈活的自定義配置,主要包括:

  • Time to live(資料生命週期):可自定義資料生命週期,例如永久儲存,或者儲存N天。
  • Storage type(儲存型別):自定義儲存型別,對儲存庫來說,HDD是最好的選擇,對同步庫來說,SSD是最好的選擇。

Timeline Module

Timeline Store內能儲存海量的Timeline,單個Timeline的詳細結構圖如上,可以看到Timeline主要包含了三大部分:

  • Timeline Meta:元資料部分,用於描述Timeline,包括:
    • Identifier:用於唯一標識Timeline,可包含多個欄位。
    • Meta:用於描述Timeline的元資料,可包含任意個數任意型別的欄位。
    • Meta Index:元資料索引,可對元資料內任意屬性列建索引,支援多欄位條件組合查詢和檢索。
  • Timeline Queue:用於儲存和同步訊息的佇列,佇列中元素由兩部分組成:
    • Sequence Id:順序ID,佇列中用於定位Message的位點資訊,在佇列中順序ID保持遞增。
    • Message:佇列中承載訊息的實體,包含了訊息的完整內容。
  • Timeline Data:Timeline的資料部分就是Message,Message主要包含:
    • Message:訊息實體,其內部也可以包含任意數量任意型別欄位。
    • Message Index:訊息資料索引,可對訊息實體內任意列做索引,支援多欄位條件組合查詢和檢索。

IM訊息系統建模

以一個簡易版IM系統為例,來看如何基於Tablestore Timeline模型建模。按照上圖中的例子,存在A、B、C三個使用者,A與B發生單聊,A與C發生單聊,以及A、B、C組成一個群聊,來看下在這個場景下訊息同步、儲存以及讀寫流程分別如何基於Tablestore Timeline建模。

訊息同步模型

訊息同步選擇寫擴散模型,能完全利用Tablestore Timeline的優勢,以及針對IM訊息場景讀多寫少的特性,通過寫擴散來平衡讀寫,均衡整個系統的資源。寫擴散模型下,每個接收訊息的個體均擁有一個收件箱,所有需要同步至該個體的訊息需要投遞到其收件箱內。圖上例子中,A、B、C三個使用者分別擁有收件箱,每個使用者不同的裝置端,均從同一個收件箱內拉取新訊息。

訊息同步庫

收件箱儲存在同步庫內,同步庫中每個收件箱對應一個Timeline。根據圖上的例子,總共存在3個Timeline作為收件箱。每個訊息接收端儲存有本地最新拉取的訊息的SequenceID,每次拉取新訊息均是從該SequenceID開始拉取訊息。對同步庫的查詢會比較頻繁,通常是對最新訊息的查詢,所以要求熱資料儘量快取在記憶體中,能提供高併發低延遲的查詢。所以對同步庫的配置,一般是需要SSD儲存。訊息如果已經同步到了所有的終端,則代表收件箱內的該訊息已經被消費完畢,理論上可以清理。但設計上來說不做主動清理,而是給資料定義一個較短的生命週期來自動過期,一般定義為一週或者兩週。資料過期之後,如果仍要同步拉取新訊息,則需要退化到讀擴散的模式,從儲存庫中拉取訊息。

訊息儲存庫

訊息儲存庫中儲存有每個會話的訊息,每個會話的發件箱對應一個Timeline。發件箱內的訊息支援按會話維度拉取訊息,例如瀏覽某個會話內的歷史訊息則通過讀取發件箱完成。一般來說,新訊息通過線上推送或者查詢同步庫可投遞到各個接收端,所以對儲存庫的查詢會相對來說較少。而儲存庫用於長期儲存訊息,例如永久儲存,相對同步庫來說資料量會較大。所以儲存庫的選擇一般是HDD,資料生命週期根據訊息需要儲存的時間來定,通常是一個較長的時間。

訊息索引庫

訊息索引庫依附於儲存庫,使用了Timeline的Message Index,可以對儲存庫內的訊息進行索引,例如對文字內容的全文索引、收件人、發件人以及傳送時間的索引等,能支援全文檢索等高階查詢和搜尋。