国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

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

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

一、引言

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

二、背景介紹

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

  1. 單群成員需要支撐百萬人,同時(shí)在線百萬級(jí)。
  2. 功能、體驗(yàn)要接近純客戶端實(shí)現(xiàn)方案。
  3. 用戶端完全用H5承載。

三、通信技術(shù)

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

四、消息存儲(chǔ)

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

圖片

圖1

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

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

舉個(gè)例子:

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

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

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

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

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

5.1 整體架構(gòu)

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

圖片

圖2

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

圖片

圖3

  1. 用戶登錄,通過負(fù)載均衡,與連接服務(wù)建立 Websocket 長(zhǎng)連接。
  2. 連接服務(wù)管理會(huì)話,管理群與用戶的映射關(guān)系,在本地內(nèi)存里使用哈希表存儲(chǔ),key為groupId,value為L(zhǎng)ist<SocketIOClient>,同一個(gè)群的用戶可能會(huì)在不同的集群服務(wù)器上。
  3. 連接服務(wù)群組服務(wù)上報(bào)群組路由,上報(bào)它的內(nèi)網(wǎng)IP和它所管理的 groupIdList 的關(guān)系,這里需要2種同步策略并行保證群組路由信息的準(zhǔn)確性:a.在用戶建立、斷開長(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)的心跳判斷,超過3個(gè)心跳周期不上報(bào),則認(rèn)為已斷線。
  5. 用戶在群里發(fā)布一條消息,消息通過 Websokcet 送達(dá)連接服務(wù),然后經(jīng)過連接服務(wù)——>消息隊(duì)列——>群組服務(wù),消息在群組服務(wù)里經(jīng)過頻控、安全檢查、格式轉(zhuǎn)換等一系列流程后入庫,持久化。
  6. 群組服務(wù)通過群組路由管理獲取這條消息所屬群的路由信息,即一組連接服務(wù)的IP地址,然后通過 HTTP 回調(diào)對(duì)應(yīng)的連接服務(wù),通知它們有新消息產(chǎn)生,這里只簡(jiǎn)單傳遞消息ID。
  7. 連接服務(wù)收到 HTTP 請(qǐng)求后,根據(jù)會(huì)話管理查詢?cè)撊核杏脩簦o用戶發(fā)送新消息提醒。
  8. 用戶收到新消息提醒,通過 Websocket 來連接服務(wù)拉取該新消息具體詳情,然后根據(jù)消息協(xié)議展示在信息流里。

5.2 路由策略

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

  1. 盡量保證各個(gè)節(jié)點(diǎn)的連接均衡;
  2. 增刪節(jié)點(diǎn)是否要做 Rebalance。

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

  1. 輪詢:挨個(gè)將各個(gè)節(jié)點(diǎn)分配給客戶端,但會(huì)出現(xiàn)新增節(jié)點(diǎn)分配不均勻的情況;
  2. 取模:類似于 HashMap,但也會(huì)出現(xiàn)輪詢的問題。當(dāng)然也可以像 HashMap 那樣做一次 Rebalance,讓所有的客戶端重新連接。不過這樣會(huì)導(dǎo)致所有的連接出現(xiàn)中斷重連,代價(jià)有點(diǎn)大。由于 Hash 取模方式的問題帶來了一致性 Hash 算法,但依然會(huì)有一部分的客戶端需要 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)重;
    筆者是采用輪詢 + 權(quán)重模式,盡量保證負(fù)載均衡。

5.3 重連機(jī)制

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

5.4 線程策略

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

  1. 充分利用多核的并行處理能力:IO線程和業(yè)務(wù)線程隔離,雙方都可以并行處理網(wǎng)絡(luò)IO和業(yè)務(wù)邏輯,充分利用計(jì)算機(jī)多核并行計(jì)算能力,提升性能;
  2. 故障隔離:業(yè)務(wù)線程處理多種業(yè)務(wù)消息,有IO密集型,也有 CPU 密集型,有些是純內(nèi)存計(jì)算,不同的業(yè)務(wù)處理時(shí)延和故障率是不同的。如果把業(yè)務(wù)線程和IO線程合并,就會(huì)有如下問題:某類業(yè)務(wù)處理較慢,阻塞IO線程,導(dǎo)致其他處理較快的業(yè)務(wù)消息響應(yīng)不及時(shí);
  3. 可維護(hù)性:IO線程和業(yè)務(wù)線程隔離之后,職責(zé)單一,有利于維護(hù)和定位問題。

5.5 有狀態(tài)鏈接

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

