日韩欧美国产精品免费一二-日韩欧美国产精品亚洲二区-日韩欧美国产精品专区-日韩欧美国产另-日韩欧美国产免费看-日韩欧美国产免费看清风阁

LOGO OA教程 ERP教程 模切知識(shí)交流 PMS教程 CRM教程 開(kāi)發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

百萬(wàn)級(jí)群聊的設(shè)計(jì)實(shí)踐

freeflydom
2025年3月5日 11:17 本文熱度 715

作者:來(lái)自 vivo 互聯(lián)網(wǎng)服務(wù)器團(tuán)隊(duì)- Cai Linfeng

 本文介紹了服務(wù)端在搭建 Web 版的百萬(wàn)人級(jí)別的群聊系統(tǒng)時(shí),遇到的技術(shù)挑戰(zhàn)和解決思路,內(nèi)容包括:通信方案選型、消息存儲(chǔ)、消息有序性、消息可靠性、未讀數(shù)統(tǒng)計(jì)。

 

一、引言

現(xiàn)在IM群聊產(chǎn)品多種多樣,有國(guó)民級(jí)的微信、QQ,企業(yè)級(jí)的釘釘、飛書(shū),還有許多公司內(nèi)部的IM工具,這些都是以客戶(hù)端為主要載體,而且群聊人數(shù)通常都是有限制,微信正常群人數(shù)上限是500,QQ2000人,收費(fèi)能達(dá)到3000人,這里固然有產(chǎn)品考量,但技術(shù)成本、資源成本也是很大的因素。而筆者業(yè)務(wù)場(chǎng)景上需要一個(gè)迭代更新快、輕量級(jí)(不依賴(lài)客戶(hù)端)、單群百萬(wàn)群成員的純H5的IM產(chǎn)品,本文將回顧實(shí)現(xiàn)一個(gè)百萬(wàn)人量級(jí)的群聊,服務(wù)器側(cè)需要考慮的設(shè)計(jì)要點(diǎn),希望可以給到讀者一些啟發(fā)。

 

二、背景介紹

不同的群聊產(chǎn)品,采用的技術(shù)方案是不同的,為了理解接下來(lái)的技術(shù)選型,需要先了解下這群聊產(chǎn)品的特性。

  1. 單群成員需要支撐百萬(wàn)人,同時(shí)在線(xiàn)百萬(wàn)級(jí)。

  2. 功能、體驗(yàn)要接近純客戶(hù)端實(shí)現(xiàn)方案。

  3. 用戶(hù)端完全用H5承載。

 

三、通信技術(shù)

即時(shí)通信常見(jiàn)的通信技術(shù)有短輪詢(xún)、長(zhǎng)輪詢(xún)、Server-Sent Events(SSE)、Websocket。短輪詢(xún)和長(zhǎng)輪詢(xún)適用于實(shí)時(shí)性要求不高的場(chǎng)景,比如論壇的消息提醒。SSE 適用于服務(wù)器向客戶(hù)端單向推送的場(chǎng)景,如實(shí)時(shí)新聞、股票行情。Websocket 適用于實(shí)時(shí)雙向通信的場(chǎng)景,實(shí)時(shí)性好,且服務(wù)端、前端都有比較成熟的三方包,如 socket.io,所以這塊在方案選擇中是比較 easy 的,前后端使用 Websocket 來(lái)實(shí)現(xiàn)實(shí)時(shí)通信。

 

四、消息存儲(chǔ)

群聊消息的保存方式,主流有2種方式:讀擴(kuò)散、寫(xiě)擴(kuò)散。圖1展示了它們的區(qū)別,區(qū)別就在于消息是寫(xiě)一次還是寫(xiě)N次,以及如何讀取。

 

圖1

 

讀擴(kuò)散就是所有群成員共用一個(gè)群信箱,當(dāng)一個(gè)群產(chǎn)生一條消息時(shí),只需要寫(xiě)入這個(gè)群的信箱即可,所有群成員從這一個(gè)信箱里讀取群消息。

優(yōu)點(diǎn)是寫(xiě)入邏輯簡(jiǎn)單,存儲(chǔ)成本低,寫(xiě)入效率高。缺點(diǎn)是讀取邏輯相對(duì)復(fù)雜,要通過(guò)消息表與其他業(yè)務(wù)表數(shù)據(jù)聚合;消息定制化處理復(fù)雜,需要額外的業(yè)務(wù)表;可能還有IO熱點(diǎn)問(wèn)題。

 

舉個(gè)例子:

很常見(jiàn)的場(chǎng)景,展示用戶(hù)對(duì)消息的已讀未讀狀態(tài),這個(gè)時(shí)候公共群信箱就無(wú)法滿(mǎn)足要求,必須增加消息已讀未讀表來(lái)記錄相關(guān)狀態(tài)。還有用戶(hù)對(duì)某條消息的刪除狀態(tài),用戶(hù)可以選擇刪除一條消息,但是其他人仍然可以看到它,此時(shí)也不適合在公共群信箱里拓展,也需要用到另一張關(guān)系表,總而言之針對(duì)消息做用戶(hù)特定功能時(shí)就會(huì)比寫(xiě)擴(kuò)散復(fù)雜。

