本文由will分享,個人博客zhangyaoo.github.io,原題“基于Netty的IM系統(tǒng)設(shè)計與實(shí)現(xiàn)”,有修訂和重新排版。
1、引言
本文將要分享的是如何從零實(shí)現(xiàn)一套基于Netty框架的分布式高可用IM系統(tǒng),它將支持長連接網(wǎng)關(guān)管理、單聊、群聊、聊天記錄查詢、離線消息存儲、消息推送、心跳、分布式唯一ID、紅包、消息同步等功能,并且還支持集群部署。
本文中針對這套架構(gòu)和系統(tǒng)設(shè)計,同時還會提供完整的源碼,比較適合有一定Java開發(fā)能力和Netty知識的IM初學(xué)者。
* 友情提示:如果你對IM即時通訊的基礎(chǔ)技術(shù)理論了解的太少,建議可以先讀:《新手入門一篇就夠:從零開發(fā)移動端IM》。
2、配套源碼
本文配套源碼的開源托管地址是:
如果你訪問Github太慢,可直接從以下附件打包下載:
fastim-master(52im.net).zip (1.12 MB , 下載次數(shù): 5 , 售價: 1 金幣)
完整源碼的目錄結(jié)構(gòu),如下圖:
3、知識準(zhǔn)備
關(guān)于 Netty 是什么,這里簡單介紹下:
Netty 是一個 Java 開源框架。Netty 提供異步的、事件驅(qū)動的網(wǎng)絡(luò)應(yīng)用程序框架和工具,用以快速開發(fā)高性能、高可靠性的網(wǎng)絡(luò)服務(wù)器和客戶端程序。
也就是說,Netty 是一個基于 NIO 的客戶、服務(wù)器端編程框架,使用Netty 可以確保你快速和簡單的開發(fā)出一個網(wǎng)絡(luò)應(yīng)用,例如實(shí)現(xiàn)了某種協(xié)議的客戶,服務(wù)端應(yīng)用。
Netty 相當(dāng)簡化和流線化了網(wǎng)絡(luò)應(yīng)用的編程開發(fā)過程,例如,TCP 和 UDP 的 Socket 服務(wù)開發(fā)。
有關(guān)Netty的入門文章:
1)新手入門:目前為止最透徹的的Netty高性能原理和框架架構(gòu)解析
2)寫給初學(xué)者:Java高性能NIO框架Netty的學(xué)習(xí)方法和進(jìn)階策略
3)史上最通俗Netty框架入門長文:基本介紹、環(huán)境搭建、動手實(shí)戰(zhàn)
如果你連Java NIO都不知道,下面的文章建議優(yōu)先讀:
Netty源碼和API 在線查閱地址:
4、整體架構(gòu)設(shè)計概覽
本次的IM系統(tǒng)設(shè)計主要基于可擴(kuò)展性高可用原則,把網(wǎng)關(guān)層、邏輯層、數(shù)據(jù)層進(jìn)行了分離,并且還要支持分布式部署。
以下是整體系統(tǒng)的架構(gòu)設(shè)計概覽圖:
下面將針對整體架構(gòu)來逐一分享設(shè)計的主要思路等。
5、整體架構(gòu)設(shè)計之客戶端設(shè)計
5.1客戶端設(shè)計
客戶端的設(shè)計主要從以下幾點(diǎn)出發(fā):
- 1)client每個設(shè)備會在本地存每一個會話,保留有最新一條消息的順序 ID;
- 2)為了避免client宕機(jī),也就是退出應(yīng)用,保存在內(nèi)存的消息ID丟失,會存到本地的文件中;
- 3)client需要在本地維護(hù)一個等待ack隊列,并配合timer超時機(jī)制,來記錄哪些消息沒有收到ack:N,以定時重發(fā);
- 4)客戶端本地生成一個遞增序列號發(fā)送給服務(wù)器,用作保證發(fā)送順序性。該序列號還用作ack隊列收消息時候的移除。
5.2客戶端序列號設(shè)計
1)方案一:
設(shè)計思路:
- 1)數(shù)據(jù)傳輸中的大小盡量小用int,不用bigint,節(jié)省傳輸大小;
- 2)只保證遞增即可,在用戶重新登錄或者重連后可以進(jìn)行日期重置,只保證單次;
- 3)客戶端發(fā)號器不需要像類似服務(wù)器端發(fā)號器那樣集群部署,不需要考慮集群同步問題。
注:上述生成器可以用18年[(2^29-1)/3600/24/365]左右,一秒內(nèi)最多產(chǎn)生4個消息。
優(yōu)點(diǎn):可以在斷線重連和重裝APP的情況下,18年之內(nèi)是有序的。
缺點(diǎn):每秒只能發(fā)4個消息,限制太大,對于群發(fā)場景不合適。
改進(jìn):使用long進(jìn)行傳輸,年限擴(kuò)展很久并且有序。
2)方案二:
設(shè)計思路:
- 1)每次重新建立鏈接后進(jìn)行重置,將sequence_id(int表示)從0開始進(jìn)行嚴(yán)格遞增;
- 2)客戶端發(fā)送消息會帶上唯一的遞增sequence_id,同一條消息重復(fù)投遞的sequence_id是一樣的;
- 3)后端存儲每個用戶的sequence_id,當(dāng)sequence_id歸0,用戶的epoch年代加1存儲入庫,單聊場景下轉(zhuǎn)發(fā)給接收者時候,接收者按照sequence_id和epoch來進(jìn)行排序。
優(yōu)點(diǎn):可以在斷線重連和重裝APP的情況下,接收者可以按照發(fā)送者發(fā)送時序來顯示,并且對發(fā)送消息的速率沒限制。
6、整體架構(gòu)設(shè)計之LSB設(shè)計
6.1思路
IM接入層的高可用、負(fù)載均衡、擴(kuò)展性全部在這里面做。客戶端通過LSB,來獲取gate IP地址,通過IP直連。
這樣做的目的是:
- 1)靈活的負(fù)載均衡策略 可根據(jù)最少連接數(shù)來分配IP;
- 2)做灰度策略來分配IP;
- 3)AppId業(yè)務(wù)隔離策略 不同業(yè)務(wù)連接不同的gate,防止相互影響;
- 4)單聊和群聊的im接入層通道分開。
6.2優(yōu)化
上述設(shè)計存在一個問題:就是當(dāng)某個實(shí)例重啟后,該實(shí)例的連接斷開后,客戶端會發(fā)起重連,重連就大概率轉(zhuǎn)移其他實(shí)例上,導(dǎo)致最近啟動的實(shí)例連接數(shù)較少,最早啟動的實(shí)例連接數(shù)較多。
解決方法:
- 1)客戶端會發(fā)起重連,跟服務(wù)器申請重連的新的服務(wù)器IP,系統(tǒng)提供合適的算法來平攤gate層的壓力,防止雪崩效應(yīng);
- 2)gate層定時上報本機(jī)的元數(shù)據(jù)信息以及連接數(shù)信息,提供給LSB中心,LSB根據(jù)最少連接數(shù)負(fù)載均衡實(shí)現(xiàn),來計算一個節(jié)點(diǎn)供連接。
7、整體架構(gòu)設(shè)計之GATE層網(wǎng)關(guān)設(shè)計
GATE層網(wǎng)關(guān)設(shè)計主要遵從以下幾點(diǎn):
- 1)任何一個gate網(wǎng)關(guān)斷掉,用戶端檢測到以后重新連接LSB服務(wù)獲取另一個gate網(wǎng)關(guān)IP,拿到IP重新進(jìn)行長連接通信(對整體服務(wù)可靠性基本沒有影響);
- 2)gate可以無狀態(tài)的橫向部署,來擴(kuò)展接入層的接入能力;
- 3)根據(jù)協(xié)議分類將入口請求打到不同的網(wǎng)關(guān)上去,HTTP網(wǎng)關(guān)接收HTTP請求,TCP網(wǎng)關(guān)接收tcp長連接請求;
- 4)長連接網(wǎng)關(guān),提供各種監(jiān)控功能,比如網(wǎng)關(guān)執(zhí)行線程數(shù)、隊列任務(wù)數(shù)、ByteBuf使用堆內(nèi)存數(shù)、堆外內(nèi)存數(shù)、消息上行和下行的數(shù)量以及時間。
8、整體架構(gòu)設(shè)計之LOGIC和路由SDK設(shè)計
logic按照分布式微服務(wù)的拆分思想進(jìn)行拆分,拆分為多個模塊,集群部署。
主要包括:
- 1)消息服務(wù);
- 2)紅包服務(wù);
- 3)其他服務(wù)。
消息logic服務(wù)集成路由客戶端的SDK,SDK職責(zé)主要是:
- 1)負(fù)責(zé)和網(wǎng)關(guān)底層通信交互;
- 2)負(fù)責(zé)網(wǎng)關(guān)服務(wù)尋址;
- 3)負(fù)責(zé)存儲uid和gate層機(jī)器ID關(guān)系(有狀態(tài):多級緩存避免和中間件多次交互。無狀態(tài):在業(yè)務(wù)初期可以不用存);
- 4)配合網(wǎng)關(guān)負(fù)責(zé)路由信息一致性保證。
針對上述第4)點(diǎn):
- 1)如果路由狀態(tài)和channel通道不一致,比如有路由狀態(tài),沒有channel通道(已關(guān)閉)那么,就會走離線消息流出,并且清除路由信息;
- 2)動態(tài)重啟gate,會及時清理路由信息。
SDK和網(wǎng)關(guān)底層通信設(shè)計:
如上圖所示:網(wǎng)關(guān)層到服務(wù)層,只需要單向傳輸發(fā)請求,網(wǎng)關(guān)層不需要關(guān)心調(diào)用的結(jié)果。而客戶端想要的ack或者notify請求是由SDK發(fā)送數(shù)據(jù)到網(wǎng)關(guān)層,SDK也不需要關(guān)心調(diào)用的結(jié)果,最后網(wǎng)關(guān)層只轉(zhuǎn)發(fā)數(shù)據(jù),不做額外的邏輯處理。
SDK和所有的網(wǎng)關(guān)進(jìn)行長連接,當(dāng)發(fā)送信息給客戶端時,根據(jù)路由尋址信息,即可通過長連接推送信息。
9、通信協(xié)議設(shè)計
9.1目標(biāo)
通信協(xié)議設(shè)計的主要目標(biāo)是:
- 1)高性能:協(xié)議設(shè)計緊湊,保證數(shù)據(jù)包小,并且序列化性能好;
- 2)可擴(kuò)展:針對后續(xù)業(yè)務(wù)發(fā)展,可以自由的自定義協(xié)議,無需較大改動協(xié)議結(jié)構(gòu)。
9.2設(shè)計
IM協(xié)議采用二進(jìn)制定長包頭和變長包體來實(shí)現(xiàn)客戶端和服務(wù)端的通信,并且采用谷歌protobuf序列化協(xié)議。
設(shè)計如下:
各個字段解釋如下:
- 1)headData:頭部標(biāo)識,協(xié)議頭標(biāo)識,用作粘包半包處理。4個字節(jié);
- 2)version:客戶端版本。4個字節(jié);
- 3)cmd:業(yè)務(wù)命令,比如心跳、推送、單聊、群聊。1個字節(jié);
- 4)msgType:消息通知類型 request response notify。1個字節(jié);
- 5)logId:調(diào)試性日志,追溯一個請求的全路徑。4個字節(jié);
- 6)sequenceId:序列號,可以用作異步處理。4個字節(jié);
- 7)dataLength:數(shù)據(jù)體的長度。4個字節(jié);
- 8)data:數(shù)據(jù)。
PS:如果你對Protobuf不了解,建議詳讀以下系列文章:
1.《強(qiáng)列建議將Protobuf作為你的即時通訊應(yīng)用數(shù)據(jù)傳輸格式》
2.《IM通訊協(xié)議專題學(xué)習(xí)(一):Protobuf從入門到精通,一篇就夠!》
3.《IM通訊協(xié)議專題學(xué)習(xí)(二):快速理解Protobuf的背景、原理、使用、優(yōu)缺點(diǎn)》
4.《IM通訊協(xié)議專題學(xué)習(xí)(三):由淺入深,從根上理解Protobuf的編解碼原理》
5.《IM通訊協(xié)議專題學(xué)習(xí)(四):從Base64到Protobuf,詳解Protobuf的數(shù)據(jù)編碼原理》
6.《IM通訊協(xié)議專題學(xué)習(xí)(五):Protobuf到底比JSON快幾倍?全方位實(shí)測!》
7.《IM通訊協(xié)議專題學(xué)習(xí)(六):手把手教你如何在Android上從零使用Protobuf》
8.《IM通訊協(xié)議專題學(xué)習(xí)(七):手把手教你如何在NodeJS中從零使用Protobuf》
9.《IM通訊協(xié)議專題學(xué)習(xí)(八):金蝶隨手記團(tuán)隊的Protobuf應(yīng)用實(shí)踐(原理篇)》
10.《IM通訊協(xié)議專題學(xué)習(xí)(九):手把手教你如何在iOS上從零使用Protobuf》
9.3實(shí)踐
針對數(shù)據(jù)data,網(wǎng)關(guān)gate層不做反序列化,反序列化步驟在service做,避免重復(fù)序列化和反序列化導(dǎo)致的性能損失。
網(wǎng)關(guān)層不做業(yè)務(wù)邏輯處理,只做消息轉(zhuǎn)發(fā)和推送,減少網(wǎng)關(guān)層的復(fù)雜度。
10、安全設(shè)計
為防止消息傳輸過程中不被截獲、篡改、偽造,采用TLS傳輸層加密協(xié)議(可參考《微信新一代通信安全解決方案:基于TLS1.3的MMTLS詳解》)。
私有化協(xié)議天然具備一定的防竊取和防篡改的能力,相對于使用JSON、XML、HTML等明文傳輸系統(tǒng),被第三方截獲后在內(nèi)容破解上相對成本更高,因此安全性上會更好一些。
消息存儲安全性:將針對賬號密碼的存儲安全可以通過“高強(qiáng)度單向散列算法”和“加鹽”機(jī)制來提升加密密碼可逆性;IM消息采用“端到端加密”方式來提供更加安全的消息傳輸保護(hù)。
安全層協(xié)議設(shè)計:基于動態(tài)密鑰,借鑒類似SSL,不需要用證書來管理(可參考《探討組合加密算法在IM中的應(yīng)用》)。
11、消息投遞設(shè)計
11.1概述
一個正常的消息流轉(zhuǎn)需要如下圖所示的流程:
如上圖所示:
- 1)客戶端A發(fā)送請求包R;
- 2)server將消息存儲到DB;
- 3)存儲成功后返回確認(rèn)ack;
- 4)server push消息給客戶端B;
- 5)客戶端B收到消息后返回確認(rèn)ack;
- 6)server收到ack后更新消息的狀態(tài)或者刪除消息。
需要考慮的是:一個健壯的IM系統(tǒng)需要考慮各種異常情況,比如丟消息,重復(fù)消息,消息時序問題。
11.2消息可靠性如何保證(不丟消息)
我的設(shè)計和實(shí)現(xiàn)思路是這樣的:
- 1)應(yīng)用層ACK;
- 2)客戶端需要超時與重傳;
- 3)服務(wù)端需要超時與重傳,具體做法就是增加ack隊列和定時器Timer;
- 4)業(yè)務(wù)側(cè)兜底保證,客戶端拉消息通過一個本地的舊的序列號來拉取服務(wù)器的最新消息;
- 5)為了保證消息必達(dá),在線客戶端還增加一個定時器,定時向服務(wù)端拉取消息,避免服務(wù)端向客戶端發(fā)送拉取通知的包丟失導(dǎo)致客戶端未及時拉取數(shù)據(jù)。
相關(guān)資料可參考:
1.《從客戶端的角度來談?wù)勔苿佣薎M的消息可靠性和送達(dá)機(jī)制》
2.《IM消息送達(dá)保證機(jī)制實(shí)現(xiàn)(一):保證在線實(shí)時消息的可靠投遞》
3.《IM消息送達(dá)保證機(jī)制實(shí)現(xiàn)(二):保證離線消息的可靠投遞》
4.《IM開發(fā)干貨分享:如何優(yōu)雅的實(shí)現(xiàn)大量離線消息的可靠投遞》
5.《理解IM消息“可靠性”和“一致性”問題,以及解決方案探討》
6.《融云技術(shù)分享:全面揭秘億級IM消息的可靠投遞機(jī)制》
11.3消息重復(fù)性如何保證(不重復(fù))
超時與重傳機(jī)制將導(dǎo)致接收的client收到重復(fù)的消息,具體做法就是一份消息使用同一個消息ID進(jìn)行去重處理。
相關(guān)資料可參考:
1.《IM群聊消息如此復(fù)雜,如何保證不丟不重?》
2.《完全自已開發(fā)的IM該如何設(shè)計“失敗重試”機(jī)制?》
11.4消息順序性如何保證(不亂序)
消息亂序影響的因素:
- 1)時鐘不一致,分布式環(huán)境下每個機(jī)器的時間可能是不一致的;
- 2)多發(fā)送方和多接收方,這種情況下,無法保先發(fā)的消息被先收到;
- 3)網(wǎng)絡(luò)傳輸和多線程,網(wǎng)絡(luò)傳輸不穩(wěn)定的話可能導(dǎo)致包在數(shù)據(jù)傳輸過程中有的慢有的快。多線程也可能是會導(dǎo)致時序不一致影響的因素。
以上:如果保持絕對的實(shí)現(xiàn),那么只能是一個發(fā)送方,一個接收方,一個線程阻塞式通訊來實(shí)現(xiàn)。那么性能會降低。
1)如何保證時序:
單聊:通過發(fā)送方的絕對時序seq,來作為接收方的展現(xiàn)時序seq。
實(shí)現(xiàn)方式:可以通過時間戳或者本地序列號方式來實(shí)現(xiàn)
缺點(diǎn):本地時間戳不準(zhǔn)確或者本地序列號在意外情況下可能會清0,都會導(dǎo)致發(fā)送方的絕對時序不準(zhǔn)確
群聊:因為發(fā)送方多點(diǎn)發(fā)送時序不一致,所以通過服務(wù)器的單點(diǎn)做序列化,也就是通過ID遞增發(fā)號器服務(wù)來生成seq,接收方通過seq來進(jìn)行展現(xiàn)時序。
實(shí)現(xiàn)方式:通過服務(wù)端統(tǒng)一生成唯一趨勢遞增消息ID來實(shí)現(xiàn)或者通過redis的遞增incr來實(shí)現(xiàn)。
缺點(diǎn):redis的遞增incr來實(shí)現(xiàn),redis取號都是從主取的,會有性能瓶頸。ID遞增發(fā)號器服務(wù)是集群部署,可能不同發(fā)號服務(wù)上的集群時間戳不同,可能會導(dǎo)致后到的消息seq還小。
群聊時序的優(yōu)化:按照上面的群聊處理,業(yè)務(wù)上按照道理只需要保證單個群的時序,不需要保證所有群的絕對時序,所以解決思路就是同一個群的消息落到同一個發(fā)號service上面,消息seq通過service本地生成即可。
2)客戶端如何保證順序:
為什么要保證順序?因為消息即使按照順序到達(dá)服務(wù)器端,也會可能出現(xiàn):不同消息到達(dá)接收端后,可能會出現(xiàn)“先產(chǎn)生的消息后到”“后產(chǎn)生的消息先到”等問題。所以客戶端需要進(jìn)行兜底的流量整形機(jī)制
如何保證順序?可以在接收方收到消息后進(jìn)行判定,如果當(dāng)前消息序號大于前一條消息的序號就將當(dāng)前消息追加在會話里。否則繼續(xù)往前查找倒數(shù)第二條、第三條等消息,一直查找到恰好小于當(dāng)前推送消息的那條消息,然后插入在其后展示。
相關(guān)資料可參考:
《零基礎(chǔ)IM開發(fā)入門(四):什么是IM系統(tǒng)的消息時序一致性?》
《一套億級用戶的IM架構(gòu)技術(shù)干貨(下篇):可靠性、有序性、弱網(wǎng)優(yōu)化等》
《如何保證IM實(shí)時消息的“時序性”與“一致性”?》
《一個低成本確保IM消息時序的方法探討》
12、消息通知設(shè)計
12.1概述
整體消息推送和拉取的時序圖如下:
12.2消息拉取方式的選擇
本系統(tǒng)是通過推拉結(jié)合來進(jìn)行服務(wù)器端消息的推送和客戶端的拉取。我們知道單pull和單push有以下缺點(diǎn)。
對于單pull:
- 1)pull要考慮到消息的實(shí)時性,不知道消息何時送達(dá);
- 2)pull要考慮到哪些好友和群收到了消息,要循環(huán)每個群和好友拿到消息列表,讀擴(kuò)散。
對于單push:
- 1)push實(shí)時性高,只要將消息推送給接收者就ok,但是會集中消耗服務(wù)器資源;
- 2)并且再群聊非常多、聊天頻率非常高的情況下,會增加客戶端和服務(wù)端的網(wǎng)絡(luò)交互次數(shù)。
對于推拉結(jié)合:
- 1)推拉結(jié)合的方式能夠分?jǐn)偡?wù)端的壓力,能保證時效性,又能保證性能;
- 2)具體做法就是有新消息時候,推送哪個好友或者哪個群有新消息,以及新消息的數(shù)量或者最新消息ID,客戶端按需根據(jù)自身數(shù)據(jù)進(jìn)行拉取。
12.3推拉隔離設(shè)計
為什么做隔離?
如果客戶端一邊正在拉取數(shù)據(jù),一邊有新的增量消息push過來。
如何做隔離?
本地設(shè)置一個全局的狀態(tài),當(dāng)客戶端拉取完離線消息后設(shè)置狀態(tài)為1(表示離線消息拉取完畢)。當(dāng)客戶端收到拉取實(shí)時消息,會啟用一個輪詢監(jiān)聽這個狀態(tài),狀態(tài)為1后,再去向服務(wù)器拉取消息。
如果是push消息過來(不是主動拉取),那么會先將消息存儲到本地的消息隊列中,等待客戶端上一次拉取數(shù)據(jù)完畢,然后將數(shù)據(jù)進(jìn)行合并即可。
相關(guān)資料可參考:
《阿里IM技術(shù)分享(六):閑魚億級IM消息系統(tǒng)的離線推送到達(dá)率優(yōu)化》
《阿里IM技術(shù)分享(七):閑魚IM的在線、離線聊天數(shù)據(jù)同步機(jī)制優(yōu)化實(shí)踐》
13、消息ID生成設(shè)計
以下是我設(shè)計的場景:
- 1)單機(jī)高峰并發(fā)量小于1W,預(yù)計未來5年單機(jī)高峰并發(fā)量小于10W;
- 2)有2個機(jī)房,預(yù)計未來5年機(jī)房數(shù)量小于4個 每個機(jī)房機(jī)器數(shù)小于150臺;
- 3)目前只有單聊和群聊兩個業(yè)務(wù)線,后續(xù)可以擴(kuò)展為系統(tǒng)消息、聊天室、客服等業(yè)務(wù)線,最多8個業(yè)務(wù)線。
根據(jù)以上業(yè)務(wù)情況,來設(shè)計分布式ID:
優(yōu)點(diǎn):
- 1)不同機(jī)房不同機(jī)器不同業(yè)務(wù)線內(nèi)生成的ID互不相同;
- 2)每個機(jī)器的每毫秒內(nèi)生成的ID不同;
- 3)預(yù)留兩位留作擴(kuò)展位。
缺點(diǎn):當(dāng)并發(fā)度不高的時候,時間跨毫秒的消息,區(qū)分不出來消息的先后順序。因為時間跨毫秒的消息生成的ID后面的最后一位都是0,后續(xù)如果按照消息ID維度進(jìn)行分庫分表,會導(dǎo)致數(shù)據(jù)傾斜。
兩種解決方案:
- 1)方案一:去掉snowflake最后8位,然后對剩余的位進(jìn)行取模;
- 2)方案二:不同毫秒的計數(shù),每次不是歸0,而是歸為隨機(jī)數(shù),相比方案一,比較簡單實(shí)用。
相關(guān)資料可參考:
《微信的海量IM聊天消息序列號生成實(shí)踐(算法原理篇)》
《微信的海量IM聊天消息序列號生成實(shí)踐(容災(zāi)方案篇)》
《解密融云IM產(chǎn)品的聊天消息ID生成策略》
《深度解密美團(tuán)的分布式ID生成算法》
《開源分布式ID生成器UidGenerator的技術(shù)實(shí)現(xiàn)》
《深度解密滴滴的高性能ID生成器(Tinyid)》
14、消息未讀數(shù)設(shè)計
14.1基本
實(shí)現(xiàn)思路大致如下:
- 1)每發(fā)一個消息,消息接收者的會話未讀數(shù)+1,并且接收者所有未讀數(shù)+1;
- 2)消息接收者返回消息接收確認(rèn)ack后,消息未讀數(shù)會-1;
- 3)消息接收者的未讀數(shù)+1,服務(wù)端就會推算有多少條未讀數(shù)的通知。
分布式鎖保證總未讀數(shù)和會話未讀數(shù)一致:
- 1)原因:當(dāng)總未讀數(shù)增加,這個時候客戶端來了請求將未知數(shù)置0,然后再增加會話未讀數(shù),那么會導(dǎo)致不一致;
- 2)保證:為了保證總未讀數(shù)和會話未讀數(shù)原子性,需要用分布式鎖來保證。
14.2群聊消息未讀數(shù)的難點(diǎn)和優(yōu)化思路
對于群聊來說,消息未讀數(shù)的技術(shù)難點(diǎn)主要是:一個群聊每秒幾百的并發(fā)聊天,比如消息未讀數(shù),相當(dāng)于每秒W級別的寫入redis,即便redis做了集群數(shù)據(jù)分片+主從,但是寫入還是單節(jié)點(diǎn),會有寫入瓶頸。
我的優(yōu)化思路是:按群ID分組或者用戶ID分組,批量寫入,寫入的兩種方式:定時flush和滿多少消息進(jìn)行flush。
15、網(wǎng)關(guān)設(shè)計
15.1概述
本套IM系統(tǒng)在設(shè)計時,將網(wǎng)關(guān)分為了接入層網(wǎng)關(guān)和應(yīng)用層網(wǎng)關(guān)兩種。
- 接入層網(wǎng)關(guān)和應(yīng)用層網(wǎng)關(guān)區(qū)別主要是:
- 1)接入層網(wǎng)關(guān)需要有接收通知包或者下行接收數(shù)據(jù)的端口,并且需要另外開啟線程池。應(yīng)用層網(wǎng)關(guān)不需要開端口,并且不需要開啟線程池;
- 2)接入層網(wǎng)關(guān)需要保持長連接,接入層網(wǎng)關(guān)需要本地緩存channel映射關(guān)系。應(yīng)用層網(wǎng)關(guān)無狀態(tài)不需要保存。
15.2接入層網(wǎng)關(guān)設(shè)計
我的設(shè)計目標(biāo)是:
- 1)網(wǎng)關(guān)的線程池實(shí)現(xiàn)1+8+4+1,減少線程切換;
- 2)集中實(shí)現(xiàn)長連接管理和推送能力;
- 3)與業(yè)務(wù)服務(wù)器解耦,集群部署縮容擴(kuò)容以及重啟升級不相互影響;
- 4)長連接的監(jiān)控與報警能力;
- 5)客戶端重連指令一鍵實(shí)現(xiàn)。
主要技術(shù)要點(diǎn):
- 1)支持自定義協(xié)議以及序列化;
- 2)支持websocket協(xié)議;
- 3)通道連接自定義保活以及心跳檢測;
- 4)本地緩存channel;
- 5)責(zé)任鏈;
- 6)服務(wù)調(diào)用完全異步;
- 7)泛化調(diào)用;
- 8)轉(zhuǎn)發(fā)通知包或者Push包;
- 9)容錯網(wǎng)關(guān)down機(jī)處理。
設(shè)計方案(一個Notify包的數(shù)據(jù)經(jīng)網(wǎng)關(guān)的線程模型圖):
15.3應(yīng)用層API網(wǎng)關(guān)設(shè)計
我的設(shè)計目標(biāo)是:
- 1)基于版本的自動發(fā)現(xiàn)以及灰度/擴(kuò)容 ,不需要關(guān)注IP;
- 2)網(wǎng)關(guān)的線程池實(shí)現(xiàn)1+8+1,減少線程切換;
- 3)支持協(xié)議轉(zhuǎn)換實(shí)現(xiàn)多個協(xié)議轉(zhuǎn)換,基于SPI來實(shí)現(xiàn);
- 4)與業(yè)務(wù)服務(wù)器解耦,集群部署縮容擴(kuò)容以及重啟升級不相互影響;
- 5)接口錯誤信息統(tǒng)計和RT時間的監(jiān)控和報警能力;
- 6)UI界面實(shí)現(xiàn)路由算法,服務(wù)接口版本管理,灰度策略管理以及接口和服務(wù)信息展示能力;
- 7)基于OpenAPI提供接口級別的自動生成文檔的功能。
主要技術(shù)要點(diǎn):
- 1)Http2.0;
- 2)channel連接池復(fù)用;
- 3)Netty http 服務(wù)端編解碼;
- 4)責(zé)任鏈;
- 5)服務(wù)調(diào)用完全異步;
- 6)全鏈路超時機(jī)制;
- 7)泛化調(diào)用。
設(shè)計方案(一個請求包的數(shù)據(jù)經(jīng)網(wǎng)關(guān)的架構(gòu)圖):
16、高并發(fā)設(shè)計
16.1架構(gòu)優(yōu)化
主要從以下幾個方面入手:
- 1)水平擴(kuò)展:各個模塊無狀態(tài)部署;
- 2)線程模型:每個服務(wù)底層線程模型遵從Netty主從reactor模型;
- 3)多層緩存:Gate層二級緩存,Redis一級緩存;
- 4)長連接:客戶端長連接保持,避免頻繁創(chuàng)建連接消耗。
16.2萬人群聊優(yōu)化
技術(shù)難點(diǎn)主要是:消息扇出大,比如每秒群聊有50條消息,群聊2000人,那么光一個群對系統(tǒng)并發(fā)就有10W的消息扇出。
優(yōu)化思路:
- 1)批量ACK:每條群消息都ACK,會給服務(wù)器造成巨大的沖擊,為了減少ACK請求量,參考TCP的Delay ACK機(jī)制,在接收方層面進(jìn)行批量ACK;
- 2)群消息和成員批量加載以及懶加載:在真正進(jìn)入一個群時才實(shí)時拉取群友的數(shù)據(jù);
- 3)群離線消息過多:群消息分頁拉取,第二次拉取請求作為第一次拉取請求的ack;
- 4)對于消息未讀數(shù)場景,每個用戶維護(hù)一個全局的未讀數(shù)和每個會話的未讀數(shù),當(dāng)群聊非常大時,未讀資源變更的QPS非常大。這個時候應(yīng)用層對未讀數(shù)進(jìn)行緩存,批量寫+定時寫來保證未讀計數(shù)的寫入性能;
- 5)路由信息存入redis會有寫入和讀取的性能瓶頸,每條消息在發(fā)出的時候會查路由信息來發(fā)送對應(yīng)的gate接入層,比如有10個群,每個群1W,那么1s100條消息,那么1000W的查詢會打滿redis,即使redis做了集群。優(yōu)化的思路就是將集中的路由信息分散到msg層 JVM本地內(nèi)存中,然后做Route可用,避免單點(diǎn)故障;
- 6)存儲的優(yōu)化:擴(kuò)散寫寫入并發(fā)量巨大,另一方面也存在存儲浪費(fèi),一般優(yōu)化成擴(kuò)散讀的方式存儲;
- 7)消息路由到相同接入層機(jī)器進(jìn)行合并請求減少網(wǎng)絡(luò)包傳輸。
相關(guān)資料:
1.《網(wǎng)易云信技術(shù)分享:IM中的萬人群聊技術(shù)方案實(shí)踐總結(jié)》
2.《企業(yè)微信的IM架構(gòu)設(shè)計揭秘:消息模型、萬人群、已讀回執(zhí)、消息撤回等》
3.《融云IM技術(shù)分享:萬人群聊消息投遞方案的思考和實(shí)踐》
16.3代碼優(yōu)化
具體的代碼優(yōu)化思路就是:本地會話信息由一個hashmap保持,導(dǎo)致鎖機(jī)制嚴(yán)重,按照用戶標(biāo)識進(jìn)行hash,講會話信息存在多個map中,減少鎖競爭。同時利用雙buffer機(jī)制,避免未讀計數(shù)寫入阻塞。
16.4推拉結(jié)合優(yōu)化合并
背景:消息下發(fā)到群聊服務(wù)后,需要發(fā)送拉取通知給接收者,具體邏輯是群聊服務(wù)同步消息到路由層,路由層發(fā)送消息給接收者,接收者再來拉取消息。
問題:如果消息連續(xù)發(fā)送或者對同一個接收者連續(xù)發(fā)送消息頻率過高,會有許多的通知消息發(fā)送給路由層,消息量過大,可能會導(dǎo)致logic線程堆積,請求路由層阻塞。
解決:發(fā)送者發(fā)送消息到邏輯層持久化后,將通知消息先存放一個隊列中,相同的接收者接收消息通知消息后,更新相應(yīng)的最新消息通知時間,然后輪訓(xùn)線程會輪訓(xùn)隊列,將多個消息會合并為一個通知拉取發(fā)送至路由層,降低了客戶端與服務(wù)端的網(wǎng)絡(luò)消耗和服務(wù)器內(nèi)部網(wǎng)絡(luò)消耗。
好處:保證同一時刻,下發(fā)線程一輪只會向同一用戶發(fā)送一個通知拉取,一輪的時間可以自行控制。
17、高可用設(shè)計
17.1心跳設(shè)計
主要是:
- 1)服務(wù)端檢測到某個客戶端遲遲沒有心跳過來可以主動關(guān)閉通道,讓它下線,并且清除在線信息和路由信息;
- 2)客戶端檢測到某個服務(wù)端遲遲沒有響應(yīng)心跳也能重連獲取一個新的連接。
智能心跳策略:比如正在發(fā)包的時候,不需要發(fā)送心跳。等待發(fā)包完畢后在開啟心跳。并且自適應(yīng)心跳策略調(diào)整。
相關(guān)資料:
《為何基于TCP協(xié)議的移動端IM仍然需要心跳保活機(jī)制?》
《一文讀懂即時通訊應(yīng)用中的網(wǎng)絡(luò)心跳包機(jī)制:作用、原理、實(shí)現(xiàn)思路等》
《微信團(tuán)隊原創(chuàng)分享:Android版微信后臺保活實(shí)戰(zhàn)分享(進(jìn)程保活篇)》
《微信團(tuán)隊原創(chuàng)分享:Android版微信后臺保活實(shí)戰(zhàn)分享(網(wǎng)絡(luò)保活篇)》
《融云技術(shù)分享:融云安卓端IM產(chǎn)品的網(wǎng)絡(luò)鏈路保活技術(shù)實(shí)踐》
《移動端IM實(shí)踐:實(shí)現(xiàn)Android版微信的智能心跳機(jī)制》
《萬字長文:手把手教你實(shí)現(xiàn)一套高效的IM長連接自適應(yīng)心跳保活機(jī)制》
17.2系統(tǒng)穩(wěn)定性設(shè)計
背景:高峰期系統(tǒng)壓力大,偶發(fā)的網(wǎng)絡(luò)波動或者機(jī)器過載,都有可能導(dǎo)致大量的系統(tǒng)失敗。加上IM系統(tǒng)要求實(shí)時性,不能用異步處理實(shí)時發(fā)過來的消息。所以有了柔性保護(hù)機(jī)制防止雪崩。
柔性保護(hù)機(jī)制開啟判斷指標(biāo),當(dāng)每個指標(biāo)不在平均范圍內(nèi)的時候就開啟。
這些判斷指標(biāo)主要是:
- 1)每條消息的ack時間 RT時間
- 2)同時在線人數(shù)以及同時發(fā)消息的人數(shù)
- 3)每臺機(jī)器的負(fù)載CPU和內(nèi)存和網(wǎng)絡(luò)IO和磁盤IO以及GC參數(shù)
當(dāng)開啟了柔性保護(hù)機(jī)制,那么會返回失敗,用戶端體驗不友好,如何優(yōu)化?
以下是我的優(yōu)化思路:
- 1)當(dāng)開啟了柔性保護(hù)機(jī)制,邏輯層hold住多余的請求,返回前端成功,不顯示發(fā)送失敗,后端異步重試,直至成功;
- 2)為了避免重試加劇系統(tǒng)過載,指數(shù)時間延遲重試。
17.3異常場景設(shè)計
gate層重啟升級或者意外down機(jī)有以下問題:
- 1)客戶端和gate意外丟失長連接,導(dǎo)致 客戶端在發(fā)送消息的時候?qū)е孪⒊瑫r等待以及客戶端重試等無意義操作;
- 2)發(fā)送給客戶端的消息,從Msg消息層轉(zhuǎn)發(fā)給gate的消息丟失,導(dǎo)致消息超時等待以及重試。
解決方案如下:
- 1)重啟升級時候,向客戶端發(fā)送重新連接指令,讓客戶端重新請求LSB獲取IP直連;
- 2)當(dāng)gate層down機(jī)異常停止時候,增加hook鉤子,向客戶端發(fā)送重新連接指令;
- 3)額外增加hook,向Msg消息層發(fā)送請求清空路由消息和在線狀態(tài),并且清除redis的路由信息。
17.4Redis宕機(jī)高可用設(shè)計
Redis的作用背景:
- 1)當(dāng)用戶鏈接上網(wǎng)關(guān)后,網(wǎng)關(guān)會將用戶的userId和機(jī)器信息存入redis,用作這個user接收消息時候,消息的路由;
- 2)消息服務(wù)在發(fā)消息給user時候,會查詢Redis的路由信息,用來發(fā)送消息給哪個一個網(wǎng)關(guān)。
如果Redis宕機(jī),會造成下面結(jié)果:
- 1)消息中轉(zhuǎn)不過去,所有的用戶可以發(fā)送消息,但是都接收不了消息;
- 2)如果有在線機(jī)制,那么系統(tǒng)都認(rèn)為是離線狀態(tài),會走手機(jī)消息通道推送。
Redis宕機(jī)兜底處理策略:
- 1)消息服務(wù)定時任務(wù)同步路由信息到本地緩存,如果redis掛了,從本地緩存拿消息;
- 2)網(wǎng)關(guān)服務(wù)在收到用戶側(cè)的上線和下線后,會同步廣播本地的路由信息給各個消息服務(wù),消息服務(wù)接收后更新本地環(huán)境數(shù)據(jù);
- 3)網(wǎng)絡(luò)交互次數(shù)多,以及消息服務(wù)多,可以用批量或者定時的方式同步廣播路由消息給各個消息服務(wù)。
18、核心表結(jié)構(gòu)設(shè)計
核心設(shè)計要點(diǎn):
- 1)群消息只存儲一份,用戶不需要為每個消息單獨(dú)存一份。用戶也無需去刪除群消息;
- 2)對于在線的用戶,收到群消息后,修改這個last_ack_msg_id;
- 3)對于離線用戶,用戶上線后,對比最新的消息ID和last_ack_msg_id,來進(jìn)行拉取(參考Kafka的消費(fèi)者模型);
- 4)對應(yīng)單聊,需要記錄消息的送達(dá)狀態(tài),以便在異常情況下來做重試處理。
群用戶消息表 t_group_user_msg:
群消息表 t_group_msg:
參考資料:
1.《一套海量在線用戶的移動端IM架構(gòu)設(shè)計實(shí)踐分享(含詳細(xì)圖文)》
2.《基于Netty,從零開發(fā)一個IM服務(wù)端》
19、紅包設(shè)計
搶紅包的大致核心邏輯如下:
- 1)銀行快捷支付,保證賬戶余額和發(fā)送紅包邏輯的一致性;
- 2)發(fā)送紅包后,首先計算好紅包的個數(shù),個數(shù)確定好后,確定好每個紅包的金額,存入存儲層【這里可以是redis的List或者是隊列】方便后續(xù)每個人來取;
- 3)生成一個24小時的延遲任務(wù),檢測紅包是否還有錢方便退回;
- 4)每個紅包的金額需要保證每個紅包的的搶金額概率是一致的,算法需要考量;
- 5)存入數(shù)據(jù)庫表中后,服務(wù)器通過長連接,給群里notify紅包消息,供群成員搶紅包;
- 6)群成員并發(fā)搶紅包,在第二步中會將每個紅包的金額放入一個隊列或者其他存儲中,群成員實(shí)際是來競爭去隊列中的紅包金額。兜底機(jī)制:如果redis掛了,可以重新生成紅包信息到數(shù)據(jù)庫中;
- 7)取成功后,需要保證紅包剩余金額、新插入的紅包流水?dāng)?shù)據(jù)、隊列中的紅包數(shù)據(jù)以及群成員的余額賬戶金額一致性;
- 8)這里還需要保證一個用戶只能領(lǐng)取一次,并且保持冪等。
相關(guān)資料:
《社交軟件紅包技術(shù)解密(一):全面解密QQ紅包技術(shù)方案——架構(gòu)、技術(shù)實(shí)現(xiàn)等》
《社交軟件紅包技術(shù)解密(二):解密微信搖一搖紅包從0到1的技術(shù)演進(jìn)》
《社交軟件紅包技術(shù)解密(三):微信搖一搖紅包雨背后的技術(shù)細(xì)節(jié)》
《社交軟件紅包技術(shù)解密(四):微信紅包系統(tǒng)是如何應(yīng)對高并發(fā)的》
《社交軟件紅包技術(shù)解密(五):微信紅包系統(tǒng)是如何實(shí)現(xiàn)高可用性的》
《社交軟件紅包技術(shù)解密(六):微信紅包系統(tǒng)的存儲層架構(gòu)演進(jìn)實(shí)踐》
《社交軟件紅包技術(shù)解密(七):支付寶紅包的海量高并發(fā)技術(shù)實(shí)踐》
《社交軟件紅包技術(shù)解密(八):全面解密微博紅包技術(shù)方案》
《社交軟件紅包技術(shù)解密(九):談?wù)勈諵紅包的功能邏輯、容災(zāi)、運(yùn)維、架構(gòu)等》
《社交軟件紅包技術(shù)解密(十):手Q客戶端針對2020年春節(jié)紅包的技術(shù)實(shí)踐》
《社交軟件紅包技術(shù)解密(十一):解密微信紅包隨機(jī)算法(含代碼實(shí)現(xiàn))》
《社交軟件紅包技術(shù)解密(十二):解密抖音春節(jié)紅包背后的技術(shù)設(shè)計與實(shí)踐》
20、核心業(yè)務(wù)流程梳理
20.1單聊流程
假設(shè)是用戶A發(fā)消息給用戶B ,以下是完整的業(yè)務(wù)流程。
1)A打包數(shù)據(jù)發(fā)送給服務(wù)端,服務(wù)端接收消息后,根據(jù)接收消息的sequence_id來進(jìn)行客戶端發(fā)送消息的去重,并且生成遞增的消息ID,將發(fā)送的信息和ID打包一塊入庫,入庫成功后返回ACK,ACK包帶上服務(wù)端生成的消息ID。
2)服務(wù)端檢測接收用戶B是否在線,在線直接推送給用戶B。
3)如果沒有本地消息ID則存入,并且返回接入層ACK信息;如果有則拿本地sequence_id和推送過來的sequence_id大小對比,并且去重,進(jìn)行展現(xiàn)時序進(jìn)行排序展示,并且記錄最新一條消息ID。最后返回接入層ack。
4)服務(wù)端接收ACK后,將消息標(biāo)為已送達(dá)。
5)如果用戶B不在線,首先將消息存入庫中,然后直接通過手機(jī)通知來告知客戶新消息到來。
6)用戶B上線后,拿本地最新的消息ID,去服務(wù)端拉取所有好友發(fā)送給B的消息,考慮到一次拉取所有消息數(shù)據(jù)量大,通過channel通道來進(jìn)行分頁拉取,將上一次拉取消息的最大的ID,作為請求參數(shù),來請求最新一頁的比ID大的數(shù)據(jù)。
20.2群聊流程
假設(shè)是用戶A發(fā)消息給群G ,以下是完整的業(yè)務(wù)流程。
1)登錄,TCP連接,token校驗,名詞檢查,sequence_id去重,生成遞增的消息ID,群消息入庫成功返回發(fā)送方ACK。
2)查詢?nèi)篏所有的成員,然后去redis中央存儲中找在線狀態(tài)。離線和在線成員分不同的方式處理。
3)在線成員:并行發(fā)送拉取通知,等待在線成員過來拉取,發(fā)送拉取通知包如丟失會有兜底機(jī)制。
4)在線成員過來拉取,會帶上這個群標(biāo)識和上一次拉取群的最小消息ID,服務(wù)端會找比這個消息ID大的所有的數(shù)據(jù)返回給客戶端,等待客戶端ACK。一段時間沒ack繼續(xù)推送。如果重試幾次后沒有回ack,那么關(guān)閉連接和清除ack等待隊列消息。
5)客戶端會更新本地的最新的消息ID,然后進(jìn)行ack回包。服務(wù)端收到ack后會更新群成員的最新的消息ID。
6)離線成員:發(fā)送手機(jī)通知欄通知。離線成員上線后,拿本地最新的消息ID,去服務(wù)端拉取群G發(fā)送給A的消息,通過channel通道來進(jìn)行分頁拉取,每一次請求,會將上一次拉取消息的最大的ID,作為請求參數(shù)來拉取消息,這里相當(dāng)于第二次拉取請求包是作為第一次拉取的ack包。
7)分頁的情況下,客戶端在收到上一頁請求的的數(shù)據(jù)后更新本地的最新的消息ID后,再請求下一頁并且?guī)舷D。上一頁請求的的數(shù)據(jù)可以當(dāng)作為ack來返回服務(wù)端,避免網(wǎng)絡(luò)多次交互。服務(wù)端收到ack后會更新群成員的最新的消息ID。
21、設(shè)計IM系統(tǒng)時的常見疑問
21.1相比傳統(tǒng)HTTP請求的業(yè)務(wù)系統(tǒng),IM業(yè)務(wù)系統(tǒng)的有哪些不一樣的設(shè)計難點(diǎn)?
主要是在線狀態(tài)維護(hù)。
相比于HTTP請求的業(yè)務(wù)系統(tǒng),接入層有狀態(tài),必須維持心跳和會話狀態(tài),加大了系統(tǒng)設(shè)計復(fù)雜度。
請求通信模型不一樣。相比于HTTP請求一個request等待一個response通信模型,IM系統(tǒng)則是一個數(shù)據(jù)包在全雙工長連接通道雙傳輸,客戶端和服務(wù)端消息交互的信令數(shù)據(jù)包設(shè)計復(fù)雜。
21.2對于單聊和群聊的實(shí)時性消息,是否需要MQ來作為通信的中間件來代替rpc?
MQ作為解耦可以有以下好處:
- 1)易擴(kuò)展:gate層到logic層無需路由,logic層多個有新的業(yè)務(wù)時候,只需要監(jiān)聽新的topic即可;
- 2)解耦:gate層到logic層解耦,不會有依賴關(guān)系;
- 3)節(jié)省端口資源:gate層無需再開啟新的端口接收logic的請求,而且直接監(jiān)聽MQ消息即可。
但是缺點(diǎn)也有:
- 1)網(wǎng)絡(luò)通信多一次網(wǎng)絡(luò)通信,增加RT的時間,消息實(shí)時性對于IM即使通信的場景是非常注重的一個點(diǎn);
- 2)MQ的穩(wěn)定性,不管任何系統(tǒng)只要引入中間件都會有穩(wěn)定性問題,需要考慮MQ不可用或者丟失數(shù)據(jù)的情況;
- 3)需要考慮到運(yùn)維的成本;
- 4)當(dāng)用消息中間代替路由層的時候,gate層需要廣播消費(fèi)消息,這個時候gate層會接收大部分的無效消息,因為這個消息的接收者channel不在本機(jī)維護(hù)的session中。
綜上:是否考慮使用MQ需要架構(gòu)師去考量,比如考慮業(yè)務(wù)是否允許、或者系統(tǒng)的流量、或者高可用設(shè)計等等影響因素。本項目基于使用成本、耦合成本和運(yùn)維成本考慮,采用Netty作為底層自定義通信方案來實(shí)現(xiàn),也能同樣實(shí)現(xiàn)層級調(diào)用。
參考資料:《阿里IM技術(shù)分享(九):深度揭密RocketMQ在釘釘IM系統(tǒng)中的應(yīng)用實(shí)踐》。
21.3為什么接入層用LSB返回的IP來做接入呢?
可以有以下好處:
- 1)靈活的負(fù)載均衡策略 可根據(jù)最少連接數(shù)來分配IP;
- 2)做灰度策略來分配IP;
- 3)AppId業(yè)務(wù)隔離策略 不同業(yè)務(wù)連接不同的gate,防止相互影響。
21.4為什么應(yīng)用層心跳對連接進(jìn)行健康檢查?
因為TCP Keepalive狀態(tài)無法反應(yīng)應(yīng)用層狀態(tài)問題,如進(jìn)程阻塞、死鎖、TCP緩沖區(qū)滿等情況。
并且要注意心跳的頻率,頻率小則可能及時感知不到應(yīng)用情況,頻率大可能有一定的性能開銷。
參考資料:《為何基于TCP協(xié)議的移動端IM仍然需要心跳保活機(jī)制?》、《徹底搞懂TCP協(xié)議層的KeepAlive保活機(jī)制》。
21.5MQ的使用場景?
IM消息是非常龐大的,比如說群聊相關(guān)業(yè)務(wù)、推送,對于一些業(yè)務(wù)上可以忍受的場景,盡量使用MQ來解耦和通信,來降低同步通訊的服務(wù)器壓力。
21.6群消息存一份還是多份,讀擴(kuò)散還是寫擴(kuò)散?
我的設(shè)計是存1份,讀擴(kuò)散。
存多份的話(也就是寫擴(kuò)散)下同一條消息存儲了很多次,對磁盤和帶寬造成了很大的浪費(fèi)。可以在架構(gòu)上和業(yè)務(wù)上進(jìn)行優(yōu)化,來實(shí)現(xiàn)讀擴(kuò)散。
當(dāng)然,對于IM是使用讀擴(kuò)散還是寫擴(kuò)散來實(shí)現(xiàn),這需要根據(jù)IM產(chǎn)品的業(yè)務(wù)定位來決定。比如微信就是寫擴(kuò)散(詳見《企業(yè)微信的IM架構(gòu)設(shè)計揭秘:消息模型、萬人群、已讀回執(zhí)、消息撤回等》),而釘釘卻是讀擴(kuò)散(詳見《深度解密釘釘即時消息服務(wù)DTIM的技術(shù)設(shè)計》)。
21.7消息ID為什么是趨勢遞增就可以,嚴(yán)格遞增的不行嗎?
嚴(yán)格遞增會有單點(diǎn)性能瓶頸,比如MySQL auto increments。
redis性能好但是沒有業(yè)務(wù)語義,比如缺少時間因素,還可能會有數(shù)據(jù)丟失的風(fēng)險,并且集群環(huán)境下寫入ID也屬于單點(diǎn),屬于集中式生成服務(wù)。
小型IM可以根據(jù)業(yè)務(wù)場景需求直接使用redis的incr命令來實(shí)現(xiàn)IM消息唯一ID。
本項目采用snowflake算法實(shí)現(xiàn)唯一趨勢遞增ID,即可實(shí)現(xiàn)IM消息中,時序性,重復(fù)性以及查找功能。
關(guān)于消息ID的生成,可以參考下面的系列文章:
《微信的海量IM聊天消息序列號生成實(shí)踐(算法原理篇)》
《微信的海量IM聊天消息序列號生成實(shí)踐(容災(zāi)方案篇)》
《解密融云IM產(chǎn)品的聊天消息ID生成策略》
《深度解密美團(tuán)的分布式ID生成算法》
《開源分布式ID生成器UidGenerator的技術(shù)實(shí)現(xiàn)》
《深度解密滴滴的高性能ID生成器(Tinyid)》
21.8gate層為什么需要開兩個端口?
gate會接收客戶端的連接請求(被動),需要外網(wǎng)監(jiān)聽端口;entry會主動給logic發(fā)請求(主動);entry會接收服務(wù)端給它的通知請求(被動),需要內(nèi)網(wǎng)監(jiān)聽端口。一個端口對內(nèi),一個端口對外。
21.9用戶的路由信息,是維護(hù)在中央存儲的redis中,還是維護(hù)在每個msg層內(nèi)存中?
維護(hù)在每個msg層內(nèi)存中有狀態(tài):多級緩存避免和中間件多次交互,并發(fā)高。
維護(hù)在中央存儲的redis中,msg層無狀態(tài),redis壓力大,每次交互IO網(wǎng)絡(luò)請求大。
業(yè)務(wù)初期為了減少復(fù)雜度,可以維護(hù)在Redis中。
21.10網(wǎng)關(guān)層和服務(wù)層以及msg層和網(wǎng)關(guān)層請求模型具體是怎樣的?
網(wǎng)關(guān)層到服務(wù)層,只需要單向傳輸發(fā)請求,網(wǎng)關(guān)層不需要關(guān)心調(diào)用的結(jié)果。
而客戶端想要的ack或者notify請求是由SDK發(fā)送數(shù)據(jù)到網(wǎng)關(guān)層,SDK也不需要關(guān)心調(diào)用的結(jié)果,最后網(wǎng)關(guān)層只轉(zhuǎn)發(fā)數(shù)據(jù),不做額外的邏輯處理。
SDK和所有的網(wǎng)關(guān)進(jìn)行長連接,當(dāng)發(fā)送信息給客戶端時,根據(jù)路由尋址信息,即可通過長連接推送信息
21.11本地寫數(shù)據(jù)成功,一定代表對端應(yīng)用側(cè)接收讀取消息了嗎?
本地TCP寫操作成功,但數(shù)據(jù)可能還在本地寫緩沖區(qū)中、網(wǎng)絡(luò)鏈路設(shè)備中、對端讀緩沖區(qū)中,并不代表對端應(yīng)用讀取到了數(shù)據(jù)。
如果你還不理解,可以讀讀這篇文章《從客戶端的角度來談?wù)勔苿佣薎M的消息可靠性和送達(dá)機(jī)制》。
21.12為什么用netty做來做http網(wǎng)關(guān), 而不用tomcat?
主要是從以下方面考慮:
- 1)netty對象池,內(nèi)存池,高性能線程模型;
- 2)netty堆外內(nèi)存管理,減少GC壓力,jvm管理的只是一個很小的DirectByteBuffer對象引用;
- 3)tomcat讀取數(shù)據(jù)和寫入數(shù)據(jù)都需要從內(nèi)核態(tài)緩沖copy到用戶態(tài)的JVM中,多1次或者2次的拷貝會有性能影響。
21.13為什么消息入庫后,對于在線狀態(tài)的用戶,單聊直接推送,群聊通知客戶端來拉取,而不是直接推送消息給客戶端(推拉結(jié)合)?
在保證消息實(shí)時性的前提下,對于單聊,直接推送。
對于群聊,由于群聊人數(shù)多,推送的話一份群消息會對群內(nèi)所有的用戶都產(chǎn)生一份推送的消息,推送量巨大。
解決辦法是按需拉取,當(dāng)群消息有新消息時候發(fā)送時候,服務(wù)端主動推送新的消息數(shù)量,然后客戶端分頁按需拉取數(shù)據(jù)。
21.14為什么除了單聊、群聊、推送、離線拉取等實(shí)時性業(yè)務(wù),其他的業(yè)務(wù)都走h(yuǎn)ttp協(xié)議?
IM協(xié)議簡單最好,如果讓其他的業(yè)務(wù)請求混進(jìn)IM協(xié)議中,會讓其IM變的更復(fù)雜,比如查找離線消息記錄拉取走h(yuǎn)ttp通道避免tcp 通道壓力過大,影響即時消息下發(fā)效率。
在比如上傳圖片和大文件,可以利用HTTP的斷點(diǎn)上傳和分段上傳特性。
21.15機(jī)集群機(jī)器要考慮到哪些優(yōu)化?
主要有:
- 1)網(wǎng)絡(luò)寬帶;
- 2)最大文件句柄;
- 3)每個tcp的內(nèi)存占用;
- 4)Linux系統(tǒng)內(nèi)核tcp參數(shù)優(yōu)化配置;
- 5)網(wǎng)絡(luò)IO模型;
- 6)網(wǎng)絡(luò)網(wǎng)絡(luò)協(xié)議解析效率;
- 7)心跳頻率;
- 8)會話數(shù)據(jù)一致性保證;
- 9)服務(wù)集群動態(tài)擴(kuò)容縮容。
22、系列文章
《跟著源碼學(xué)IM(一):手把手教你用Netty實(shí)現(xiàn)心跳機(jī)制、斷線重連機(jī)制》
《跟著源碼學(xué)IM(二):自已開發(fā)IM很難?手把手教你擼一個Andriod版IM》
《跟著源碼學(xué)IM(三):基于Netty,從零開發(fā)一個IM服務(wù)端》
《跟著源碼學(xué)IM(四):拿起鍵盤就是干,教你徒手開發(fā)一套分布式IM系統(tǒng)》
《跟著源碼學(xué)IM(五):正確理解IM長連接、心跳及重連機(jī)制,并動手實(shí)現(xiàn)》
《跟著源碼學(xué)IM(六):手把手教你用Go快速搭建高性能、可擴(kuò)展的IM系統(tǒng)》
《跟著源碼學(xué)IM(七):手把手教你用WebSocket打造Web端IM聊天》
《跟著源碼學(xué)IM(八):萬字長文,手把手教你用Netty打造IM聊天》
《跟著源碼學(xué)IM(九):基于Netty實(shí)現(xiàn)一套分布式IM系統(tǒng)》
《跟著源碼學(xué)IM(十):基于Netty,搭建高性能IM集群(含技術(shù)思路+源碼)》
《跟著源碼學(xué)IM(十一):一套基于Netty的分布式高可用IM詳細(xì)設(shè)計與實(shí)現(xiàn)(有源碼)》(* 本文)
《SpringBoot集成開源IM框架MobileIMSDK,實(shí)現(xiàn)即時通訊IM聊天功能》
23、參考資料
[1] 史上最通俗Netty框架入門長文:基本介紹、環(huán)境搭建、動手實(shí)戰(zhàn)
[2] 強(qiáng)列建議將Protobuf作為你的即時通訊應(yīng)用數(shù)據(jù)傳輸格式
[3] IM通訊協(xié)議專題學(xué)習(xí)(一):Protobuf從入門到精通,一篇就夠!
[4] 微信新一代通信安全解決方案:基于TLS1.3的MMTLS詳解
[5] 探討組合加密算法在IM中的應(yīng)用
[6] 從客戶端的角度來談?wù)勔苿佣薎M的消息可靠性和送達(dá)機(jī)制
[7] IM消息送達(dá)保證機(jī)制實(shí)現(xiàn)(一):保證在線實(shí)時消息的可靠投遞
[8] 理解IM消息“可靠性”和“一致性”問題,以及解決方案探討
[9] 融云技術(shù)分享:全面揭秘億級IM消息的可靠投遞機(jī)制
[10] IM群聊消息如此復(fù)雜,如何保證不丟不重?
[11] 零基礎(chǔ)IM開發(fā)入門(四):什么是IM系統(tǒng)的消息時序一致性?
[12] 一套億級用戶的IM架構(gòu)技術(shù)干貨(下篇):可靠性、有序性、弱網(wǎng)優(yōu)化等
[13] 如何保證IM實(shí)時消息的“時序性”與“一致性”?
[14] 阿里IM技術(shù)分享(六):閑魚億級IM消息系統(tǒng)的離線推送到達(dá)率優(yōu)化
[15] 微信的海量IM聊天消息序列號生成實(shí)踐(算法原理篇)
[16] 社交軟件紅包技術(shù)解密(一):全面解密QQ紅包技術(shù)方案——架構(gòu)、技術(shù)實(shí)現(xiàn)等
[17] 網(wǎng)易云信技術(shù)分享:IM中的萬人群聊技術(shù)方案實(shí)踐總結(jié)
[18] 企業(yè)微信的IM架構(gòu)設(shè)計揭秘:消息模型、萬人群、已讀回執(zhí)、消息撤回等
[19] 融云IM技術(shù)分享:萬人群聊消息投遞方案的思考和實(shí)踐
[20] 為何基于TCP協(xié)議的移動端IM仍然需要心跳保活機(jī)制?
[21] 一文讀懂即時通訊應(yīng)用中的網(wǎng)絡(luò)心跳包機(jī)制:作用、原理、實(shí)現(xiàn)思路等
[22] 微信團(tuán)隊原創(chuàng)分享:Android版微信后臺保活實(shí)戰(zhàn)分享(網(wǎng)絡(luò)保活篇)
[23] 融云技術(shù)分享:融云安卓端IM產(chǎn)品的網(wǎng)絡(luò)鏈路保活技術(shù)實(shí)踐
[24] 阿里IM技術(shù)分享(九):深度揭密RocketMQ在釘釘IM系統(tǒng)中的應(yīng)用實(shí)踐
[25] 徹底搞懂TCP協(xié)議層的KeepAlive保活機(jī)制
[26] 深度解密釘釘即時消息服務(wù)DTIM的技術(shù)設(shè)計
(本文已同步發(fā)布于:http://www.52im.net/thread-4257-1-1.html)