5.6 群組路由

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

5.7 消息流轉(zhuǎn)

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

六、消息順序

6.1 亂序現(xiàn)象

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

圖片

圖4

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

6.2 原因分析

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

  1. 發(fā)送方發(fā)送消息。
  2. 服務(wù)端接收消息。
  3. 服務(wù)端返回 ACK 消息。
  4. 服務(wù)端推送新消息或客戶端拉取新消息。

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

  1. 時(shí)鐘不一致:多個(gè)客戶端、服務(wù)端集群、DB集群,時(shí)鐘不能保證完全一致,因此不能用本地時(shí)間來決定消息順序。
  2. 網(wǎng)絡(luò)傳輸:發(fā)送消息環(huán)節(jié),先發(fā)后至,到達(dá)服務(wù)器的順序可能是 msg2、msg1、msg3。
  3. 多線程:服務(wù)器考慮性能、吞吐量,往往會(huì)在多處環(huán)節(jié)采用線程池、異步去提升整體速度,因此也會(huì)產(chǎn)生順序問題。

6.3 解決方案

6.3.1 單用戶保持有序

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

  1. 發(fā)送消息使用 Websocket 發(fā)送,并且多次發(fā)送保持同一個(gè)會(huì)話,那么 tcp 協(xié)議就保證了應(yīng)用層收到的消息必定是有序的。
  2. 在應(yīng)用程序內(nèi)部處理時(shí),涉及相關(guān)多線程的模塊,根據(jù) uid 進(jìn)行 hash,匹配一個(gè)單線程的線程池,即同一個(gè) uid 的消息永遠(yuǎn)用同一個(gè)線程去處理,不同用戶之間仍是并行處理。
  3. 在跨應(yīng)用程序時(shí),一般有2種處理方式:一是用 rpc 同步調(diào)用;二是利用消息中間件的全局有序。
  4. 用戶端上做消息發(fā)送頻率限制,2次發(fā)送必須間隔1秒,能大大降低亂序的可能性了。

6.3.2 推拉結(jié)合

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

第三、四步存在的問題是這樣的:

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

  1. 用戶端發(fā)出消息,服務(wù)端將消息以群維度按照消息的入庫順序緩存在 Redis 有序 SET。
  2. 服務(wù)端推送給用戶端新消息提醒,內(nèi)容是該新消息的id。
  3. 用戶端拉取消息,攜帶2個(gè)消息id,startId 和 endId,startId:本地最新的完整消息id;endId:服務(wù)端推送得到的新消息id。
  4. 服務(wù)端返回2個(gè)消息id區(qū)間內(nèi)的消息列表。

圖片

圖5


圖片

圖6

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

  1. t1時(shí)刻用戶本地最新的完整消息是 msg1,即這條消息已經(jīng)完整展示給用戶。
  2. t2時(shí)刻收到服務(wù)端推送的 msg3 新消息提醒,放到提醒隊(duì)列,此時(shí)用戶看不到這條消息。
  3. t3時(shí)刻向服務(wù)端拉取消息詳情,請(qǐng)求參數(shù)為 startId:msg1,endId:msg3,服務(wù)端會(huì)按順序一起返回2個(gè)消息區(qū)間內(nèi)的所有消息的詳情即 msg2、msg4、msg3,將消息詳情同步寫入到消息隊(duì)列,此時(shí)用戶可以看到刷新出3條消息。
  4. t4時(shí)刻用戶還會(huì)收到 msg2、msg4 的新消息提醒,用戶端校驗(yàn)消息隊(duì)列已經(jīng)存在 msg2、msg4 的詳情,忽略該新消息提醒。

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

七、消息可靠性

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

  1. 消息不丟失:對(duì)發(fā)送人來說,必須保證消息能入庫;對(duì)接收者來說,不管是在線還是離線,都能保證收到。但是這里的不丟失,只是說以最大努力去保證,并不是說完全不丟失。
  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)沒有收到 ACK 消息,則進(jìn)行一定次數(shù)的重試,重新發(fā)送;當(dāng)重發(fā)次數(shù)超過預(yù)設(shè)次數(shù),就不再重發(fā),消息發(fā)送失敗。
  3. 最終一致性:這是對(duì)接收者而言,如果某條新消息提醒因網(wǎng)絡(luò)等其他原因丟失,用戶沒有收到這條消息提醒,那么用戶就不會(huì)去拉消息詳情,在用戶視角就是沒有看到這條消息。但是當(dāng)后續(xù)的新消息提醒送達(dá)時(shí),可以依賴前面提到的拉取機(jī)制拿到一個(gè)區(qū)間內(nèi)的消息列表,這里就包含了丟失的消息,因此能達(dá)到最終一致性。

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

  1. 增加UUID:每條消息增加 UUID,由客戶端創(chuàng)建消息時(shí)生成,同一個(gè)用戶的消息 UUID 唯一。
  2. 服務(wù)端:用戶 ID+UUID 在數(shù)據(jù)庫做聯(lián)合唯一索引,保證數(shù)據(jù)層面消息不重復(fù)。
  3. 用戶端:進(jìn)行兜底,構(gòu)造一個(gè)map來維護(hù)已接收消息的id,當(dāng)收到id重復(fù)的消息時(shí)直接丟棄。

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

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

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

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

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