寫(xiě)擴(kuò)散就是每個(gè)群成員擁有獨(dú)立的信箱,每產(chǎn)生一條消息,需要寫(xiě)入所有群成員信箱,群成員各自從自己的信箱內(nèi)讀取群消息。優(yōu)點(diǎn)是讀取邏輯簡(jiǎn)單,適合消息定制化處理,不存在IO熱點(diǎn)問(wèn)題。缺點(diǎn)是寫(xiě)入效率低,且隨著群成員數(shù)增加,效率降低;存儲(chǔ)成本大。

 

所以當(dāng)單群成員在萬(wàn)級(jí)以上時(shí),用寫(xiě)擴(kuò)散就明顯不太合適了,寫(xiě)入效率太低,而且可能存在很多無(wú)效寫(xiě)入,不活躍的群成員也必須得有信箱,存儲(chǔ)成本是非常大的,因此采用讀擴(kuò)散是比較合適的。

 

據(jù)了解,微信是采用寫(xiě)擴(kuò)散模式,微信群設(shè)定是500人上限,寫(xiě)擴(kuò)散的缺點(diǎn)影響就比較小。

 

五、架構(gòu)設(shè)計(jì)

5.1 整體架構(gòu)

先來(lái)看看群聊的架構(gòu)設(shè)計(jì)圖,如圖2所示:

 

圖2

 

從用戶(hù)登錄到發(fā)送消息,再到群用戶(hù)收到這條消息的系統(tǒng)流程如圖3所示:

 

圖3

 

  1. 用戶(hù)登錄,通過(guò)負(fù)載均衡,與連接服務(wù)建立 Websocket 長(zhǎng)連接。

  2. 連接服務(wù)管理會(huì)話(huà),管理群與用戶(hù)的映射關(guān)系,在本地內(nèi)存里使用哈希表存儲(chǔ),key為groupId,value為L(zhǎng)ist<SocketIOClient>,同一個(gè)群的用戶(hù)可能會(huì)在不同的集群服務(wù)器上。

  3. 連接服務(wù)向群組服務(wù)上報(bào)群組路由,上報(bào)它的內(nèi)網(wǎng)IP和它所管理的 groupIdList 的關(guān)系,這里需要2種同步策略并行保證群組路由信息的準(zhǔn)確性:a.在用戶(hù)建立、斷開(kāi)長(zhǎng)連接時(shí)即刻上報(bào);b.定時(shí)上報(bào)。

  4. 群組服務(wù)管理群組路由,使用遠(yuǎn)程中心緩存 Redis 管理 groupId 和連接服務(wù)器IP的關(guān)系,key 為 groupId,value 為 List,該IP為連接服務(wù)的內(nèi)網(wǎng)IP地址,這里會(huì)做上報(bào)的心跳判斷,超過(guò)3個(gè)心跳周期不上報(bào),則認(rèn)為已斷線(xiàn)。

  5. 用戶(hù)在群里發(fā)布一條消息,消息通過(guò) Websokcet 送達(dá)連接服務(wù),然后經(jīng)過(guò)連接服務(wù)——>消息隊(duì)列——>群組服務(wù),消息在群組服務(wù)里經(jīng)過(guò)頻控、安全檢查、格式轉(zhuǎn)換等一系列流程后入庫(kù),持久化。

  6. 群組服務(wù)通過(guò)群組路由管理獲取這條消息所屬群的路由信息,即一組連接服務(wù)的IP地址,然后通過(guò) HTTP 回調(diào)對(duì)應(yīng)的連接服務(wù),通知它們有新消息產(chǎn)生,這里只簡(jiǎn)單傳遞消息ID。

  7. 連接服務(wù)收到 HTTP 請(qǐng)求后,根據(jù)會(huì)話(huà)管理查詢(xún)?cè)撊核杏脩?hù),給用戶(hù)發(fā)送新消息提醒。

  8. 用戶(hù)收到新消息提醒,通過(guò) Websocket 來(lái)連接服務(wù)拉取該新消息具體詳情,然后根據(jù)消息協(xié)議展示在信息流里。

 

5.2 路由策略

用戶(hù)應(yīng)該連接到哪一臺(tái)連接服務(wù)呢?這個(gè)過(guò)程重點(diǎn)考慮如下2個(gè)問(wèn)題:

  1. 盡量保證各個(gè)節(jié)點(diǎn)的連接均衡;

  2. 增刪節(jié)點(diǎn)是否要做 Rebalance。

 

