天天看點

[開源精品] C#.NET im 聊天通訊架構設計 -- FreeIM 支援叢集、職責分明、高性能

FreeIM 使用 websocket 協定實作簡易、高性能(單機支援5萬+連接配接)、叢集即時通訊元件,支援點對點通訊、群聊通訊、上線下線事件消息等衆多實用性功能。使用場景:好友聊天、群聊天、直播間、實時評論區、遊戲。FreeIM 解耦了通訊與業務子產品,讓項目架構變得更加簡單易維護。解決了業務與通訊的職責沖突,簡化了架構,降低了維護成本。經曆 1年半的生産環境,整理代碼于 2018 年開源。

💻 FreeIM 是什麼?

FreeIM 使用 websocket 協定實作簡易、高性能(單機支援5萬+連接配接)、叢集即時通訊元件,支援點對點通訊、群聊通訊、上線下線事件消息等衆多實用性功能。

ImCore

已正式改名為

FreeIM

使用場景:好友聊天、群聊天、直播間、實時評論區、遊戲。

FreeIM 解耦了通訊與業務子產品,讓項目架構變得更加簡單易維護,2017年的設計再過5年也不過時。

FreeIM 提供了一套永遠不需要疊代更新的

ImServer

服務端,支援 .NET5.0、.NETCore2.1+、NETStandard2.0。

以及一套簡單的 ImHelper API 提供給

業務端

使用,例如 ImHelper.SendMessage(a, b, 'hello world') 就可以實作 a -> b 發送消息。

開源位址:https://github.com/2881099/FreeIM

⛳ 項目由來

2017 年進朋友的公司救火,那個時候公司的人員架構、技術架構一團糟,例如通訊子產品,配備了幾人的全職團隊負責工作,痛點如下:

1、IM服務端代碼臃腫不堪;

2、邏輯混亂不堪,IM服務端代碼包含了大量業務邏輯,例如聊天記錄、訂單資料,這本來應該是業務方的資料;

3、混亂持續放大,IM服務端為了适應需求,不斷增加業務協定,越來越像業務端;

4、溝湧成本巨高,IM服務端經常和業務方開怼,比如某業務到底以誰的資料為準;

5、通訊協定失策,IM服務端使用原生Socket自定義通訊協定,後來要維護 WebSocket 及 自己定義協定兩套,經常發生消息無法傳輸的問題;

FreeIM 架構的接入之後,解散了 IM 團隊,解決了業務與通訊的職責沖突,簡化了架構,降低了維護成本。經曆 1年半的生産環境,整理代碼于 2018 年開源。

我是不是太卷了。。别急啊,他們是 java 團隊,是不是瞬間舒服了

⚡ 如何接入?

dotnet add package FreeIM

1、ImServer 服務端

一套永遠不需要疊代更新的IM服務端,

ImServer

支援 .NET6.0、.NETCore2.1+、NETStandard2.0
public void Configure(IApplicationBuilder app)
{
    app.UseFreeImServer(new ImServerOptions
    {
        Redis = new FreeRedis.RedisClient("127.0.0.1:6379,poolsize=5"),
        Servers = new[] { "127.0.0.1:6001" }, //叢集配置
        Server = "127.0.0.1:6001"
    });
}
           

2、WebApi 業務端

public void Configure(IApplicationBuilder app)
{
    //...

    ImHelper.Initialization(new ImClientOptions
    {
        Redis = new FreeRedis.RedisClient("127.0.0.1:6379,poolsize=5"),
        Servers = new[] { "127.0.0.1:6001" }
    });

    ImHelper.EventBus(
        t => Console.WriteLine(t.clientId + "上線了"), 
        t => Console.WriteLine(t.clientId + "下線了"));
}
           
ImHelper方法 參數 描述
PrevConnectServer (clientId, string) 在終端準備連接配接 websocket 前調用
SendMessage (發送者, 接收者, 消息内容, 是否回執) 發送消息
GetClientListByOnline - 傳回所有線上clientId
HasOnline clientId 判斷用戶端是否線上
EventBus (上線委托, 離線委托) socket上線與下線事件
頻道 參數 描述
JoinChan (clientId, 頻道名) 加入
LeaveChan (clientId, 頻道名) 離開
GetChanClientList (頻道名) 擷取頻道所有clientId
GetChanList - 擷取所有頻道和線上人數
GetChanListByClientId (clientId) 擷取使用者參與的所有頻道
GetChanOnline (頻道名) 擷取頻道的線上人數
SendChanMessage (clientId, 頻道名, 消息内容) 發送消息,所有線上的使用者将收到消息
  • clientId 應該與使用者id相同,或者關聯;
  • 頻道适用臨時的群聊需求,如聊天室、讨論區;
ImHelper 支援 .NetFramework 4.5+、.NetStandard 2.0

3、Html5 終端

終端連接配接 websocket 前,應該先請求

WebApi

獲得授權過的位址(ImHelper.PrevConnectServer),僞代碼:

ajax('/prev-connect-imserver', function(data) {
    var url = data; //此時的值:ws://127.0.0.1:6001/ws?token=xxxxx
    var sock = new WebSocket(url);
    sock.onmessage = function (e) {
        //...
    };
})
           

📡 項目示範

運作環境:.NET6.0 + redis-server 2.8+

cd ImServer && dotnet run --urls=http://*:6001
cd WebApi && dotnet run

打開多個浏覽器,分别通路 http://127.0.0.1:5000 發送群消息

[開源精品] C#.NET im 聊天通訊架構設計 -- FreeIM 支援叢集、職責分明、高性能

🎣 分析痛點

