分享利用 redis 訂閱與發布特性,巧妙的現實高性能im系統。為表誠意,先貼源碼地址:https://github.com/2881099/im
下載源碼后的運行方法:
運行環境:.NETCore 2.1 + redis-server 2.8
下載Redis-x64-2.8.2402.zip,點擊 start.bat 運行;或者修改 imServer、web 下面 appsettings.json redis 配置,指向可用的redis-server
cd imServer && dotnet run --urls="http://0.0.0.0:6001"
cd web && dotnet run --urls="http://0.0.0.0:5555"
打開多個瀏覽器,訪問 http://127.0.0.1:5555 發送群消息
設計思路
終端(如瀏覽器) 使用 webSocket 連接 imServer;
imServer 根據 clientId 分區管理 webSocket 連接,可群集部署;
webApi 或其他應用端,使用 ImHelper 調用相關方法(如:SendMessage、群聊相關方法),將數據推至 Redis Channel;
imServer 訂閱 Redis Channel,收到消息后向終端(如瀏覽器)推送消息;
1、可緩解並發推送消息過多的問題;
2、可解決連接數過多的問題;
3、解決業務和通訊分離,結構更加清淅;
imServer 充當消息轉發,維護連接,代碼萬年不變不需要重啟維護
webApi 負責所有業務
webSocket
如果瀏覽器使用 webSocket ,iOS 使用其他協議,協議不一致的后果很嚴重(難維護)。
建議所有端都使用 webSocket 協議,adorid/ios/h5/小程序 全部支持 webSocket 客戶端。
業務通訊
IM 系統一般涉及【我的好友】、【我的群】、【歷史消息】等等。。
那么,imServer與業務方(webApi)該保持何種關系呢?
用戶A向好友B發送消息,分析一下:
- 需要判斷B是否為A好友;
- 需要判斷A是否有權限;
- 等等。。
諸如此類業務判斷會很復雜,如果使用imServer做業務協議,它是不是會變成巨無霸難以維護?
又如獲取歷史聊天記錄,難道客戶端要先webSocket.send('gethistory'),再在onmessage里定位回調處理?
發送消息
業務和推送分離的設計,即 imServer 只負責推送工作,webApi 負責業務。
用戶A向B發消息:終端A ajax -> webApi -> imServer -> 終端B webSocket.onmessage;
獲取歷史消息:客戶端請求業務方(webApi)接口,返回json(歷史消息)。
背后采用 redis 輕量級的訂閱發布功能,實現消息緩沖發送,方案必備之一,后期可更換為其他技術。比如 webApi 業務發需要通知1000個人,若不用消息緩沖,會對 webApi 應用程序整體將造成性能損耗。
還有使用 redis 存儲一些數據,如在線 clientId,頻道信息。
集群分區
單個 imServer 實例支持多少個客戶端連接,兩千個沒問題?如果在線用戶有10萬人,怎么辦???
部署 4 個 imServer:
imServer1 訂閱 redisChanne1
imServer2 訂閱 redisChanne2
imServer3 訂閱 redisChanne3
imServer4 訂閱 redisChanne4
業務方(webApi) 根據接收方的 clientId 后四位 16 進制與節點總數取模,定位到對應的 redisChannel,進行 redis->publish 操作將消息定位到相應的 imServer。
每個 imServer 管理着對應的終端連接,當接收到 redis 訂閱消息后,向對應的終端連接推送數據。
事件消息
IM 系統比較常用的有上線、下線,在 imServer 層才能准確捕捉事件,但業務代碼不合適在這上面編寫了。
此時采用 redis 發布訂閱技術,將上線、下線等事件向指定頻道發布,業務方(webApi) 通過 ImHelper.EventBus 方法進行訂閱捕捉。
A向B發文件的例子
1、A向 webapi 傳文件
2、webapi 告訴 imServer,A向B正在傳文件,ImHelper.SendMessage(B, "A正在給傳送文件...")
3、B收到消息,A正在傳文件
4、webapi 文件接收完成時告訴imServer,A向B文件傳輸完畢,ImHelper.SendMessage(B, "A文件傳輸完畢(含文件鏈接)")
5、B收到消息,A文件傳輸完畢(含文件鏈接)