保證均衡有如下幾個(gè)算法:

  1. 輪詢(xún):挨個(gè)將各個(gè)節(jié)點(diǎn)分配給客戶(hù)端,但會(huì)出現(xiàn)新增節(jié)點(diǎn)分配不均勻的情況;

  2. 取模:類(lèi)似于 HashMap,但也會(huì)出現(xiàn)輪詢(xún)的問(wèn)題。當(dāng)然也可以像 HashMap 那樣做一次 Rebalance,讓所有的客戶(hù)端重新連接。不過(guò)這樣會(huì)導(dǎo)致所有的連接出現(xiàn)中斷重連,代價(jià)有點(diǎn)大。由于 Hash 取模方式的問(wèn)題帶來(lái)了一致性 Hash 算法,但依然會(huì)有一部分的客戶(hù)端需要 Rebalance;

  3. 權(quán)重:可以手動(dòng)調(diào)整各個(gè)節(jié)點(diǎn)的負(fù)載情況,甚至可以做成自動(dòng)的,基于監(jiān)控當(dāng)某些節(jié)點(diǎn)負(fù)載較高就自動(dòng)調(diào)低權(quán)重,負(fù)載較低的可以提高權(quán)重;筆者是采用輪詢(xún) + 權(quán)重模式,盡量保證負(fù)載均衡。

 

5.3 重連機(jī)制

當(dāng)應(yīng)用在擴(kuò)縮容或重啟升級(jí)時(shí),在該節(jié)點(diǎn)上的客戶(hù)端怎么處理?由于設(shè)計(jì)有心跳機(jī)制,當(dāng)心跳不通或監(jiān)聽(tīng)連接斷開(kāi)時(shí),就認(rèn)為該節(jié)點(diǎn)有問(wèn)題了,就嘗試重新連接;如果客戶(hù)端正在發(fā)送消息,那么就需要將消息臨時(shí)保存住,等待重新連接上后再次發(fā)送。

 

5.4 線(xiàn)程策略

將連接服務(wù)里的IO線(xiàn)程與業(yè)務(wù)線(xiàn)程隔離,提升整體性能,原因如下:

  1. 充分利用多核的并行處理能力:IO線(xiàn)程和業(yè)務(wù)線(xiàn)程隔離,雙方都可以并行處理網(wǎng)絡(luò)IO和業(yè)務(wù)邏輯,充分利用計(jì)算機(jī)多核并行計(jì)算能力,提升性能;

  2. 故障隔離:業(yè)務(wù)線(xiàn)程處理多種業(yè)務(wù)消息,有IO密集型,也有 CPU 密集型,有些是純內(nèi)存計(jì)算,不同的業(yè)務(wù)處理時(shí)延和故障率是不同的。如果把業(yè)務(wù)線(xiàn)程和IO線(xiàn)程合并,就會(huì)有如下問(wèn)題:某類(lèi)業(yè)務(wù)處理較慢,阻塞IO線(xiàn)程,導(dǎo)致其他處理較快的業(yè)務(wù)消息響應(yīng)不及時(shí);

  3. 可維護(hù)性:IO線(xiàn)程和業(yè)務(wù)線(xiàn)程隔離之后,職責(zé)單一,有利于維護(hù)和定位問(wèn)題。

 

5.5 有狀態(tài)鏈接

在這樣的場(chǎng)景中不像 HTTP 那樣是無(wú)狀態(tài)的,需要明確知道各個(gè)客戶(hù)端和連接的關(guān)系。比如需要向客戶(hù)端廣播群消息時(shí),首先得知道客戶(hù)端的連接會(huì)話(huà)保存在哪個(gè)連接服務(wù)節(jié)點(diǎn)上,自然這里需要引入第三方中間件來(lái)存儲(chǔ)這個(gè)關(guān)系。通過(guò)由連接服務(wù)主動(dòng)上報(bào)給群組服務(wù)來(lái)實(shí)現(xiàn),上報(bào)時(shí)機(jī)是客戶(hù)端接入和斷開(kāi)連接服務(wù)以及周期性的定時(shí)任務(wù)。

 

5.6 群組路由

設(shè)想這樣一個(gè)場(chǎng)景:需要給群所有成員推送一條消息怎么做?通過(guò)群編號(hào)去前面的路由 Redis 獲取對(duì)應(yīng)群的連接服務(wù)組,再通過(guò) HTTP 方式調(diào)用連接服務(wù),通過(guò)連接服務(wù)上的長(zhǎng)連接會(huì)話(huà)進(jìn)行真正的消息下發(fā)。

 

5.7 消息流轉(zhuǎn)

連接服務(wù)直接接收用戶(hù)的上行消息,考慮到消息量可能非常大,在連接服務(wù)里做業(yè)務(wù)顯然不合適,這里完全可以選擇 Kafka 來(lái)解耦,將所有的上行消息直接丟到 Kafka 就不管了,消息由群組服務(wù)來(lái)處理。

 

六、消息順序

?6.1 亂序現(xiàn)象

為什么要講消息順序,來(lái)看一個(gè)場(chǎng)景。假設(shè)群里有用戶(hù)A、用戶(hù)B、用戶(hù)C、用戶(hù)D,下面以 ABCD 代替,假設(shè)A發(fā)送了3條消息,順序分別是 msg1、msg2、msg3,但B、C、D看到的消息順序不一致,如圖4所示:

圖4

 

這時(shí)B、C、D肯定會(huì)覺(jué)得A在胡言亂語(yǔ)了,這樣的產(chǎn)品用戶(hù)必定是不喜歡的,因此必須要保證所有接收方看到的消息展示順序是一致的。

 

6.2 原因分析

所以先了解下消息發(fā)送的宏觀過(guò)程:

  1. 發(fā)送方發(fā)送消息。

  2. 服務(wù)端接收消息。

  3. 服務(wù)端返回 ACK 消息。

  4. 服務(wù)端推送新消息或客戶(hù)端拉取新消息。

 

在上面的過(guò)程中,都可能產(chǎn)生順序問(wèn)題,簡(jiǎn)要分析幾點(diǎn)原因:

 

  1. 時(shí)鐘不一致:多個(gè)客戶(hù)端、服務(wù)端集群、DB集群,時(shí)鐘不能保證完全一致,因此不能用本地時(shí)間來(lái)決定消息順序。

  2. 網(wǎng)絡(luò)傳輸:發(fā)送消息環(huán)節(jié),先發(fā)后至,到達(dá)服務(wù)器的順序可能是 msg2、msg1、msg3。

  3. 多線(xiàn)程:服務(wù)器考慮性能、吞吐量,往往會(huì)在多處環(huán)節(jié)采用線(xiàn)程池、異步去提升整體速度,因此也會(huì)產(chǎn)生順序問(wèn)題。

 

6.3 解決方案

6.3.1 單用戶(hù)保持有序

通過(guò)上面的分析可以知道,其實(shí)無(wú)法保證或是無(wú)法衡量不同用戶(hù)之間的消息順序,那么只需保證同一個(gè)用戶(hù)的消息是有序的,保證上下文語(yǔ)義,所以可以得出一個(gè)比較樸素的實(shí)現(xiàn)方式:以服務(wù)端數(shù)據(jù)庫(kù)的唯一自增ID為標(biāo)尺來(lái)衡量消息的時(shí)序,然后讓同一個(gè)用戶(hù)的消息處理串行化。那么就可以通過(guò)以下幾個(gè)技術(shù)手段配合來(lái)解決:

 

  1. 發(fā)送消息使用 Websocket 發(fā)送,并且多次發(fā)送保持同一個(gè)會(huì)話(huà),那么 tcp 協(xié)議就保證了應(yīng)用層收到的消息必定是有序的。

  2. 在應(yīng)用程序內(nèi)部處理時(shí),涉及相關(guān)多線(xiàn)程的模塊,根據(jù) uid 進(jìn)行 hash,匹配一個(gè)單線(xiàn)程的線(xiàn)程池,即同一個(gè) uid 的消息永遠(yuǎn)用同一個(gè)線(xiàn)程去處理,不同用戶(hù)之間仍是并行處理。

  3. 在跨應(yīng)用程序時(shí),一般有2種處理方式:一是用 rpc 同步調(diào)用;二是利用消息中間件的全局有序。

  4. 用戶(hù)端上做消息發(fā)送頻率限制,2次發(fā)送必須間隔1秒,能大大降低亂序的可能性了。

 

6.3.2 推拉結(jié)合

到這里基本解決了同一個(gè)用戶(hù)的消息可以按照他自己發(fā)出的順序入庫(kù)的問(wèn)題,即解決了消息發(fā)送流程里第一、二步。

 

第三、四步存在的問(wèn)題是這樣的:

A發(fā)送了 msg1、msg2、msg3,B發(fā)送了 msg4、msg5、msg6,最終服務(wù)端的入庫(kù)順序是msg1、msg2、msg4、msg3、msg5、msg6,那除了A和B其他人的消息順序需要按照入庫(kù)順序來(lái)展示,而這里的問(wèn)題是服務(wù)端考量推送吞吐量,在推送環(huán)節(jié)是并發(fā)的,即可能 msg4 比 msg1 先推送到用戶(hù)端上,如果按照推送順序追加來(lái)展示,那么就與預(yù)期不符了,每個(gè)人看到的消息順序都可能不一致,如果用戶(hù)端按照消息的id大小進(jìn)行比較插入的話(huà),用戶(hù)體驗(yàn)將會(huì)比較奇怪,突然會(huì)在2個(gè)消息中間出現(xiàn)一條消息。所以這里采用推拉結(jié)合方式來(lái)解決這個(gè)問(wèn)題,具體步驟如下:

 

  1. 用戶(hù)端發(fā)出消息,服務(wù)端將消息以群維度按照消息的入庫(kù)順序緩存在 Redis 有序 SET。

  2. 服務(wù)端推送給用戶(hù)端新消息提醒,內(nèi)容是該新消息的id。

  3. 用戶(hù)端拉取消息,攜帶2個(gè)消息id,startId 和 endId,startId:本地最新的完整消息id;endId:服務(wù)端推送得到的新消息id。

  4. 服務(wù)端返回2個(gè)消息id區(qū)間內(nèi)的消息列表。

 

圖5

 

圖6

 

舉例,圖5表示服務(wù)端的消息順序,圖6表示用戶(hù)端拉取消息時(shí)本地消息隊(duì)列和提醒隊(duì)列的變化邏輯。

 

  1. t1時(shí)刻用戶(hù)本地最新的完整消息是 msg1,即這條消息已經(jīng)完整展示給用戶(hù)。

  2. t2時(shí)刻收到服務(wù)端推送的 msg3 新消息提醒,放到提醒隊(duì)列,此時(shí)用戶(hù)看不到這條消息。

  3. t3時(shí)刻向服務(wù)端拉取消息詳情,請(qǐng)求參數(shù)為 startId:msg1,endId:msg3,服務(wù)端會(huì)按順序一起返回2個(gè)消息區(qū)間內(nèi)的所有消息的詳情即 msg2、msg4、msg3,將消息詳情同步寫(xiě)入到消息隊(duì)列,此時(shí)用戶(hù)可以看到刷新出3條消息。

  4. t4時(shí)刻用戶(hù)還會(huì)收到 msg2、msg4 的新消息提醒,用戶(hù)端校驗(yàn)消息隊(duì)列已經(jīng)存在 msg2、msg4 的詳情,忽略該新消息提醒。

 

通過(guò)推拉結(jié)合的方式可以保證所有用戶(hù)收到的消息展示順序一致。細(xì)心的讀者可能會(huì)有疑問(wèn),如果聊天信息流里有自己發(fā)送的消息,那么可能與其他的人看到的不一致,這是因?yàn)樽约旱南⒄故静灰蕾?lài)?yán)?,需要即時(shí)展示,給用戶(hù)立刻發(fā)送成功的體驗(yàn),同時(shí)其他人也可能也在發(fā)送,最終可能比他先入庫(kù),為了不出現(xiàn)信息流中間插入消息的用戶(hù)體驗(yàn),只能將他人的新消息追加在自己的消息后面。所以如果作為發(fā)送者,消息順序可能不一致,但是作為純接收者,大家的消息順序都是一樣的。

 

七、消息可靠性

在IM系統(tǒng)中,消息的可靠性同樣非常重要,它主要體現(xiàn)在:

  1. 消息不丟失:對(duì)發(fā)送人來(lái)說(shuō),必須保證消息能入庫(kù);對(duì)接收者來(lái)說(shuō),不管是在線(xiàn)還是離線(xiàn),都能保證收到。但是這里的不丟失,只是說(shuō)以最大努力去保證,并不是說(shuō)完全不丟失。

  2. 消息不重復(fù):這很容易理解,同一條消息不能重復(fù)出現(xiàn)。

 

7.1 消息不丟失設(shè)計(jì)

  1. 傳輸協(xié)議保障:首先 TCP 是可靠的協(xié)議,能較大程度上保證消息不丟失。

  2. 增加ACK機(jī)制:服務(wù)端在執(zhí)行完消息處理的所有流程后,給發(fā)送者發(fā)送 ACK;假如發(fā)送者在超時(shí)時(shí)間內(nèi)沒(méi)有收到 ACK 消息,則進(jìn)行一定次數(shù)的重試,重新發(fā)送;當(dāng)重發(fā)次數(shù)超過(guò)預(yù)設(shè)次數(shù),就不再重發(fā),消息發(fā)送失敗。

  3. 最終一致性:這是對(duì)接收者而言,如果某條新消息提醒因網(wǎng)絡(luò)等其他原因丟失,用戶(hù)沒(méi)有收到這條消息提醒,那么用戶(hù)就不會(huì)去拉消息詳情,在用戶(hù)視角就是沒(méi)有看到這條消息。但是當(dāng)后續(xù)的新消息提醒送達(dá)時(shí),可以依賴(lài)前面提到的拉取機(jī)制拿到一個(gè)區(qū)間內(nèi)的消息列表,這里就包含了丟失的消息,因此能達(dá)到最終一致性。

 

7.2 消息不重復(fù)設(shè)計(jì)

  1. 增加UUID:每條消息增加 UUID,由客戶(hù)端創(chuàng)建消息時(shí)生成,同一個(gè)用戶(hù)的消息 UUID 唯一。

  2. 服務(wù)端:用戶(hù) ID+UUID 在數(shù)據(jù)庫(kù)做聯(lián)合唯一索引,保證數(shù)據(jù)層面消息不重復(fù)。

  3. 用戶(hù)端:進(jìn)行兜底,構(gòu)造一個(gè)map來(lái)維護(hù)已接收消息的id,當(dāng)收到id重復(fù)的消息時(shí)直接丟棄。

 

八、未讀數(shù)統(tǒng)計(jì)

為了提醒用戶(hù)有新消息,需要給用戶(hù)展示新消息提醒標(biāo)識(shí),產(chǎn)品設(shè)計(jì)上一般有小紅點(diǎn)、具體的數(shù)值2種方式。具體數(shù)值比小紅點(diǎn)要復(fù)雜,這里分析下具體數(shù)值的處理方式,還需要分為初始打開(kāi)群和已打開(kāi)群2個(gè)場(chǎng)景。

 

