posted @ 2022-08-15 12:32 Jack Jiang 閱讀(259) | 評論 (0) | 編輯 收藏
posted @ 2022-08-09 12:11 Jack Jiang 閱讀(125) | 評論 (0) | 編輯 收藏
一、關于RainbowChat-Web
RainbowChat-Web是一套Web網頁端IM系統,是RainbowChat的姊妹產品(RainbowChat是一套基于開源IM聊天框架 MobileIMSDK(Github地址) 的產品級移動端IM系統)。
不同于市面上某些開源或淘寶售賣的demo級代碼,RainbowChat-Web的產品級代碼演化自真正運營過的商業產品,其所依賴的通信層核心SDK(即MobileIMSDK-Web)已在數年內經過大量客戶及其輻射的最終用戶的使用和驗證。
二、v4.1 版更新內容
此版更新內容(更多歷史更新日志):
- 1)[bug][前端]解決了掉線后發出的消息,在被判定未送達的情況下,重連成功時會再次重發的問題(這是MobileIMSDK-Web的bug);
- 2)[優化][前端]解決了發送的html等內容,對方顯示正常,而自已這邊顯示不正常的問題(沒被轉義);
- 3)[優化][服務端-獨立交付版]解決了log4j2的兩個jar包沖突導致在linux下不能正常輸出log的問題;
- 4)[優化][服務端-RainbowChatMQserver]優化了使用mysql8.0驅動時,不能正確讀取SQL異常信息的問題(會報空指針異常);
- 5)[優化][前端]解決了位置消息發送功能無法正常使用的問題(高德地圖官方API升級,已適配并升級完成);
- 6)[優化][前端]解決了位置消息查看時的地圖控制工具不正常的問題(高德地圖官方API升級,已適配并升級完成)。
升級后的位置消息相關功能截圖(更多截圖點此查看):

三、關于兼容性
截止目前:RainbowChat-Web努力保證在各主流系統、主流瀏覽器、不同分辨率屏幕上的一致體驗,包括但不限于:Chrome、Safari、FireFox、Edge、360瀏覽器、世界之窗瀏覽器等▼
▲ 在各種主流瀏覽器上的運行情況(更多截圖點此進入、更多演示視頻點此進入)
▲ 超寬屏上的顯示情況(更多截圖點此進入、更多演示視頻點此進入)
▲ 不同系統、不同分辨率屏幕的真機運行情況(更多截圖點此進入、更多演示視頻點此進入)
四、主要界面截圖概覽
▲ 主界面(更多截圖點此進入、更多演示視頻點此進入)
▲ 主界面(聊天窗全屏時)(更多截圖點此進入、更多演示視頻點此進入)
▲ 主界面(聊天窗關閉時)(更多截圖點此進入、更多演示視頻點此進入)
posted @ 2022-08-06 12:14 Jack Jiang 閱讀(129) | 評論 (0) | 編輯 收藏
posted @ 2022-08-01 12:37 Jack Jiang 閱讀(138) | 評論 (0) | 編輯 收藏
本文由作者“大白菜”分享,有較多修訂和改動。注意:本系列是給IM初學者的文章,IM老油條們還望海涵,勿噴!
1、引言
前兩篇《編碼實踐篇(單聊功能)》、《編碼實踐篇(群聊功能)》分別實現了控制臺版本的IM單聊和群聊的功能。
通過前兩篇這兩個小案例來體驗的只是Netty在IM系統這種真實的開發實踐,但對比在真實的Netty應用開發當中,本系列的案例是非常的簡單的,主要目的其實是讓大家可以更好地了解其原理,從而寫出更高質量的 Netty 代碼。
不過,雖然 Netty 的性能很高,但是也不能保證隨意寫出來的項目就是性能很高的,所以本篇將主要講解幾個基于Netty的IM系統的優化實戰技術點。