協定痛點:如果浏覽器使用 websocket 協定,iOS 使用其他協定,協定不一緻将很難維護。

職責痛點:IM 的系統一般涉及【我的好友】、【我的群】、【曆史消息】等等。。

ImServer

WebApi

(業務方) 該保持何種關系呢?

使用者A向好友B發送消息,分析一下:

  • 需要判斷B是否為A好友;
  • 需要判斷A是否有權限;

擷取曆史聊天記錄,多個

終端

websocket.send('gethistory'),再在 onmessage 定位回調處理,多麻煩啊?

諸如此類業務判斷會很複雜,使用

ImServer

做業務邏輯,最終

ImServer

終端

都将變成巨無霸難以維護。

🌈 設計思路

終端

(如浏覽器/小程式/iOS/android) 統一使用 websocket 連接配接

ImServer

ImServer

(支援叢集)根據 clientId 分區管理 websocket 連接配接;

WebApi

使用 ImHelper 調用方法(如:SendMessage、群聊相關方法),将資料推至 Redis chan;

ImServer

訂閱 Redis chan,收到消息後向

終端

推送消息;

  • 緩解了并發推送消息過多的問題;
  • 解決了連接配接數過多的問題;
  • 解耦了業務和通訊,架構更加清淅;
    • ImServer

      充當消息轉發,連接配接維護,代碼萬年不變、且不需要重新開機維護
    • WebApi

      負責所有業務

舉例1、使用者A向B發送消息:

終端

A ajax ->

WebApi

->

ImServer

->

終端

B websocket.onmessage;

舉例2、擷取曆史聊天記錄:

終端

請求

WebApi

(業務方) 接口,傳回json(曆史消息)。

舉例3、A向B發檔案的例子:

  • A向

    WebApi

    傳檔案
  • WebApi

    通知

    ImServer

    ,ImHelper.SendMessage(B, "A正在給傳送檔案...")
  • B收到消息,A正在給傳送檔案...
  • WebApi

    檔案接收完成時通知

    ImServer

    ,ImHelper.SendMessage(B, "A檔案傳輸完畢(含檔案連結)")
  • B收到消息,A檔案傳輸完畢(含檔案連結)

FreeIM 強依賴 redis-server 元件功能:

  • 內建了 redis 輕量級的訂閱釋出功能,實作消息緩沖發送,後期可更換為其他技術
  • 使用了 redis 存儲一些關系資料,如線上 clientId、頻道資訊、授權資訊等

🌳 叢集分區

單個

ImServer

執行個體支援多少個用戶端連接配接,3萬?如果線上使用者有10萬人,怎麼辦???

部署 4 個

ImServer

  • ImServer

    1 訂閱 redisChan1
  • ImServer

    2 訂閱 redisChan2
  • ImServer

    3 訂閱 redisChan3
  • ImServer

    4 訂閱 redisChan4

WebApi

(業務方) 根據接收方的 clientId 後四位 16 進制與節點總數取模,定位到對應的 redisChan,進行 redis->publish 操作将消息定位到相應的

ImServer

每個

ImServer

管理着對應的終端連接配接,當接收到 redis 訂閱消息後,向對應的終端連接配接推送資料。

📰 事件消息

IM 系統比較常用的有上線、下線,在

ImServer

層才能準确捕捉事件,但業務代碼不合适在這上面編寫了。

此時采用 redis 釋出訂閱,将上線、下線等事件向指定頻道釋出,

WebApi

(業務方) 通過 ImHelper.EventBus 方法進行訂閱捕捉。

[開源精品] C#.NET im 聊天通訊架構設計 -- FreeIM 支援叢集、職責分明、高性能

🌌 有感而發

為什麼說 SignalR 不合适做 IM?

1、IM 的特點必定是長連接配接,輪訓的功能用不上;

2、因為 SignalR 是雙工通訊的設計,

終端

使用 hub.invoke 發送指令給 SignalR 服務端處理業務,适合用來代替 ajax 減少 http 請求數量;

3、過多使用 hub,SignalR 服務端會被業務入侵,業務變化頻繁後不得不重新釋出版本,每次部署所有終端都會斷開連接配接,遇到5分鐘發一次業務更新檔的時候,類似離線和上線提示好友的功能就無法實作;

FreeIM 業務和推送分離設計,

終端

連接配接永不更新重新開機

ImServer

,業務代碼全部在

WebApi

編寫,是以重新開機

WebApi

不會造成連接配接斷開。

作者是什麼人?

作者是一個入行 18年的老批,他目前寫的.net 開源項目有:

開源項目 描述 開源位址 開源協定
FreeIM 聊天系統架構 https://github.com/2881099/FreeIM MIT
FreeRedis Redis SDK https://github.com/2881099/FreeRedis MIT
csredis https://github.com/2881099/csredis MIT
FightLandlord 鬥DI主網絡版 https://github.com/2881099/FightLandlord 學習用途
FreeScheduler 定時任務 https://github.com/2881099/FreeScheduler MIT
IdleBus 空閑容器 https://github.com/2881099/IdleBus MIT
FreeSql ORM https://github.com/dotnetcore/FreeSql MIT
FreeSql.Cloud 分布式tcc/saga https://github.com/2881099/FreeSql.Cloud MIT
FreeSql.AdminLTE 低代碼背景生成 https://github.com/2881099/FreeSql.AdminLTE MIT
FreeSql.DynamicProxy 動态代理 https://github.com/2881099/FreeSql.DynamicProxy 學習用途

需要的請拿走,這些都是最近幾年的開源作品,以前更早寫的就不發了。

繼續閱讀