圖片

圖7

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

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

  1. 調(diào)整產(chǎn)品設(shè)計(jì):未讀數(shù)最大顯示調(diào)整為99+。算是產(chǎn)品上的一個(gè)讓步,有很多產(chǎn)品也采用這個(gè)方案,所以用戶也是有這個(gè)心智的,99+表示“有很多新消息”,至于具體多少,是幾百、幾千很多時(shí)候不是特別重要。所以問題就變得簡(jiǎn)單多了,只要計(jì)算游標(biāo)是否在最新的100條消息以內(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,可以通過 zrevrank 命令得到某個(gè) offset 的排名值,該值可以換算成未讀數(shù)。比如:用戶1在群89的未讀消息數(shù),'zrevrank 89 1022' = 2,也就是有2條未讀數(shù)。用戶2在群89的未讀數(shù),'zrevrank 89 890' = nil,那么未讀數(shù)就是99+。同時(shí)消息新增、刪除都需要同步維護(hù)該數(shù)據(jù)結(jié)構(gòu),失效或不存在時(shí)從 MySQL 初始化。

九、超大群策略

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

影響它的因素有:

  1. 服務(wù)器自身配置:內(nèi)存、CPU、網(wǎng)卡、Linux 支持的最大文件打開數(shù);
  2. 應(yīng)用自身配置:應(yīng)用本身啟動(dòng)需要的內(nèi)存,如 Netty 依賴的堆外內(nèi)存,大量的本地緩存;
  3. 性能要求:當(dāng)連接數(shù)不斷變大時(shí),消息分發(fā)的整體耗時(shí)肯定在不斷增加,因此要關(guān)注最慢的分發(fā)耗時(shí)要滿足即時(shí)性要求;
    結(jié)合以上情況,可以測(cè)試出固定配置服務(wù)器單點(diǎn)能支持的最大用戶連接數(shù),假如單機(jī)能支持20000個(gè)用戶連接,那么百萬在線連接,在連接服務(wù)層用50個(gè)服務(wù)的集群就能解決。

9.1 消息風(fēng)暴

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

  1. 消息發(fā)送風(fēng)暴:極端情況下,用戶同時(shí)發(fā)送消息,假設(shè)服務(wù)端承載住了這些流量,那么瓶頸其實(shí)在用戶端,第一用戶端會(huì)經(jīng)歷網(wǎng)絡(luò)風(fēng)暴,網(wǎng)卡帶寬能否支撐是一個(gè)大問題;第二假設(shè)網(wǎng)卡能通過這些流量,用戶端上百萬條消息該如何展示,要是瞬間刷出這些消息,用戶端 CPU能否撐住又是個(gè)問題,即使能抗住用戶體驗(yàn)也很糟糕,根本就看不清消息,一直在飛速刷屏。因此服務(wù)端可以在發(fā)送消息風(fēng)暴時(shí)做好限流、丟棄策略,給到用戶友好的提示。
  2. 消息提醒風(fēng)暴:一條新消息的產(chǎn)生,就要推送提醒消息百萬次,對(duì)服務(wù)器來說,要考量整體推送完成的時(shí)效性,如果時(shí)效性差,對(duì)有些用戶來說,就是消息需要較長(zhǎng)時(shí)間才刷出來,出現(xiàn)明顯的延遲。新消息持久化后,群組服務(wù) HTTP 回調(diào)一組連接服務(wù),單群百萬在線用戶,需要50臺(tái)連接服務(wù)集群,那么回調(diào)50次,為了保證時(shí)效性,因此這里要并發(fā)回調(diào),并設(shè)置合理的線程池,然后連接服務(wù)收到回調(diào)后也需要并發(fā)完成對(duì)群用戶的新消息提醒推送。
  3. 消息拉取風(fēng)暴:連接服務(wù)收到拉取消息事件,需要去群組服務(wù)獲取消息詳情,QPS 就非常高了,理論上集群達(dá)到 100wQPS,20臺(tái)群組服務(wù),那么每臺(tái)群組服務(wù)就是 5wQPS。這里的策略是在鏈路前端連接服務(wù)上進(jìn)行流量過濾,因?yàn)橛脩舳际钦?qǐng)求同一個(gè)群的同一條消息或附近的消息,那么就可以在連接服務(wù)里設(shè)計(jì)群消息的本地緩存,所有用戶都只從本地緩存里讀,如果本地緩存里沒有,就放一個(gè)線程去群組服務(wù)請(qǐng)求加載緩存,其他線程同步等待,這樣就大大降低了打到群組服務(wù)的 QPS。