學習交流:
- 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM》
- 開源IM框架源碼:https://github.com/JackJiang2011/MobileIMSDK(備用地址點此)
(本文同步發布于:http://www.52im.net/thread-3988-1-1.html)
2、寫在前面
建議你在閱讀本文之前,務必先讀本系列的前三篇《IM系統設計篇》、《編碼實踐篇(單聊功能)》、《編碼實踐篇(群聊功能)》。
最后,在開始本文之前,請您務必提前了解Netty的相關基礎知識,可從本系列首篇《IM系統設計篇》中的“知識準備”一章開始。
3、系列文章
本文是系列文章的第3篇,以下是系列目錄:
- 《基于Netty,從零開發IM(一):IM系統設計篇》
- 《基于Netty,從零開發IM(二):編碼實踐篇(單聊功能)》
- 《基于Netty,從零開發IM(三):編碼實踐篇(群聊功能)》
- 《基于Netty,從零開發IM(四):編碼實踐篇(系統優化)》(* 本文)
4、基于Netty的IM系統常見優化方向
常見優化方向腦圖:

我們逐條詳細解釋一下這些優化的目的:
- 1)心跳檢測:主要是避免連接假死現象;
- 2)連接斷開:則刪除通道綁定屬性、刪除對應的映射關系,這些信息都是保存在內存當中的,如果不刪除則造成資源浪費;
- 3)性能問題:用戶 ID 和 Channel 的關系綁定存在內存當中,比如:Map,key 是用戶 ID,value 是 Channel,如果用戶量多的情況(客戶端數量過多),那么服務端的內存將被消耗殆盡;
- 4)性能問題:每次服務端往客戶端推送消息,都需從Map里查找到對應的Channel,如果數量較大和查詢頻繁的情況下如何保證查詢性能;
- 5)安全問題:HashMap 是線程不安全的,并發情況下,我們如何去保證線程安全;
- 6)身份校驗:如何 LoginHandler 是負責登錄認證的業務 Handler,AuthHandler 是負責每次請求時校驗該請求是否已經認證了,這些 Handler 在鏈接就緒時已經被添加到 Pipeline 管道當中,其實,我們可以采用熱插拔的方式去把一些在做業務操作時用不到的 Handler 給剔除掉。
以上是基于Netty的IM系統開發當中,需要去注意的技術優化點,當然還有很多其他的細節,比如:線程池這塊,需要大家慢慢去從實戰中積累。
5、本篇優化方向
本篇主要的優化內容主要是在第二篇單聊功能和第三篇群聊功能的基礎上繼續完善幾點。
具體的優化方向如下:
- 1)無論客戶端還是服務端都分別只有一個 Handler,這樣的話,業務越來越多,Handler 里面的代碼就會越來越臃腫,我們應該想辦法把 Handler 拆分成各個獨立的 Handler;
- 2)如果拆分的 Handler 很多,每次有連接進來,那么都會觸發 initChannel () 方法,所有的 Handler 都得被 new 一遍,我們應該把這些 Handler 改成單例模式(不需要每次都 new,提高效率);
- 3)發送消息時,無論是單聊還是群聊,對方不在線,則把消息緩存起來,等待其上線再推送給他;
- 4)連接斷開時,無論是主動和被動,需要刪除 Channel 屬性、刪除用戶和 Channel 映射關系。
6、業務拆分以及單例模式優化
6.1 概述
主要優化細節如下:
- 1)自定義 Handler 繼承 SimpleChannelInboundHandler,那么解碼的時候,會自動根據數據格式類型轉到相應的 Handler 去處理;
- 2)@Shareable 修飾 Handler,保證 Handler 是可共享的,避免每次都創建一個實例。
6.2 登錄Handler優化
@ChannelHandler.Sharable
public class ClientLogin2Handler extends SimpleChannelInboundHandler<LoginResBean> {
//1.構造函數私有化,避免創建實體
private ClientLogin2Handler(){}
//2.定義一個靜態全局變量
public static ClientLogin2Handler instance=null;
//3.獲取實體方法
public static ClientLogin2Handler getInstance(){
if(instance==null){
synchronized(ClientLogin2Handler.class){
if(instance==null){
instance=new ClientLogin2Handler();
}
}
}
return instance;
}
protected void channelRead0(
ChannelHandlerContext channelHandlerContext,
LoginResBean loginResBean) throws Exception {
//具體業務代碼,參考之前
}
}
6.3 消息發送Handler優化
@ChannelHandler.Sharable
public class ClientMsgHandler extends SimpleChannelInboundHandler<MsgResBean> {
//1.構造函數私有化,避免創建實體
private ClientMsgHandler(){}
//2.定義一個靜態全局變量
public static ClientMsgHandler instance=null;
//3.獲取實體方法
public static ClientMsgHandler getInstance(){
if(instance==null){
synchronized(ClientMsgHandler.class){
if(instance==null){
instance=new ClientMsgHandler();
}
}
}
return instance;
}
protected void channelRead0(
ChannelHandlerContext channelHandlerContext,
MsgResBean msgResBean) throws Exception {
//具體業務代碼,參考之前
}
}
6.4 initChannel方法優化
.handler(newChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) {
//1.拆包器
ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,5,4));
//2.解碼器
ch.pipeline().addLast(new MyDecoder());
//3.登錄Handler,使用單例獲取
ch.pipeline().addLast(ClientLogin2Handler.getInstance());
//4.消息發送Handler,使用單例獲取
ch.pipeline().addLast(ClientMsgHandler.getInstance());
//5.編碼器
ch.pipeline().addLast(new MyEncoder());
}
});
6.5 小結
這種業務拆分以及單例模式優優化是Netty開發當中很常用的,可以更好的維護基于Netty的代碼并提高應用性能。
7、數據緩存優化
為了提高用戶體驗,在發送消息(推送消息)時,如果接收方不在線,則應該把消息緩存起來,等對方上線時,再推送給他。
7.1 數據緩存到集合
//1.定義一個集合存放數據(真實項目可以存放數據庫或者redis緩存),這樣數據比較安全。
private List<Map<Integer,String>> datas=new ArrayList<Map<Integer,String>>();
//2.服務端推送消息
private void pushMsg(MsgReqBean bean,Channel channel){
Integer touserid=bean.getTouserid();
Channel c=map.get(touserid);
if(c==null){//對方不在線
//2.1存放到list集合
Map<Integer,String> data=new HashMap<Integer, String>();
data.put(touserid,bean.getMsg());
datas.add(data);
//2.2.給消息“發送人”響應
MsgResBean res=new MsgResBean();
res.setStatus(1);
res.setMsg(touserid+">>>不在線");
channel.writeAndFlush(res);
}else{//對方在線
//2.3.給消息“發送人”響應
MsgResBean res=new MsgResBean();
res.setStatus(0);
res.setMsg("發送成功);
channel.writeAndFlush(res);
//2.4.給接收人推送消息
MsgRecBean res=new MsgRecBean();
res.setFromuserid(bean.getFromuserid());
res.setMsg(bean.getMsg());
c.writeAndFlush(res);
}
}
7.2 上線推送
private void login(LoginReqBean bean, Channel channel){
Channel c=map.get(bean.getUserid());
LoginResBean res=new LoginResBean();
if(c==null){
//1.添加到map
map.put(bean.getUserid(),channel);
//2.給通道賦值
channel.attr(AttributeKey.valueOf("userid")).set(bean.getUserid());
//3.登錄響應
res.setStatus(0);
res.setMsg("登錄成功");
res.setUserid(bean.getUserid());
channel.writeAndFlush(res);
//4.根據user查找是否有尚未推送消息
//思路:根據userid去lists查找.......
}else{
res.setStatus(1);
res.setMsg("該賬戶目前在線");
channel.writeAndFlush(res);
}
}
8、連接斷開事件處理優化
如果客戶端網絡故障導致連接斷開了(非主動下線),那么服務端就應該能監聽到連接的斷開,且此時應刪除對應的 map 映射關系。但是映射關系如果沒有刪除掉,將導致服務器資源沒有得到釋放,進而影響客戶端的下次同一個賬號登錄以及大量的客戶端掉線時性能。
8.1 正確寫法
實例:
public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {
//映射關系
private static Map<Integer, Channel> map=new HashMap<Integer, Channel>();
//連接斷開,觸發該事件
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
//1.獲取Channel
Channel channel=ctx.channel();
//2.從map里面,根據Channel找到對應的userid
Integer userid=null;
for(Map.Entry<Integer, Channel> entry : map.entrySet()){
Integer uid=entry.getKey();
Channel c=entry.getValue();
if(c==channel){
userid=uid;
}
}
//3.如果userid不為空,則需要做以下處理
if(userid!=null){
//3.1.刪除映射
map.remove(userid);
//3.2.移除標識
ctx.channel().attr(AttributeKey.valueOf("userid")).remove();
}
}
}
8.2 錯誤寫法
Channel 斷開,服務端監聽到連接斷開事件,但是此時 Channel 所綁定的屬性已經被移除掉了,因此這里無法直接獲取的到 userid。
實例:
public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {
//映射關系
private static Map<Integer, Channel> map=new HashMap<Integer, Channel>();
//連接斷開,觸發該事件
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
//1.獲取Channel綁定的userid
Object userid=channel.attr(AttributeKey.valueOf("userid")).get();
//2.如果userid不為空
if(userid!=null){
//1.刪除映射
map.remove(userid);
//2.移除標識
ctx.channel().attr(AttributeKey.valueOf("userid")).remove();
}
}
}
9、本篇小結
本篇內容還是相對容易理解的,主要是優化前面兩篇實現的IM聊天功能,優化內容是業務 Handler 的拆分以及使用單例模式、接受人不在線則緩存數據、等其上線再推送、監聽連接斷開刪除對應的映射關系。
限于篇幅,本系列文章文章沒辦法真正講解開發一個完整IM系統所涉及的方方面面,如果有興趣,可以繼續閱讀更有針對性的IM開發文章,比如IM架構設計、IM通信協議、IM通信安全、群聊優化、弱網優化、網絡保活等。
10、參考資料
[1] 新手入門:目前為止最透徹的的Netty高性能原理和框架架構解析
[3] 淺談IM系統的架構設計
[4] 簡述移動端IM開發的那些坑:架構設計、通信協議和客戶端
[5] 一套海量在線用戶的移動端IM架構設計實踐分享(含詳細圖文)
[7] 一套高可用、易伸縮、高并發的IM群聊、單聊架構方案設計實踐
[8] 一套億級用戶的IM架構技術干貨(上篇):整體架構、服務拆分等
[9] 從新手到專家:如何設計一套億級消息量的分布式IM系統
[10] 基于實踐:一套百萬消息量小規模IM系統技術要點總結
[11] 探探的IM長連接技術實踐:技術選型、架構設計、性能優化
[14] 基于Netty實現一套分布式IM系統
[15] SpringBoot集成開源IM框架MobileIMSDK,實現即時通訊IM聊天功能
(本文同步發布于:http://www.52im.net/thread-3988-1-1.html)
posted @ 2022-07-25 12:02 Jack Jiang 閱讀(138) | 評論 (0) | 編輯 收藏
一、更新內容簡介
本次更新為次要版本更新,進行了若干優化(更新歷史詳見:碼云 Release Nodes)。可能是市面上唯一同時支持 UDP+TCP+WebSocket 三種協議的同類開源IM框架。
二、MobileIMSDK簡介
MobileIMSDK 是一套專為移動端開發的原創IM通信層框架:
- 歷經8年、久經考驗;
- 超輕量級、高度提煉,lib包50KB以內;
- 精心封裝,一套API同時支持UDP、TCP、WebSocket三種協議(可能是全網唯一開源的);
- 客戶端支持 iOS、Android、標準Java、H5、小程序(開發中..)、Uniapp(開發中..);
- 服務端基于Netty,性能卓越、易于擴展;
- 可與姊妹工程 MobileIMSDK-Web 無縫互通實現網頁端聊天或推送等;
- 可應用于跨設備、跨網絡的聊天APP、企業OA、消息推送等各種場景。
MobileIMSDK工程始于2013年10月,起初用作某產品的即時通訊底層實現,完全從零開發,技術自主可控!
您可能需要:查看關于MobileIMSDK的詳細介紹。
三、代碼托管同步更新
OsChina.net
GitHub.com
四、MobileIMSDK設計目標
讓開發者專注于應用邏輯的開發,底層復雜的即時通訊算法交由SDK開發人員,從而解偶即時通訊應用開發的復雜性。
五、MobileIMSDK框架組成
整套MobileIMSDK框架由以下5部分組成:
- Android客戶端SDK:用于Android版即時通訊客戶端,支持Android 2.3及以上,查看API文檔;
- iOS客戶端SDK:用于開發iOS版即時通訊客戶端,支持iOS 8.0及以上,查看API文檔;
- Java客戶端SDK:用于開發跨平臺的PC端即時通訊客戶端,支持Java 1.6及以上,查看API文檔;
- H5客戶端SDK:暫無開源版,查看精編注釋版;
- 服務端SDK:用于開發即時通訊服務端,支持Java 1.7及以上版本,查看API文檔。
整套MobileIMSDK框架的架構組成:
另外:MobileIMSDK可與姊妹工程 MobileIMSDK-Web 無縫互通,從而實現Web網頁端聊天或推送等。
六、MobileIMSDK v6.2更新內容
【重要說明】:
MobileIMSDK v6.2 為次要版本,進行了若干優化! 查看詳情
【新增的特性】:
- [服務端] 新增兩個聊天消息前置處理回調,方便開發者進行內容鑒黃、過濾、修改等運營管理;
- [服務端] 新增新增了一個與 Web 互通情況下的 C2C 模式回調,用于開發者在互通模式下實現離線消息 Push 邏輯;
【其它優化和提升】:
- [Andriod] 支持最新的 Andriod 12,解決了 Demo 工程中的 Andriod12 兼容問題;
- [Andriod] 解決了 Demo 工程在最新 Android Studio 編譯時報方法數超過 65535 的經典問題;
- [服務端] 升級 log4j2 至 2.17.0,解決 Log4j2 遠程代碼執行高危漏洞;
- [服務端] 為 ServerEventListener 類中的 onUserLogout 回調增加 beKickoutCode 參數;
- [服務端] [優化] 嘗試解決與 Web 互通情況下,MQProvider 中的 work 方法會因異步消息導致的 AlreadCloseException 問題;
【版本地址】:
posted @ 2022-07-20 10:29 Jack Jiang 閱讀(104) | 評論 (0) | 編輯 收藏
posted @ 2022-07-18 15:06 Jack Jiang 閱讀(115) | 評論 (0) | 編輯 收藏
posted @ 2022-07-11 11:39 Jack Jiang 閱讀(109) | 評論 (0) | 編輯 收藏
本文收作者“大白菜”分享,有改動。注意:本系列是給IM初學者的文章,IM老油條們還望海涵,勿噴!
1、引言
這又是一篇基于Netty的IM編碼實踐文章,因為合成一篇內容太長,讀起來太累,所以也就順著作者的思路分開成4篇,讀起來心理壓力也就沒那么大了。
這個系列的幾篇文章分享的是:假設在沒有任何成型的第3方IM庫或SDK的情況下,以網絡編程的基礎技術視野,思考和實踐如何基于Netty網絡庫從零寫一個可以聊天的IM系統的過程,沒有眼花繚亂的架構設計、也沒有高端大氣的模式設計方法論,有的只是從IM入門者的角度的思路和實戰,適合IM初學者閱讀。
本篇主要是徒手擼IM系列的開篇,主要講解的是的IM設計思路,不涉及實踐編碼,希望給你帶來幫助。

學習交流:
- 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM》
- 開源IM框架源碼:https://github.com/JackJiang2011/MobileIMSDK(備用地址點此)
(本文已同步發布于:http://www.52im.net/thread-3963-1-1.html)
2、知識準備
* 重要提示:本系列文章主要是代碼實戰分享,如果你對即時通訊(IM)技術理論了解的不多,建議先詳細閱讀:《零基礎IM開發入門:什么是IM系統?》、《新手入門一篇就夠:從零開發移動端IM》。
不知道 Netty 是什么?這里簡單介紹下:
Netty 是一個 Java 開源框架。Netty 提供異步的、事件驅動的網絡應用程序框架和工具,用以快速開發高性能、高可靠性的網絡服務器和客戶端程序。
也就是說,Netty 是一個基于 NIO 的客戶、服務器端編程框架,使用Netty 可以確保你快速和簡單的開發出一個網絡應用,例如實現了某種協議的客戶,服務端應用。
Netty 相當簡化和流線化了網絡應用的編程開發過程,例如,TCP 和 UDP 的 Socket 服務開發。
Netty的基礎入門好文章:
- 1)新手入門:目前為止最透徹的的Netty高性能原理和框架架構解析
- 2)寫給初學者:Java高性能NIO框架Netty的學習方法和進階策略
- 3)史上最通俗Netty框架入門長文:基本介紹、環境搭建、動手實戰
如果你連Java的NIO都不知道是什么,下面的文章建議優先讀:
Netty源碼和API的在線查閱地址:
3、系列文章
本文是系列文章的第1篇,以下是系列目錄:
- 《基于Netty,徒手擼IM(一):IM系統設計篇》(* 本文)
- 《基于Netty,徒手擼IM(二):編碼實踐篇(單聊功能)》
- 《基于Netty,徒手擼IM(三):編碼實踐篇(群聊功能)》
- 《基于Netty,徒手擼IM(一):編碼實踐篇(系統優化)》
4、需求分析
業務場景: 本次實戰就是模擬微信的IM聊天,每個客戶端和服務端建立連接,并且可以實現點對點通信(單聊),點對多點通信(群聊)。
設計思路: 我們要實現的是點(客戶端)對點(客戶端)的通訊,但是我們大部分情況下接觸的業務都是客戶端和服務端之間的通訊(所謂的C/S模式?),客戶端只需要知道服務端的 IP 地址和端口號即可發起通訊了。那么客戶端和客戶端應該怎么去設計呢?
技術思考:難道是手機和手機之間建立通訊連接(所謂的P2P),互相發送消息嗎?
這種方案顯然不是很好的方案:
- 1)首先: 客戶端和客戶端之間通訊,首先需要確定對方的 IP 地址和端口號,顯然不是很現實;
- 2)其次: 即使有辦法拿到對方的 IP 地址和端口號,那么每個點(客戶端)既作為服務端還得作為客戶端,無形之中增加了客戶端的壓力。
其實:我們可以使用服務端作為IM聊天消息的中轉站,由服務端主動往指定客戶端推送消息。如果是這種模式的話,那么 Http 協議是無法支持的(因為Http 是無狀態的,只能一請求一響應的模式),于是就只能使用 TCP 協議去實現了。
Jack Jiang注:此處作者表述不太準確,因為雖然HTTP是無狀態的,但一樣可以實現即時通訊能力,有興趣的讀者可以閱讀以下幾篇文章,了解一下這些曾經利用HTTP實現即時通訊聊天的技術方法:
- 《新手入門貼:史上最全Web端即時通訊技術原理詳解》
- 《Web端即時通訊技術盤點:短輪詢、Comet、Websocket、SSE》
- 《網頁端IM通信技術快速入門:短輪詢、長輪詢、SSE、WebSocket》
5、IM單聊思路設計
5.1 通訊架構原理
以下是通訊架構原理圖:

如上圖所示,通訊流程解析如下:
- 1)實現客戶端和客戶端之間通訊,那么需要使用服務端作為通訊的中轉站,每個客戶端都必須和服務端建立連接;
- 2)每個客戶端和服務端建立連接之后,服務端保存用戶 ID 和通道的映射關系,其中用戶 ID 作為客戶端的唯一標識;
- 3)客戶端 A 往客戶端 B 發送消息時,先把消息發送到服務端,再有服務端往客戶端 B 進行推送。
針對上述第“3)”點,服務端如何找到客戶端 B 呢?
客戶端 A 往服務端發送消息時,消息攜帶的信息有:“客戶端 A 用戶 ID”、“客戶端 B 用戶 ID”、“消息內容”。這樣服務端就能順利找到服務端 B 的通道并且進行推送消息了。
5.2 消息推送流程
每個客戶端和服務端建立連接的時候,必須把個人用戶信息上傳到服務端,由服務端統一保存映射關系。如果某個客戶端下線了,則服務端監聽到連接斷開,刪除對應的映射關系。
其次:發起群聊的時候,需要傳遞 touser 字段,服務端根據該字段在映射表里面查找到對應的連接通道并發起消息推送。
上述邏輯原理如下圖所示:

5.3 更多的細節
其實在真正要做IM之前,要考慮的技術細節還是很多的,以下這幾篇文章就步及到了典型的幾個IM熱門技術點,有興趣的一定要讀一讀:
- 《移動端IM開發需要面對的技術問題》
- 《談談移動端 IM 開發中登錄請求的優化》
- 《IM消息送達保證機制實現(一):保證在線實時消息的可靠投遞》
- 《IM消息送達保證機制實現(二):保證離線消息的可靠投遞》
- 《如何保證IM實時消息的“時序性”與“一致性”?》
6、IM群聊思路設計
群聊指的是一個組內多個用戶之間的聊天,一個用戶發到群組的消息會被組內任何一個成員接收 。
具體架構思路如下所示:

如上圖所示,群聊通訊流程解析如下。
1)群聊其實和單聊整體上思路都是一致的,都是需要保存每個用戶和通道的對應關系,方便后期通過用戶 ID 去查找到對應的通道,再跟進通道推送消息。
2)如何把消息發送給多個組內的成員呢?
其實很簡單,服務端再保存另外一份映射關系,那就是聊天室和成員的映射關系。發送消息時,首先根據聊天室 ID 找到對應的所有成員,然后再跟進各個成員的 ID 去查找到對應的通道,最后由每個通道進行消息的發送。
3)成員加入某個群聊組的時候,往映射表新增一條記錄,如果成員退群的時候則刪除對應的映射記錄。
通過上面的架構圖可以發現,群聊和單聊相比,其實就是多了一份映射關系而已。
其實群聊是IM里相對來說技術難度較高的功能,有興趣的讀者可以閱讀下面這幾篇:
- 《IM單聊和群聊中的在線狀態同步應該用“推”還是“拉”?》
- 《IM群聊消息如此復雜,如何保證不丟不重?》
- 《移動端IM中大規模群消息的推送如何保證效率、實時性?》
- 《現代IM系統中聊天消息的同步和存儲方案探討》
- 《關于IM即時通訊群聊消息的亂序問題討論》
- 《IM群聊消息的已讀回執功能該怎么實現?》
- 《IM群聊消息究竟是存1份(即擴散讀)還是存多份(即擴散寫)?》
- 《一套高可用、易伸縮、高并發的IM群聊、單聊架構方案設計實踐》
另外,對于超大規模群聊,技術難度更是指數上升:
- 《網易云信技術分享:IM中的萬人群聊技術方案實踐總結》
- 《阿里釘釘技術分享:企業級IM王者——釘釘在后端架構上的過人之處》
- 《IM群聊消息的已讀未讀功能在存儲空間方面的實現思路探討》
- 《企業微信的IM架構設計揭秘:消息模型、萬人群、已讀回執、消息撤回等》
- 《融云IM技術分享:萬人群聊消息投遞方案的思考和實踐》
- 《微信直播聊天室單房間1500萬在線的消息架構演進之路》
7、本文小結
本篇主要是幫助讀者掌握單聊和群聊的核心設計思路。
單聊: 主要是服務器保存了一份用戶和通道之間的映射關系,發送消息的時候,根據接收人 ID 找到其對應的通道 Channel,Channel 的 write () 可以給客戶端發送消息。
群聊: 保存兩份關系,分別是用戶 ID 和 Channel 之間的關系、群組 ID 和用戶 ID 的關系。推送消息的時候,首先根據聊天組 ID 找到其對應的成員,遍歷每個成員再進行找出其對應的通道即可。
整體來說,思路還是很簡單的,掌握了該設計思路以后,你會發現設計一款 IM 聊天軟件其實也不是很復雜。
8、相關文章
如果你覺得對本系列文章還不夠詳細,可以系統學習以下系列文章:
- 《跟著源碼學IM(一):手把手教你用Netty實現心跳機制、斷線重連機制》
- 《跟著源碼學IM(二):自已開發IM很難?手把手教你擼一個Andriod版IM》
- 《跟著源碼學IM(三):基于Netty,從零開發一個IM服務端》
- 《跟著源碼學IM(四):拿起鍵盤就是干,教你徒手開發一套分布式IM系統》
- 《跟著源碼學IM(五):正確理解IM長連接、心跳及重連機制,并動手實現》
- 《跟著源碼學IM(六):手把手教你用Go快速搭建高性能、可擴展的IM系統》
- 《跟著源碼學IM(七):手把手教你用WebSocket打造Web端IM聊天》
- 《跟著源碼學IM(八):萬字長文,手把手教你用Netty打造IM聊天》
- 《跟著源碼學IM(九):基于Netty實現一套分布式IM系統》
- 《跟著源碼學IM(十):基于Netty,搭建高性能IM集群(含技術思路+源碼)》
- 《SpringBoot集成開源IM框架MobileIMSDK,實現即時通訊IM聊天功能》
9、參考資料
[1] 新手入門:目前為止最透徹的的Netty高性能原理和框架架構解析
[3] 淺談IM系統的架構設計
[4] 簡述移動端IM開發的那些坑:架構設計、通信協議和客戶端
[5] 一套海量在線用戶的移動端IM架構設計實踐分享(含詳細圖文)
[7] 一套高可用、易伸縮、高并發的IM群聊、單聊架構方案設計實踐
[8] 一套億級用戶的IM架構技術干貨(上篇):整體架構、服務拆分等
[9] 一套億級用戶的IM架構技術干貨(下篇):可靠性、有序性、弱網優化等
[10] 從新手到專家:如何設計一套億級消息量的分布式IM系統
[11] 基于實踐:一套百萬消息量小規模IM系統技術要點總結
[12] 探探的IM長連接技術實踐:技術選型、架構設計、性能優化
(本文已同步發布于:http://www.52im.net/thread-3963-1-1.html)
posted @ 2022-07-04 18:38 Jack Jiang 閱讀(180) | 評論 (0) | 編輯 收藏
本文由作者jhon_11分享,有大量修訂和改動。
1、引言
如何設計一款高性能、高并發、高可用的im綜合消息平臺是很多公司發展過程中會碰到且必須要解決的問題。比如一家公司內部的通訊系統、各個互聯網平臺的客服咨詢系統,都是離不開一款好用且維護的方便im綜合消息系統。
那么,我們應該怎么樣來設計一款三高特性的im系統,并能同時支持各個業務線的接入(比如:內部OA通訊、客服咨詢、消息推送等等功能)有呢?

學習交流:
- 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM》
- 開源IM框架源碼:https://github.com/JackJiang2011/MobileIMSDK(備用地址點此)
(本文已同步發布于:http://www.52im.net/thread-3954-1-1.html)
2、初版IM架構
2.1 概述
im第一版設計的初衷是公司需要一款im消息中間件用于支撐客服咨詢業務。
但是,考慮到為了方便日后其他業務線也能接入消息溝通平臺,所以一開始就將整個消息中心的能力需求給到中間件團隊進行開發,以便除客服外的各業務線接入綜合消息中心,從而實現多元的消息實時觸達能力。
2.2 初版架構介紹
初版架構圖如下圖所示:

針對上面的架構圖,我們逐個解釋一下各模塊的作用。
1)存儲端:
在初版的架構下,存儲端我們使用tidb、redis作為主要存儲:
- [1] redis用于存儲消息已讀未讀,緩存連接信息等功能;
- [2] tidb作為開源的分布式數據庫,選擇它是為了方便消息的存儲。
2)mq消息總線:
我們使用rocketmq來實現消息總線(PS:即分布式情況下,不同im實例間通過MQ進行消息交互)。
消息總線是整個im的核心,使用rocketmq能支持十萬級別的tps。基本所有服務都要從消息總線中消費消息進行業務處理。
3)zookeeper注冊中心:各個服務會注冊到zk中,方便服務之間內部進行調用,同樣也可以暴露服務給外部進行調用。
4)link服務:
link服務主要用于接收客戶端的ws(WebSocket協議)、tcp、udp等協議的連接。
同時調用用戶服務進行認證,并投遞連接成功的消息給位置服務進行消費,存儲連接信息。
ws(WebSocket協議)過來的消息先到link再投遞到消息總線。
5)消息分發服務:
消息分發服務主要用于接收消息總線推過來的消息進行處理,按照im內部消息協議構造好消息體后,又推送到消息總線中(比如會推給會話服務、消息盒子、link服務)。
6)位置服務:
存儲link的(WebSocket協議)連接、tcp連接等信息,并使用redis進行緩存(key為userId),方便根據UserId查詢到該用戶所登錄的客戶端連接在哪個link上。
一個用戶在相同設備只能登錄一個,但可以支持多端登錄。
7)用戶服務:用于存儲所有用戶,提供認證查詢接口。
8)消息盒子:存儲所有消息,提供消息查詢、消息已讀未讀、消息未讀數、消息檢索等功能。
9)會話服務:管理會話、群聊會話、單聊會話等功能。
2.3 整體時序圖
整體架構的時序圖如下:

3、初版IM架構存在的問題及思考
在上節的架構設計介紹中,我們詳細分享了初版IM系統架構的設計思路以及具體流程。
那么在初版IM架構設計中還存在什么樣的問題,又該如何優化呢?我們一條條來看看。
3.1 使用MQ消息總線的問題
正如上節所分享的那樣,我們初版IM架構中,link服務到消息分發服務的消息使用的MQ消息總線。
初版架構設計中,link服務將消息下推給消息分發服務進行處理時,使用的是mq消息總線(通俗了說,IM集群內不同IM實例間的通信是依賴于MQ進行的消息傳遞),而mq消息總線必然做對有一定的時延(而且時延受制于MQ本身的系統實現和技術策略)。
舉個例子:
當兩個處于不同IM實例的客戶端A和B聊天時,A用戶發送消息到link --> 消息總線 --> 消息分發服務 --> 消息總線 --> link --> B用戶。
正如上面這個例子,im消息投遞流程太長了,并且這樣也會大大降低系統的吞吐量。
3.2 消息落庫為寫擴散的問題
其實現階段我們使用的是跟微信一樣的寫擴散策略(詳見《企業微信的IM架構設計揭秘:消息模型、萬人群、已讀回執、消息撤回等》)。
那么為啥微信使用寫擴散不是缺陷,而對于我們的IM架構來說確是缺陷呢?
微信的技術特性:
- 1)微信號稱沒有存儲用戶的聊天記錄,全是實時推送;
- 2)微信聊天記錄全部會在我們手機端存儲一份,兩臺手機終端上的聊天記錄并不互通,并且互不可見。
我們的IM綜合消息中心技術特性:
- 1)綜合消息中心是會有拉取歷史聊天記錄(服務端拉取)的功能,存儲了全量消息;
- 2)綜合消息中心的客戶端,需要支持網頁版本。
綜上所述:
- 1)寫擴散對微信這樣有移動端的富客戶端版本的即時通訊產品十分友好,每個消息在消息分發的時候給處于這個會話(單聊,群聊)下的所有用戶所在客戶端先推送消息,沒找到連接就針對這個用戶寫一個離線緩存消息,那么下次該用戶登錄進來,可以從緩存中拉取到該消息,并且清掉緩存;
- 2)寫擴散對于我們這類通用綜合消息平臺并不友好,由于接入方大部分是網頁版的客戶端,所以沒有緩存消息的能力,瀏覽器刷新就沒有了任何消息,所以需要實時去服務端拉取歷史消息。假設我是寫擴散,在一個群聊中有五百個用戶,針對這五百個用戶在這個會話,我需要去寫五百條消息,大大的增加了寫io,并且還不能寫緩存(得寫數據庫)。
3.3 tidb存在不穩定性和事務并發的問題
tidb是目前主流的開源分布式數據庫,查詢效率高、無需分庫分表。
但同樣的,tidb存在一些隱藏的問題:
- 1)tidb在高并發情況下,并發事務會導致事務失敗,具體原因不知;
- 2)tidb排錯成本高,公司很少有tidb專業運維,經常遇到不走索引的情況。
3.4 群聊、單聊冗余在同一個服務的問題
在我們初版的IM架構設計中,單聊和群聊是冗余在會話服務中的,并且冗余在同一張表的。
其實單聊、群聊從數據角度來說,還是會有些不同(比如業務屬性)雖然都是會話,我們還是需要將這兩個服務拆分開,細粒度的服務拆分能更好的把控整體的邏輯。
4、升級版IM架構
4.1 初始架構問題
正如前面兩節分享的那樣,漸漸的我們發現初版im架構有很大的不足之處。
在生產上暴露出了以下問題:
- 1)tps沒達到預期,吞吐量不能滿足公司業務的發展;
- 2)使用的存儲中間件難以維護(主要是tidb),試錯成本高,經常在生產暴露問題,并且速度越來越慢;
- 3)消息寫擴散沒有太大必要,并大大增加了系統io次數(原因見上一節);
- 4)一些特性無法支持,比如消息圖文檢索,消息已讀未讀。
4.2 升級版im架構介紹
本次升級后的im架構如下圖所示:

如上圖所示,改版后的各模塊情況如下:
- 1)存儲端:存儲端我們改用了mysql,針對消息服務單獨使用了主從mysql集群(主節點用于寫消息、從節點用于消息檢索)——;
- 2)mq消息總線:與第一版相比沒有改動;
- 3)link服務:與第一版相比,改動了link服務到消息分發服務的消息推送方式(由MQ總線方式變更為tcp實時推送);
- 4)消息分發服務:集成了消息處理能力、路由能力,每臺消息分發服務擁有所有link服務的tcp連接;
- 5)單聊服務:負責單聊會話的管理能力;
- 6)群聊服務:負責群聊會話的管理能力;
- 7)用戶服務:提供用戶認證,登錄\注冊能力。
5、詳細對比針對初版IM架構的改動
升級版的IM架構,對比初始初始,具體主要是下面這些改動。
5.1 改進了不同im實例間的消息分發方式
針對初版MQ消息總結的問題,升級版架構中,我們將link到消息分發服務改為tcp實時連接,百萬客戶端連接同一臺link機器,消息實時觸達能力tps達到16萬。
link到消息分發服務的改版是本次設計的亮點之一,完全消除了mq推送的時延性,并且路由簡單,幾乎實時觸達。
舉個例子:(當兩個處于不同IM實例的客戶端A和B聊天時)
- 1)初版架構中是:A用戶發送消息到link --> 消息總線 --> 消息分發服務 --> 消息總線 --> link --> B用戶;
- 2)升級版架構是:用戶A --> link --> 消息分發 --> link --> 用戶B。
而且:link服務到消息分發服務集群的消息推送使用輪詢負載均衡的方式,保證公平,不會導致個別機器負載過高。
5.2 取消了位置服務
取消了位置服務(這里的位置不是指的IM消息里的地理位置消息哦),消息分發服務集成位置服務的能力。
消息分發服務本身業務簡單,不需要再單獨劃分位置服務,因為會增加網絡io,并且消息分發服務直連link,而讓它負責路由則更加方便。
5.3 存儲由tidb改成了mysql
存儲端由tidb改成了mysql,增強了可維護性,消息服務使用mysql主從讀寫分離方式,提高了消息落庫速度與檢索速度的同時,也減輕數據庫壓力。
前面有提到過使用tidb這樣維護成本高,排查問題難的分布式數據庫是一件很痛苦的事情。
而我們使用mysql更加穩定,大家對mysql的學習成本相對較低。針對消息服務使用讀寫分離的方式,能大大提高消息的吞吐量。
5.4 實現了初版無法實現的特性功能
升級版架構中,我們實現了初版無法實現的特性功能,比如消息已讀未讀、紅包推送、商品鏈接推送等功能。
新版綜合消息中心加入了消息已讀未讀、發送紅包、鏈接推送等功能,但這些功能帶有一定的業務特性,畢竟不是所有Im都需要,可通過配置取消這些功能。
5.5 消息由寫擴散改為讀擴散
升級版IM架構中,消息存儲由寫擴散改為了讀擴散。
前面我們有提到寫擴散和讀擴散的利弊,對于網頁端IM我們更適合使用讀擴散,只需要落一條消息,大大提高消息服務的吞吐量.
5.6 增加了門面服務
升級版IM架構中,我們增加門面服務 im-logic,用于暴露給第三方業務線接口調用。
初版架構中,都是im的各個服務各自暴露接口給到外部進行調用, 而升級版架中我們統一使用logic服務暴露給外部調用。
在logic服務針對調用可以做一些處理,這樣不會影響到整體im的通用,不會增加im底層代碼的復雜度,從而將業務邏輯與底層進行解耦。
6、優化后的效果對比
針對升級版和初版IM架構,我們也做了一些對比測試,具體的測試過程就是詳細展開了。
以下是測試結果:

