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

2021-09-23 17:47:48 字數 3427 閱讀 7051

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

**儲存新推出的主鍵列遞增功能可以有效地處理上述場景的需求。具體做法為在建立表時,宣告主鍵中的某一列為自增列,在寫入一行新資料的時候,應用無需為自增列填入真實值,只需填入乙個佔位符,**儲存系統在接收到這一行資料後會自動為自增列生成乙個值,並且保證在相同的分割槽鍵範圍內,後生成的值比先生成的值大.

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

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

我們要做的im聊天軟體需要支援下列功能:

傳送方傳送了一條訊息後,這條訊息被客戶端推送給應用伺服器,應用伺服器根據接收者的id,將訊息分發給其中乙個佇列,同乙個接收者的訊息位於同乙個佇列中,在佇列中,順序的處理每條訊息,先從自增id生成器中獲取乙個新的訊息id,然後將這條訊息寫入**儲存系統。寫成功後再寫入下一條訊息。

同乙個接收方的訊息會盡量在乙個佇列中,乙個佇列中可能會有多個傳送方的訊息。

群組內聊天時可能會出現同乙個時刻兩個使用者同時傳送了訊息,這兩個訊息可能會進入不同的應用伺服器,但是應用伺服器會將同乙個接收方的訊息發給同乙個佇列服務,這時候,對於同乙個接收方,這兩條訊息就會處於同乙個佇列中,如下圖:

每個佇列中的資料序列處理,每次寫入**儲存的時候,分配乙個新的id,比之前的id要大,為了保證訊息可以嚴格遞增,避免前乙個訊息寫失敗導致無法嚴格遞增的情況出現,需要在寫入資料到儲存系統的時候,持有乙個使用者級別的鎖,在沒有寫成功之前,同使用者的其他訊息不能繼續寫,以免當前訊息寫失敗後導致亂序,當寫成功後,釋放這個鎖,下乙個訊息繼續。

上一步中,如果佇列宕機,這些訊息需要重新處理,這時候,原有訊息就會進入乙個新的佇列,這時候新的佇列需要乙個新的訊息id,但要比之前已有的訊息id更大,而這個新佇列並不知道之前的最大id是啥,所以,這裡每個佇列沒法自主建立自增id,而需要乙個全域性的自增id生成器。

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

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

主鍵順序

主鍵名稱

主鍵值說明

1partition_key

md5(receive_id)前4位

分割槽鍵,保證資料均勻分布

2receive_id

receive_id

接收方的使用者id

3message_id

message_id

訊息id

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

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

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

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

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

主鍵順序

主鍵名稱

主鍵值說明

1partition_key

hash(receive_id)前4位

分割槽鍵,保證資料均勻分布,可以使用md5作為hash函式

2receive_id

receive_id

接收方的使用者id

3message_id

message_id

訊息id

第三列pk是message_id,這一列是主鍵自增列,建表時指定message_id列的屬性為auto_increment,且型別為integer。

private static void createtable(syncclient client)
通過上述方式就建立了乙個第三列pk為自動自增的表。

寫資料目前支援putrow和batchwriterow兩種方式,這兩種介面都支援主鍵列自增功能,寫資料時,第三列message_id是主鍵自增列,這一列不需要填值,只需要填入佔位符即可。

private static void putrow(syncclient client, string receive_id) 

// 列印出消耗的cu

capacityunit cu = response.getconsumedcapacity().getcapacityunit();

system.out.println("read capacityunit:" + cu.getreadcapacityunit());

system.out.println("write capacityunit:" + cu.getwritecapacityunit());

}

讀訊息的時候,需要通過getrange介面讀取最近的訊息,message_id這一列pk的起始位置是上一條訊息的message_id+1, 結束位置是inf_max,這樣每次都可以讀出最新的訊息,然後傳送給客戶端

private static void getrange(syncclient client, string receive_id, string lastmessageid) 

// 若nextstartprimarykey不為null, 則繼續讀取.

if (getrangeresponse.getnextstartprimarykey() != null) else }}

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

其他文章推薦:

如何高效儲存gps資料

使用maxcompute訪問tablestore(ots) 簡明手冊

高併發高負載系統架構

一 為什麼要進行高併發和高負載的研究 1 產品發展的需要 2 公司發展的需要 3 當前形式決定的 二 高併發和高負載的約束條件 1 硬體 2 部署 3 作業系統 4 web 伺服器 5 php 6 mysql 7 測試 三 解決之道 硬體篇 處理能力的提公升 部署多顆cpu,選擇多核心 具備更高運算...

高併發高負載系統架構

首先呢,我羅列一下文章的目錄,讓大家有個整體輪廓的了解!1 為什麼要進行高併發和高負載的研究 2 高併發和高負載的約束條件 3 解決之道 硬體篇 4 解決之道 部署篇 5 解決之道 環境篇 6 解決之道 siteengine篇 7 解決之道 測試篇 8 結尾 1 為什麼要進行高併發和高負載的研究 1...

PHP高併發高負載系統架構

處理能力的提公升最直接的反應在於web請求的處理效率和php程式的執行效率。記憶體頻寬與容量 更大的記憶體頻寬和容量 記憶體頻寬與容量的提公升最直接的反應在於應對資料庫大量的資料交換。磁碟搜尋與i o能力 選擇更高的轉速 更大的硬碟快取 元件磁碟陣列 raid 磁碟搜尋與i o能力的提公升最直接反應...