已打開(kāi)群:可以完全依賴(lài)用戶(hù)端本地統(tǒng)計(jì),用戶(hù)端獲取到新消息后,就將未讀數(shù)累計(jì)加1,等點(diǎn)進(jìn)去查看后,清空未讀數(shù)統(tǒng)計(jì),這個(gè)比較簡(jiǎn)單。

 

初始打開(kāi)群:由于用戶(hù)端采用H5開(kāi)發(fā),用戶(hù)端沒(méi)有緩存,沒(méi)有能力緩存最近的已讀消息游標(biāo),因此這里完全需要服務(wù)端來(lái)統(tǒng)計(jì),在打開(kāi)群時(shí)下發(fā)最新的聊天信息流和未讀數(shù),下面具體講下這個(gè)場(chǎng)景下該怎么設(shè)計(jì)。

既然由服務(wù)端統(tǒng)計(jì)未讀數(shù),那么少不了要保存用戶(hù)在某個(gè)群里已經(jīng)讀到哪個(gè)消息,類(lèi)似一個(gè)游標(biāo),用戶(hù)已讀消息,游標(biāo)往前走。用戶(hù)已讀消息存儲(chǔ)表設(shè)計(jì)如圖7所示:

 

圖7

 

游標(biāo)offset采用定時(shí)更新策略,連接服務(wù)會(huì)記錄用戶(hù)最近一次拉取到的消息ID,定時(shí)異步上報(bào)批量用戶(hù)到群組服務(wù)更新 offset。

該表第一行表示用戶(hù)1在 id=89 的群里,最新的已讀消息是id=1022消息,那么可以通過(guò)下面的SQL來(lái)統(tǒng)計(jì)他在這個(gè)群里的未讀數(shù):select count(1) from msg_info where groupId = 89 and id > 1022。但是事情并沒(méi)這么簡(jiǎn)單,一個(gè)用戶(hù)有很多群,每個(gè)群都要展示未讀數(shù),因此要求未讀數(shù)統(tǒng)計(jì)的程序效率要高,不然用戶(hù)體驗(yàn)就很差,很明顯這個(gè) SQL 的耗時(shí)波動(dòng)很大,取決于 offset 的位置,如果很靠后,SQL 執(zhí)行時(shí)間會(huì)非常長(zhǎng)。筆者通過(guò)2個(gè)策略來(lái)優(yōu)化這個(gè)場(chǎng)景:

 

  1. 調(diào)整產(chǎn)品設(shè)計(jì):未讀數(shù)最大顯示調(diào)整為99+。算是產(chǎn)品上的一個(gè)讓步,有很多產(chǎn)品也采用這個(gè)方案,所以用戶(hù)也是有這個(gè)心智的,99+表示“有很多新消息”,至于具體多少,是幾百、幾千很多時(shí)候不是特別重要。所以問(wèn)題就變得簡(jiǎn)單多了,只要計(jì)算游標(biāo)是否在最新的100條消息以?xún)?nèi)還是以外。

  2. 合理利用數(shù)據(jù)結(jié)構(gòu):因?yàn)橛腥簝?nèi)有很多人,每個(gè)人登錄的時(shí)候都需要統(tǒng)計(jì),所以每次都去查 MySQL 是比較低效的,因此筆者的方案是在 Redis 里設(shè)計(jì)一個(gè)有界的ZSET結(jié)構(gòu)。

 

圖8

 

如上圖8所示,每個(gè)群都會(huì)構(gòu)建一個(gè)長(zhǎng)度為100,score 和 member 都是消息ID,可以通過(guò) zrevrank 命令得到某個(gè) offset 的排名值,該值可以換算成未讀數(shù)。比如:用戶(hù)1在群89的未讀消息數(shù),'zrevrank 89 1022' = 2,也就是有2條未讀數(shù)。用戶(hù)2在群89的未讀數(shù),'zrevrank 89 890' = nil,那么未讀數(shù)就是99+。同時(shí)消息新增、刪除都需要同步維護(hù)該數(shù)據(jù)結(jié)構(gòu),失效或不存在時(shí)從 MySQL 初始化。

 

九、超大群策略

前面提到,設(shè)計(jì)目標(biāo)是在同一個(gè)群里能支撐百萬(wàn)人,從架構(gòu)上可以看到,連接服務(wù)處于流量最前端,所以它的承載力直接決定了同時(shí)在線(xiàn)用戶(hù)的上限。