7、業務線接入im綜合消息系統的業務劃分思考
7.1 到底該如何設計高性能通用im綜合消息系統
關于業務線接入im綜合消息系統的業務劃分,我也做了一些總結和思考,為了更形象和易于理解,我這里以客服系統以及企業微信為例來進行分析。
假如我開發了一款通用的im綜合消息系統,現在有很多業務方需要接入我們,我們該如何進行業務域的清晰劃分就顯得尤為重要,需要在妥協與不妥協中進行平衡。
就像當前市面上開源的im消息平臺來說,存在的問題主要是:要么是集成了很多的業務邏輯,要么就只是一款單純的客服系統,再或者就是一款IM好友聊天系統,中間的業務劃分并不明確。當然,這也有好處,拿來就能用,并不需要進行二次業務封裝。
那么,到底如何將im設計為一款真正的高性能通用im綜合消息系統呢?
通用的綜合消息消息平臺只需要有通用的底層能力:

以下案例假設在我已經按照上述架構設計了一版im綜合消息中心。
7.2 以客服系統為例
客服系統:

客服系統不光需要實現自身業務,還需要整合im的消息能力(消費im的消息),來進行場景分析,實現會話變更、信令消息推送等邏輯。
客服系統內部需要根據im的底層支持能力進行相應的業務封裝以及客服系統的客服用戶池,c端用戶池如何初始化到im的用戶中心這些問題都是需要考慮進去的。
7.3 內部OA通信為例
內部OA通信:
員工內部OA通信系統需要集成IM好友功能,需要根據im的用戶中心封裝組織架構,用戶權限等功能。
同時,內部通信系統需要根據im實現消息已讀未讀,群聊列表,會話列表拉取等功能。
8、本文小結
im的綜合消息平臺是一款需要高度結合業務的中間件系統,它直接與業務打交道,跟普通的中間件有根本的區別。
一款好用的im綜合消息平臺,直接取決于你的通用性,可擴展性以及系統吞吐能力。
希望這篇文章所分享的內容,能對大家開發im時候的思路有所啟迪。
9、參考資料
[2] 從游擊隊到正規軍(一):馬蜂窩旅游網的IM系統架構演進之路
[3] 瓜子IM智能客服系統的數據架構設計(整理自現場演講,有配套PPT)
[4] 阿里釘釘技術分享:企業級IM王者——釘釘在后端架構上的過人之處
[8] 一套億級用戶的IM架構技術干貨(上篇):整體架構、服務拆分等
[9] 一套億級用戶的IM架構技術干貨(下篇):可靠性、有序性、弱網優化等
[10] 從新手到專家:如何設計一套億級消息量的分布式IM系統
[11] 企業微信的IM架構設計揭秘:消息模型、萬人群、已讀回執、消息撤回等
[12] 阿里IM技術分享(三):閑魚億級IM消息系統的架構演進之路
[13] 一套高可用、易伸縮、高并發的IM群聊、單聊架構方案設計實踐
(本文已同步發布于:http://www.52im.net/thread-3954-1-1.html)
posted @ 2022-06-28 10:40 Jack Jiang 閱讀(144) | 評論 (0) | 編輯 收藏