最近做im調研,記錄一下筆記。
拆分了代碼,使其可以在windows下運行
(圖是掛了,可以移步github查看) im_servers
特色
- 自帶 android 、 ios 、 web 的 sdk
- 單臺50w併發,3000條/s消息發送量(32g,16核),支持服務器的超大集羣。
- 支持點對點消息, 羣組消息, 聊天室消息
- 支持超大羣組(3000人)
- 與終端交互靠自定義協議,傳輸體小
- 敏感詞過濾
- 支持持久化和非持久化消息。(目前僅客服消息支持非持久化)
- 可調用api查看im系統使用情況
項目介紹
分爲三個服務:
im 終端服務(可分佈式部署,暫無負載均衡模塊)
imr 路由服務(主要解決 im 分佈式部署的問題)
ims 存儲服務(主從部署)
-
im 與終端相連,將鏈接、路由信息、用戶、組、聊天室等存儲在內存中。
accept 收到一個連接 開啓寫線程和讀線程 寫線程:監聽 client.wt 阻塞隊列,一有數據就寫入 conn 讀線程:按照數據包協議從 conn 讀出數據包,由 client.HandleMessage 處理 分發給 imr 處理
-
imr 將消息交付給對應的 im 處理, 解決 im 分佈式部署
-
ims 存儲服務 (主從服務),存儲介質爲文件、同時創建消息索引
其中對超級羣做了單獨處理,如果配置了超級羣存儲節點、信息單獨存儲
客戶端消息發到 IM 服務器之後,IM服務器會給接收客戶端發送 MSG_SYNC_NOTIFY ,然後讓客戶端再主動去拉,
這樣做的目的是嚴格控制消息順序的,客戶端接受到的消息順序和服務端存儲的順序會一致。
只有不持久化的消息纔會即時發送到接收客戶端。
-
使用 mysql、redis:
redis 做隊列和緩存、
數據先存儲在內存中,過後會將數據刷新到 mysql -
使用glog作爲系統日誌
協議
使用兩種通信協議:
custom:自定義協議
gorpc:https://github.com/valyala/gorpc
終端 <-> IM:custom
IM <-> IMR:custom
IM <-> IMS:gorpc
- 自定義協議:
包:header(12)|body header:len(4),seq(4),cmd(1),version(1),空(2)
- gorpc
github.com/valyala/gorpc
存儲文件
分爲索引文件、消息文件
-
索引文件
索引文件兩種類型: peer:單聊消息通訊 group:羣消息通訊 索引文件位置: peer:%root%/peer_index group:%root%/group_index
如果索引文件不存在,那麼在 ims 啓動的時候會根據消息內容文件進行重建;
如果索引文件讀取出現問題,則會直接退出,說明文件已經破壞,需要人工干預。
在取離線消息時,可以對羣組消息和單聊消息分別獲取,
這樣可以做到分別控制單聊消息和羣組消息讀取量,避免單次讀取超量的離線消息消息索引全部放在內存中,在程序退出時,再全部保存到文件中,
如果索引文件不存在或上次保存失敗,則在程序啓動的時候,從消息 DB 中重建索引,這需要遍歷每一條消息 -
消息文件
都存儲在一個目錄裏,這個目錄由參數 storage_root 指定。 消息存儲被劃分到不同的塊,每個消息塊大小爲 128M,一個消息塊對應一個文件。 消息存儲文件的文件名爲: message_0 ... message_N 數字0、1、2、...N爲消息塊 ID
-
消息 ID
每條消息都會對應一個消息 ID,這個消息 ID 是全局唯一且逐漸遞增的。
消息 ID 是該條消息在全局消息文件中的起始位置。 msgid = BLOCK_SIZE * block_NO + block_offset BLOCK_SIZE:消息塊大小(128M) block_NO:消息所在消息塊號,從0開始 block_offset:消息在對應消息塊的起始位置 如何根據消息 ID 來定位消息所在的塊文件以及在塊文件中的起始位置? block_NO = int(msgid/BLOCK_SIZE) block_offset = int(msgid%BLOCK_SIZE)
-
文件格式
消息存儲文件由兩部分組成,文件頭+記錄列表。
-
文件頭
每個消息塊文件都有文件頭,文件頭大小爲32字節。
前面兩個4字節分別爲:MAGIC、VERSION。
後面24字節作爲保留區域,暫未使用,用0進行填充。
-
消息記錄
消息並非固定長度,消息的前後分別用來存放 MAGIC 數字。
cmd、version、flag 各佔一個字節,最後用0補齊4個字節。
len:存放消息記錄 body 部分的長度
seq:TODO
cmd:
version:
flag:
body:TODO
-
消息同步
-
消息同步過程
1、當終端發送 MSG_IM 消息時,服務端會通知接收終端 MSG_SYNC_NOTIFY ,其中消息內容 SyncKey 爲當前最新 msgid(由 IMS 服務器返回) 2、終端收到這個 SyncKey 時,會向 IM 服務器發送 MSG_SYNC 消息,消息內容即爲 SyncKey 3、IM服務器調用 IMS ,獲取從 sync_key 位置的最新消息 4、IM服務器發送 MSG_SYNC_BEGIN + 歷史消息列表 + MSG_SYNC_END 5、終端收到 MSG_SYNC_END 消息時,保存最新 SyncKey 6、如果終端設置了 SyncKeyHandler ,那麼將會向服務器發送 MSG_SYNC_KEY 消息,消息內容爲最新 SyncKey 7、服務器收到 MSG_SYNC_KEY 後,會比較當前 Redis 裏面的 SyncKey 和終端發送的 SyncKey 8、如果終端發送 SyncKey 較大,那麼將其存入 Redis ;否則,do nothing
-
終端初始化消息同步
1、終端連接後,進行 Token 認證 2、發送 MSG_SYNC ,此時 SyncKey 爲0 3、服務器收到 SyncKey 爲0的消息同步請求,從 Redis 中獲取對應的 SyncKey users_#{APPID}_#{UID} 用戶狀態數據,類型爲 Hash,hashKey 如下: * sync_key * group_sync_key_#{GROUPID} * forbidden * unread 4、後面的流程保持一致 5、IM 服務器調用 IMS,獲取從 sync_key 位置的最新消息 6、IM 服務器發送 MSG_SYNC_BEGIN+歷史消息列表+MSG_SYNC_END 7、終端收到 MSG_SYNC_END 消息時,保存最新 SyncKey
流程圖
-
消息傳輸流程
-
imr
-
ims
性能
單臺50w併發,3000條/s消息發送量(32g,16核)
來自官方說明,未做性能測試
二次開發相關
- 消息體主要通過cmd區分操作,增加新的操作增加cmd類型和後續處理
- 目前重複代碼較多,二次開發會有重複性修改
- 消息存儲是文件加索引,這部分內容想要修改的話 工作量極大(二進制文件)
- 因官方代碼不支持在windows下運行編譯所以做了結構調整,跟官方代碼更新做同步難度加大
消息類型在 message.go
存在問題:
- 終端的登陸認證
- 終端的配置信息、聊天的系統配置
- im可直接擴容,imr、ims 擴容必須重啓
ims主從,目前只做備份使用、沒有被im直接訪問減少ims壓力- 消息推送 APNs 實現未找到
- 沒有Token鑑權
- 記錄終端 ip 時,只支持 ipv4
- 有很多重複代碼、有些 bug ,看不懂邏輯、需要在梳理
- 沒有用戶體系
- 沒有知名使用的線上案例
- 社區不活躍
- 不支持語音、視頻聊天,圖片服務需要自己現行搭建
- 如果服務器掛掉導致存儲文件不完整、可能需要人工介入處理
- 數據庫表結構需要重新設計以增加業務邏輯
備註
參考地址: