高并發 IM 系統架構優化實踐

分類:技術 時間:2017-01-13

作者簡介:

少強,網名 無衣蒹葭, 阿里云資深工程師,主要做分布式存儲和搜索相關的工作。

摘要: 介紹如何設計一個穩定、高并發、消息保序的IM系統,以及如何通過使用存儲層的高級功能來優化系統架構。

在構建社交IM和朋友圈應用時,一個基本的需求是將用戶發送的消息和朋友圈更新及時準確的更新給該用戶的好友。 為了做到這一點,通常需要為用戶發送的每一條消息或者朋友圈更新設置一個序號或者ID,并且保證遞增,通過這一機制來確保所有的消息能夠按照完整并且以正確的順序被接收端處理。當消息總量或者消息發送的并發數很大的時候,我們通常選擇NoSQL存儲產品來存儲消息, 但常見的NoSQL產品都沒有提供自增列的功能,因此通常要借助外部組件來實現消息序號和ID的遞增,使得整體的架構更加復雜,也影響了整條鏈路的延時。

功能介紹

表格存儲新推出的主鍵列遞增功能 可以有效地處理上述場景的需求。 具體做法如下:

在創建表時,聲明主鍵中的某一列為自增列,在寫入一行新數據的時候,應用無需為自增列填入真實值,只需填入一個占位符,表格存儲系統在接收到這一行數據后會自動為自增列生成一個值,并且保證在相同的分區鍵范圍內,后生成的值比先生成的值大。

主鍵列自增功能具有以下幾個特性:

  • 表格存儲獨有的系統架構和主鍵自增列實現方式,可以保證生成的自增列的值唯一,且嚴格遞增。

  • 目前支持多個主鍵,第一個主鍵為分區鍵,為了數據的均勻分布,不允許設置分區健為自增列。

  • 因為分區健不允許設置為自增列,所以主鍵列自增是分區鍵級別的自增。

  • 除了分區鍵外,其余主鍵中的任意一個都可以被設置為遞增列。

  • 對于每張表,目前只允許設置一個主鍵列為自增列。

  • 屬性列不允許設置為自增列。

  • 自增列自動生成的值為64位的有符號長整型。

  • 自增列功能是表級別的,同一個實例下面可以有自增列的表,也可以有非自增列的表。

  • 僅支持在創建表的時候設置自增列,對于已存在的表不支持升級為自增列。

介紹了表格存儲的主鍵列自增功能后,下面通過具體的場景介紹下如何使用。

場景

接下來通過構建一個IM聊天工具,演示主鍵列自增功能的作用和使用方法。

我們要做的IM聊天軟件需要支持下列功能:

  • 支持用戶一對一聊天

  • 支持用戶群組內聊天

  • 支持同一個用戶的多終端消息同步

現有架構

1

確認消息模型

  1. 上圖展示這一消息模型

  2. 發送方發送了一條消息后,消息會被客戶端推送給后臺系統

  3. 后臺系統會先存儲消息

  4. 存儲成功后,會推送消息給接收方的客戶端

2

確認后臺架構

  • 后臺架構主要分為兩部分:邏輯層和存儲層。

  • 邏輯層包括應用服務器,隊列服務和自增ID生成器,是整個后臺架構的核心,處理消息的接收、推送、通知,群消息寫復制等核心業務邏輯。

  • 存儲層主要是用來持久化消息數據和其他一些需要持久化的數據。

  • 對于一對一聊天,發送方發送消息給應用服務器后,應用服務器將消息存到接收方為主鍵的表中,同時通知應用服務器中的消息推送服務有新消息了,消息推送服務會將上次推送給接收方的最后一條消息的消息ID作為起始主鍵,從存儲系統中讀取之后的所有消息,然后將消息推送給接收方。

  • 對于群組內的聊天,邏輯會更加復雜,需要通過異步隊列來完成消息的擴散寫,也就是說發到群組內的一條消息會給群組內的每個人都存一份。

上圖展示了省略掉存儲層后的群消息發送過程。 使用擴散寫而非擴散讀,主要是由于以下兩點原因:

  • 群組內成員一般都不多,存儲成本并不高,而且有壓縮,成本更低。

  • 消息擴散寫到每個人的存儲中(收件箱)后,為每個接收方推送消息時,只需要檢查自己的收件箱即可,這時候,群聊和單聊的處理邏輯一樣,實現簡單。

發送消息的過程如下:

發送方發送了一條消息后,這條消息被客戶端推送給應用服務器,應用服務器根據接收者的ID,將消息分發給其中一個隊列,同一個接收者的消息位于同一個隊列中,在隊列中,順序的處理每條消息,先從自增ID生成器中獲取一個新的消息ID,然后將這條消息寫入表格存儲系統。寫成功后再寫入下一條消息。

同一個接收方的消息會盡量在一個隊列中,一個隊列中可能會有多個接收方的消息。 群組內聊天時可能會出現同一個時刻兩個用戶同時發送了消息,這兩個消息可能會進入不同的應用服務器,但是應用服務器會將同一個接收方的消息發給同一個隊列服務,這時候,對于同一個接收方,這兩條消息就會處于同一個隊列中,如下圖:

每個隊列中的數據串行處理 ,每次寫入表格存儲的時候,分配一個新的ID,比之前的ID要大,為了保證消息可以嚴格遞增,避免前一個消息寫失敗導致無法嚴格遞增的情況出現,需要在寫入數據到存儲系統的時候,持有一個用戶級別的鎖,在沒有寫成功之前,同用戶的其他消息不能繼續寫,以免當前消息寫失敗后導致亂序,當寫成功后,釋放這個鎖,下一個消息繼續。

上一步中,如果隊列宕機,這些消息需要重新處理,這時候,原有消息就會進入一個新的隊列,這時候新的隊列需要一個新的消息ID,但要比之前已有的消息ID更大,而這個新隊列并不知道之前的最大ID是啥,所以,這里每個隊列沒法自主創建自增ID,而需要一個全局的自增ID生成器。

為了支持多終端, 在應用服務器中會為每個終端持有一個session,每個session持有一個當前最新消息的ID,當被通知有新消息時,會去存儲系統讀取當前消息之后的所有消息, 這樣就保證了多終端同時在線時,每個終端都可以同步消息,且相互不影響, 見下圖。

在多終端中,如果有部分終端由在線變成了離線,那么應用服務器會將這個終端的session保存到存儲系統的另一張表中,當一段時間后,這個終端再次上線時,可以從存儲系統中恢復出之前的session,繼續為此終端推送之前未讀取的消息。

3

確認存儲系統

存儲系統,我們選擇了阿里云的表格存儲,主要是因為下列原因:

  • 寫操作不僅支持單行寫,也支持多行批量寫,滿足大并發寫數據需求。

  • 支持按范圍讀,消息多時可翻頁。

  • 支持數據生命周期管理,對過期數據進行自動清理,節省存儲費用

  • 表格存儲是阿里云已經商業化的云服務,穩定可靠。

  • 表格存儲價格便宜,對于數據量大的用戶還可以以更優惠的價格購買套餐。

  • 讀寫性能優秀,對于聊天消息,延遲基本在毫秒,甚至微妙級別。

4

確定表結構

確定的表格存儲的表結構如下:


  • 表格存儲的表結構分為兩部分,主鍵列部分和屬性列部分,主鍵列部分最多支持4個主鍵,第一個主鍵為分區健。

  • 使用前,需要確定主鍵列部分的結構,使用過程中不能修改;屬性列部分是Schema Free的,用戶可以自由定制,每一行數據的屬性列部分可以不一樣,所以,只需要設計主鍵列部分的結構。

  • 第一個主鍵是分片鍵,目的是讓數據和請求可以均衡分布,避免熱點,由于最終讀取消息時是要按照接收方讀取,所以這里可以使用接收方ID作為分片鍵,為了更加均衡,可以使用接收方ID的md5值的部分區域,比如前4個字符。這樣就可以將數據均衡分布了。

  • 第一個主鍵只用了部分接收方ID,為了能定位到接收方的消息,需要保存完整的接收方ID,所以,可以將接收方ID作為第二個主鍵。

  • 第三個主鍵就可以是消息ID了,由于需要查詢最新的消息,這個值需要是單調自增的。

  • 屬性列可以存消息內容和元數據等。

到此,我們已經設計出了一個完整的聊天系統,雖然這個系統已經可以運行,且能處理大并發,性能也不差,但是還是存在一些挑戰。