9.2 消息壓縮

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

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

經(jīng)過測(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ù)器性能:減少IO系統(tǒng)調(diào)用,減少用戶態(tài)與內(nèi)核態(tài)之前的切換;
  2. 減少傳輸量:合并消息后,可以減少傳輸多余的消息頭,進(jìn)一步壓縮消息大小;
  3. 提升用戶體驗(yàn):一定程度上能減小消息風(fēng)暴,消息渲染的節(jié)奏比較均勻,帶給用戶更好的體驗(yàn);

十、總結(jié)

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

責(zé)任編輯:龐桂玉 來源: vivo互聯(lián)網(wǎng)技術(shù)
相關(guān)推薦

2024-01-03 16:29:01

Agent性能優(yōu)化

2014-02-10 16:27:09

百萬級(jí)IOPSOceanStor 1

2020-03-18 07:11:24

實(shí)時(shí)同步搜索

2022-05-24 09:30:00

消息吞吐車聯(lián)網(wǎng)平臺(tái)車聯(lián)網(wǎng)

2012-02-01 16:32:32

2017-01-09 16:06:19

2018-11-22 09:17:21

消息推送系統(tǒng)

2025-05-30 03:40:00

2019-02-12 09:34:00

微博短視頻架構(gòu)

2024-10-15 16:31:30

2022-09-25 21:45:54

日志平臺(tái)

2023-10-25 11:20:09

快手電商混合云容器云

2025-03-03 10:30:00

JavaExcelSpringBoot

2019-11-14 15:44:32

系統(tǒng)緩存架構(gòu)

2010-07-23 08:48:21

PHP架構(gòu)

2025-08-05 09:20:20

2019-01-21 14:20:26

Java開發(fā)代碼

2016-12-27 08:49:55

API設(shè)計(jì)策略

2022-11-24 13:25:18

EMQX 5.0架構(gòu)

2010-12-06 15:05:08

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