影響它的因素有:

  1. 服務(wù)器自身配置:內(nèi)存、CPU、網(wǎng)卡、Linux 支持的最大文件打開(kāi)數(shù);

  2. 應(yīng)用自身配置:應(yīng)用本身啟動(dòng)需要的內(nèi)存,如 Netty 依賴(lài)的堆外內(nèi)存,大量的本地緩存;

  3. 性能要求:當(dāng)連接數(shù)不斷變大時(shí),消息分發(fā)的整體耗時(shí)肯定在不斷增加,因此要關(guān)注最慢的分發(fā)耗時(shí)要滿(mǎn)足即時(shí)性要求;結(jié)合以上情況,可以測(cè)試出固定配置服務(wù)器單點(diǎn)能支持的最大用戶(hù)連接數(shù),假如單機(jī)能支持20000個(gè)用戶(hù)連接,那么百萬(wàn)在線(xiàn)連接,在連接服務(wù)層用50個(gè)服務(wù)的集群就能解決。

 

9.1 消息風(fēng)暴

當(dāng)同時(shí)在線(xiàn)用戶(hù)數(shù)非常多,例如百萬(wàn)時(shí),會(huì)面臨如下幾個(gè)問(wèn)題:

 

  1. 消息發(fā)送風(fēng)暴:極端情況下,用戶(hù)同時(shí)發(fā)送消息,假設(shè)服務(wù)端承載住了這些流量,那么瓶頸其實(shí)在用戶(hù)端,第一用戶(hù)端會(huì)經(jīng)歷網(wǎng)絡(luò)風(fēng)暴,網(wǎng)卡帶寬能否支撐是一個(gè)大問(wèn)題;第二假設(shè)網(wǎng)卡能通過(guò)這些流量,用戶(hù)端上百萬(wàn)條消息該如何展示,要是瞬間刷出這些消息,用戶(hù)端 CPU能否撐住又是個(gè)問(wèn)題,即使能抗住用戶(hù)體驗(yàn)也很糟糕,根本就看不清消息,一直在飛速刷屏。因此服務(wù)端可以在發(fā)送消息風(fēng)暴時(shí)做好限流、丟棄策略,給到用戶(hù)友好的提示。

  2. 消息提醒風(fēng)暴:一條新消息的產(chǎn)生,就要推送提醒消息百萬(wàn)次,對(duì)服務(wù)器來(lái)說(shuō),要考量整體推送完成的時(shí)效性,如果時(shí)效性差,對(duì)有些用戶(hù)來(lái)說(shuō),就是消息需要較長(zhǎng)時(shí)間才刷出來(lái),出現(xiàn)明顯的延遲。新消息持久化后,群組服務(wù) HTTP 回調(diào)一組連接服務(wù),單群百萬(wàn)在線(xiàn)用戶(hù),需要50臺(tái)連接服務(wù)集群,那么回調(diào)50次,為了保證時(shí)效性,因此這里要并發(fā)回調(diào),并設(shè)置合理的線(xiàn)程池,然后連接服務(wù)收到回調(diào)后也需要并發(fā)完成對(duì)群用戶(hù)的新消息提醒推送。

  3. 消息拉取風(fēng)暴:連接服務(wù)收到拉取消息事件,需要去群組服務(wù)獲取消息詳情,QPS 就非常高了,理論上集群達(dá)到 100wQPS,20臺(tái)群組服務(wù),那么每臺(tái)群組服務(wù)就是 5wQPS。這里的策略是在鏈路前端連接服務(wù)上進(jìn)行流量過(guò)濾,因?yàn)橛脩?hù)都是請(qǐng)求同一個(gè)群的同一條消息或附近的消息,那么就可以在連接服務(wù)里設(shè)計(jì)群消息的本地緩存,所有用戶(hù)都只從本地緩存里讀,如果本地緩存里沒(méi)有,就放一個(gè)線(xiàn)程去群組服務(wù)請(qǐng)求加載緩存,其他線(xiàn)程同步等待,這樣就大大降低了打到群組服務(wù)的 QPS。

 

9.2 消息壓縮

如果某一個(gè)時(shí)刻,推送消息的數(shù)量比較大,且群同時(shí)在線(xiàn)人數(shù)比較多的時(shí)候,連接服務(wù)層的機(jī)房出口帶寬就會(huì)成為消息推送的瓶頸。

做個(gè)計(jì)算,百萬(wàn)人在線(xiàn),需要5臺(tái)連接服務(wù),一條消息1KB,一般情況下,5臺(tái)連接服務(wù)集群都是部署在同一個(gè)機(jī)房,那么這個(gè)機(jī)房的帶寬就是1000000*1KB=1GB,如果多幾個(gè)超大群,那么對(duì)機(jī)房的帶寬要求就更高,所以如何有效的控制每一個(gè)消息的大小、壓縮每一個(gè)消息的大小,是需要思考的問(wèn)題。

經(jīng)過(guò)測(cè)試,使用 protobuf 數(shù)據(jù)交換格式,平均每一個(gè)消息可以節(jié)省43%的字節(jié)大小,可以大大節(jié)省機(jī)房出口帶寬。

 

9.3 塊消息

超大群里,消息推送的頻率很高,每一條消息推送都需要進(jìn)行一次IO系統(tǒng)調(diào)用,顯然會(huì)影響服務(wù)器性能,可以采用將多個(gè)消息進(jìn)行合并推送。