挑戰

  • 多個用戶在一個隊列中,這個隊列串行執行,為了保證消息嚴格遞增,這里執行過程中要持有鎖,這里就會有一個風險點:如果發送給某個用戶的消息量很大,這個用戶所在的隊列中消息會變多,就有可能堵塞其他用戶的消息,導致同隊列的其他用戶消息出現延遲。

  • 當出現重大事件或者特定節假日,聊天信息量大的時候,隊列部分需要擴容,否則可能扛不住大壓力,導致整體系統延遲增大或者崩潰。

針對上述兩個問題,問題2可以通過增加機器的方式解決,但是問題1沒法通過增加機器解決,增加機器只能緩解問題,卻沒法徹底解決。那有沒有辦法可以徹底解決掉上述兩個問題?

新架構

上面兩個問題的復雜度主要是由于需要消息嚴格遞增引起的,如果使用了表格存儲的主鍵列自增功能,那么上層的應用層就會簡單的多。

使用了表格存儲**主鍵列自增功能**后的新架構如下:

  • 最明顯的區別是少了隊列服務和自增ID生成器兩個組件,架構更加簡單。

  • 應用服務器接收到消息后,直接將消息寫入表格存儲,對于主鍵自增列message_id,在寫數據時不需要填確定的值,只需要填充一個特定的占位符即可,這個值會在表格存儲系統內部自動生成。

  • 新架構中自增操作是在表格存儲系統內部處理的,就算多個應用服務器同時給表格存儲中的同一個接收方寫數據,表格存儲內部也能保證這些消息是串行處理,每個消息都有一個獨立的消息ID,且嚴格遞增。那么之前的隊列服務就不在需要了。這樣也就徹底解決了上面的問題1

  • 表格存儲系統是一個云服務,用戶并不需要考慮系統的容量,而且表格存儲支持按量付費,這樣也就徹底解決了上面的問題2

  • 之前只能有一個隊列處理同一個用戶的消息,現在可以多個隊列并行處理了,就算某些用戶的消息量突然變大,也不會立即堵塞其他用戶,而是將壓力均勻分布給了所有隊列。

  • 使用主鍵自增列功能后,應用服務器可以直接寫數據到表格存儲,不再需要經過隊列和獲取消息ID,性能表現會更加優秀。

實現

有了上面的架構圖后,現在可以開始實現了,這里選用JAVA SDK,目前4.2.0版本已經支持主鍵列自增功能。

注:4.2.0版本Java SDK文檔和下載地址

https://help.aliyun.com/document_detail/43005.html?spm=5176.100239.blogcont66461.21.218KAh

1

建表

按照之前的設計,表結構如下:


第三列PK是message_id,這一列是主鍵自增列,建表時指定message_id列的屬性為AUTO_INCREMENT,且類型為INTEGER。


通過上述方式就創建了一個第三列PK為自動自增的表。

2

寫數據

寫數據目前支持PutRow和BatchWriteRow兩種方式,這兩種接口都支持主鍵列自增功能,寫數據時,第三列message_id是主鍵自增列,這一列不需要填值,只需要填入占位符即可。



3

讀數據

讀消息的時候,需要通過GetRange接口讀取最近的消息,message_id這一列PK的起始位置是上一條消息的message_id 1, 結束位置是INF_MAX,這樣每次都可以讀出最新的消息,然后發送給客戶端。

上面演示了表格存儲及其主鍵列自增功能在聊天系統中的應用,在其他場景中也有很大的價值,期待大家一起去探索。

如何加入quot;云和恩墨大講堂quot;微信群

搜索 蓋國強(Eygle)微信號:eyygle,或者掃描下面二維碼,備注:云和恩墨大講堂,即可入群。每周與千人共享免費技術分享,與講師在線討論。

近期文章

性能優化之查詢轉換 - 子查詢類

基于Oracle公有云的備份與恢復

苦海岸邊,GR的基礎知識

Oracle 12c Global Data Services

MySQL Group Replication 學習筆記

利用硬鏈接和truncate降低drop table對線上影響

以12c Identity類型示范自我探索式學習方法

從執行計劃洞察ORACLE優化器的“小聰明”

Oracle安全比特幣勒索問題揭秘和防范


Tags: 并發 軟件架構

文章來源:http://mp.weixin.qq.com/s/hD9HWuQopYVXF-GqfPsvCA


ads
ads

相關文章
ads

相關文章

ad