91在线精品| 亚洲成熟丰满熟妇高潮xxxxx| 在线不卡日本v二区707| 国产aⅴ精品一区二区三区色成熟| 欧美日韩久久久| 国产偷人视频免费| 激情婷婷欧美| 欧美一级电影久久| 快射视频在线观看| 一区二区三区四区在线播放| 久久久日本电影| 日韩在线观看不卡| 日韩欧美国产综合在线一区二区三区 | 久久久在线免费观看| 国产精品69xx| 日韩视频一区二区三区| 波多野结衣中文在线| 亚洲国产精品高清久久久| 黄色小网站在线观看| 日韩精品一区在线观看| 黄色网页在线看| 亚洲国产欧美自拍| 中国色在线日|韩| 日韩中文字幕视频在线| 欧美成年网站| 精品国产不卡一区二区三区| 成人免费网站视频www| 久久电影网站中文字幕 | 国产精品乱码一区二区三区软件| 啊啊啊一区二区| 91在线一区二区| 国产特级淫片免费看| 日韩人在线观看| av在线影院| 久久久人成影片一区二区三区| 欧美在线高清| 国产中文字幕亚洲| 精品一区二区av| 99在线免费视频观看| 国产片一区二区| 91在线看片| 亚洲欧美一区二区三区四区| 成人影院中文字幕| 久久精品成人一区二区三区| 欧美黑白配在线| 久久婷婷国产综合尤物精品| 成人av一区二区三区| 国产小视频在线| 亚洲欧美国产日韩中文字幕| 欧美精品成人一区二区在线观看| 欧美伊人影院| 欧美黑人又粗又大又爽免费| 51精品久久久久久久蜜臀| 日韩不卡在线| 国产精品视频公开费视频| 日本不卡一区二区| 超碰97在线免费| 曰本色欧美视频在线| 亚洲高清资源| 亚洲一区日韩精品| 亚洲欧美激情在线视频| 美女久久一区| 国产天堂在线| 成人淫片在线看| 亚洲男同性视频| 99久久婷婷国产综合精品青牛牛| 国产高清在线精品一区二区三区| 国产精品成人一区二区三区夜夜夜| av片在线观看永久免费| 国产精品中文久久久久久久| 久久久精品中文字幕麻豆发布| 一本一道波多野毛片中文在线| 欧美亚洲在线观看| 国产人成一区二区三区影院| 欧美va在线| 黄网站色视频免费观看| 欧美成人女星排名| 9191在线| 性娇小13――14欧美| 国产伦理一区二区三区| 中文一区在线播放| 国产一二三在线| 日本午夜精品电影| 91福利视频久久久久| 亚洲欧美亚洲| 天堂视频中文在线| 国产三级精品在线不卡| 久久奇米777| 亚洲三级在线| 亚洲另类第一页| 精品美女在线观看| 日韩高清在线电影| y4480在线8影院| 国产免费观看久久黄| 国产高清不卡二三区| 日本美女久久| 日韩精品免费专区| 真实国产乱子伦对白视频| 欧美一区二区三区成人| 国产精品99一区二区三| 老司机免费在线视频| 一级黄色片播放| 日韩精品一区国产麻豆| 国产在线视频不卡二| 亚洲精品aa| 91成人综合网| 日韩中文综合网| 中文久久乱码一区二区| 欧美黄色一区二区| 3344国产永久在线观看视频| 国产高潮av| 日韩亚洲视频| 揄拍成人国产精品视频| 亚洲亚洲精品在线观看| aⅴ色国产欧美| 桃花岛tv亚洲品质| 在线中文视频| 国产精品视频二| 999国内精品视频在线| 久久久久久久国产| 制服丝袜国产精品| 日韩欧美999| 国产精品久久久久久妇女6080| 精品电影一区| 俺要去色综合狠狠| 搞黄网站在线看| 精品视频在线观看一区| 国产精品视频福利| 国产精品美女www爽爽爽视频| 欧美一区三区四区| 日本二三区不卡| 亚洲欧洲另类国产综合| 91色九色蝌蚪| 不卡的av电影| 成人免费视频app| 成人晚上爱看视频| 水野朝阳av一区二区三区| 精品产国自在拍| 国内精品麻豆美女在线播放视频 | 欧美精彩视频一区二区三区| 日日夜夜免费精品| 9999精品免费视频| 波多野结衣欧美| 在线观看免费网站黄| 日韩网站在线免费观看| 国产乱码精品一区二区三区不卡| 这里只有视频精品| 色婷婷av一区| 欧美日韩高清不卡| 欧美日韩成人综合天天影院| 在线中文字幕一区| 欧美日韩亚洲综合一区| 亚洲欧美激情一区二区| 欧美日韩久久精品| 最新黄色片网站| 动漫3d精品一区二区三区| 精品国产91亚洲一区二区三区婷婷| 国产亚洲一区在线| 国产黄色精品| 三上悠亚激情av一区二区三区| 91青青草免费在线看| 国产精品久久久久久超碰| 欧美成熟毛茸茸复古| 宅男av一区二区三区| 久久久久国产精品视频| 中文字幕无码精品亚洲资源网久久| 九色一区二区| 日本免费观看网站| 日本女优天堂99伊人| 丰满诱人av在线播放| 韩日毛片在线观看| 自拍偷拍亚洲视频| 黄色成人免费网| 国产香蕉久久| 精品久久美女| www.亚洲色图.com| 日本大香伊一区二区三区| 色综合久久久久网| 欧美成人高清视频| 亚洲伊人婷婷| 巨骚激情综合| 在线观看欧美| 成人精品鲁一区一区二区| 国产精品美女久久久久久| 欧美日韩久久一区二区| 国产精品久久久久久久久久东京 | 豆花视频一区二区| 国内精品久久久久久久影视麻豆 | 欧美日本一区二区| 国产精品国产自产拍高清av水多 | 石原莉奈一区二区三区在线观看| 午夜影院在线观看欧美| 久久精品视频亚洲| 成人免费在线网| 无遮挡动作视频在线观看免费入口| 一个人看的www视频在线免费观看 一个人www视频在线免费观看 | 欧美一区二区三区色| 91久久精品在线| 在线观看免费毛片| 国产一区二区三区四区五区3d |