主要思路:以群為維度,累計(jì)一段時(shí)間內(nèi)的消息,如果達(dá)到閾值,就立刻合并推送,否則就以勻速的時(shí)間間隔將在這個(gè)時(shí)間段內(nèi)新增的消息進(jìn)行推送。

時(shí)間間隔是1秒,閾值是10,如果500毫秒內(nèi)新增了10條消息,就合并推送這10條消息,時(shí)間周期重置;如果1秒內(nèi)只新增了8條消息,那么1秒后合并推送這8條消息。這樣做的好處如下:

 

  1. 提升服務(wù)器性能:減少I(mǎi)O系統(tǒng)調(diào)用,減少用戶(hù)態(tài)與內(nèi)核態(tài)之前的切換;

  2. 減少傳輸量:合并消息后,可以減少傳輸多余的消息頭,進(jìn)一步壓縮消息大?。?/p>

  3. 提升用戶(hù)體驗(yàn):一定程度上能減小消息風(fēng)暴,消息渲染的節(jié)奏比較均勻,帶給用戶(hù)更好的體驗(yàn);

 

十、總結(jié)

在本文中,筆者介紹了從零開(kāi)始搭建一個(gè)生產(chǎn)級(jí)百萬(wàn)級(jí)群聊的一些關(guān)鍵要點(diǎn)和實(shí)踐經(jīng)驗(yàn),包括通信方案選型、消息存儲(chǔ)、消息順序、消息可靠性、高并發(fā)等方面,但仍有許多技術(shù)設(shè)計(jì)未涉及,比如冷熱群、高低消息通道會(huì)放在未來(lái)的規(guī)劃里。IM開(kāi)發(fā)業(yè)界沒(méi)有統(tǒng)一的標(biāo)準(zhǔn),不同的產(chǎn)品有適合自己的技術(shù)方案,希望本文能夠帶給讀者更好地理解和應(yīng)用這些技術(shù)實(shí)踐,為構(gòu)建高性能、高可靠性的群聊系統(tǒng)提供一定的參考。

轉(zhuǎn)自https://www.cnblogs.com/vivotech/p/18740478


該文章在 2025/3/5 11:18:40 編輯過(guò)
關(guān)鍵字查詢(xún)
相關(guān)文章
正在查詢(xún)...
點(diǎn)晴ERP是一款針對(duì)中小制造業(yè)的專(zhuān)業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國(guó)內(nèi)大量中小企業(yè)的青睞。
點(diǎn)晴PMS碼頭管理系統(tǒng)主要針對(duì)港口碼頭集裝箱與散貨日常運(yùn)作、調(diào)度、堆場(chǎng)、車(chē)隊(duì)、財(cái)務(wù)費(fèi)用、相關(guān)報(bào)表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點(diǎn),圍繞調(diào)度、堆場(chǎng)作業(yè)而開(kāi)發(fā)的。集技術(shù)的先進(jìn)性、管理的有效性于一體,是物流碼頭及其他港口類(lèi)企業(yè)的高效ERP管理信息系統(tǒng)。
點(diǎn)晴WMS倉(cāng)儲(chǔ)管理系統(tǒng)提供了貨物產(chǎn)品管理,銷(xiāo)售管理,采購(gòu)管理,倉(cāng)儲(chǔ)管理,倉(cāng)庫(kù)管理,保質(zhì)期管理,貨位管理,庫(kù)位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號(hào)管理軟件。
點(diǎn)晴免費(fèi)OA是一款軟件和通用服務(wù)都免費(fèi),不限功能、不限時(shí)間、不限用戶(hù)的免費(fèi)OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved

主站蜘蛛池模板: 国产美女爽到喷出水来视频 | 高清综合国产欧美 | 精品国产污网站在线观看15 | 精品亚洲成a人在线看片 | 午夜激成人免费视频在线观看 | 精品欧美一区二区三区在线观看 | 免费午夜美女在线视频播放 | 男女xxⅹ爽免费视频 | 专区一乛方| 国产欧美精品一区 | 日韩精品一区二区最新 | 亚洲国产高清在线不卡 | 国内精品免费 | 国产精品不卡一区二区 | 日本乱伦自拍欧美 | 午夜欧美国产一区 | 国产亚洲精品精品精品 | 狠狠五月天 | 91进入蜜 | 国产爽片大全免费在线观看 | 日本一区二区在线观看精品 | 国产精品日本一区二区在线播 | 亚洲欧美乱综合图片区小说区 | 国户一区二区免费视频 | 日本欧美视频在线 | 在线免费观看视频a | 独家高清资源库 | 欧美高清一级 | 日韩一本之道一区中文字幕 | 国产高颜值大学生情侣酒店 | a级在线观看日韩 | 在线视频 | 亚洲综合精品第一页 | 国产福利高颜 | 黑人免费 | 亚欧洲乱码视频在线专区网站 | 精品一区二区三区高清 | 精品成人一区二区三区免费视频 | 日本免费一区二区三区在线视频 | 亚洲永久免费精品 | 亚洲一区二区三区四区在线观看 |