IM系統種類:
1. 單聊:已讀未讀,端到端加密,離線消息,
2. 群聊:
大群(萬人群),記錄一份聊天記錄。
小群 (200人以下群,用戶體驗不同,功能更多,可以在小群內做已讀未讀消息,隱私消息),
小群 已讀未讀,使用mongoDB記錄狀態。msgID,user_11110001:1
3. 聊天室:沒有離線消息的概念。用戶可以隨機加入和退出聊天室。(已讀未讀,端到端加密,離線消息)這些都沒有。
4. Channel超大群:群成員數量沒有上限,客戶端訂閱某個Channel。但Channel的總個數不應該太多。
IM服務心得:
1. 服務之間調用不應該有同步阻塞式IO操作
2. 消息在鏈路的任何一個環節,為了保證消息可靠性,都要有ACK和重發機制。
3. 為了避免多線程操作同一份數據,需要加鎖導致等待的問題。在進程內部對thread的分配也根據sessionId進行sharding
考慮的問題:
1. 安全性
2. 可靠性,不丟消息,消息不重復。
3. 高性能,高並發
群聊服務器
# 架構模型
1. 聊天室:AccessServer -> RocketMQ -> AccessServer
2. AccessServer-> GroupServer -> AccessServer
AccessServer -> Redis
GroupServer: 緩存的是 在線的群成員UID,以及AccessServerID
缺點:當一台AccessServer關閉后,如果將這個AccessServer所的連接信息從Redis清除。
同一個UID可以有多個連接,可以連接到多個AccessServer中。
3. AccessServer -> GroupServer -> UserRouterServer-> AccessServer
AccessServer -> UserRouterServer
GroupServer: 只緩存:在線群成員的UID
UserRouterServer: 只緩存:AccessServerID
好處:職責單一。
# 聊天室與Group的區別
1. 聊天室:可以隨時加入或退出。退出聊天室之后,無需推送消息。
群:成員不會隨時退出,可以在同一時間接收多個群的消息。
2. 聊天室:沒有離線消息的概念
群:用戶離線,下次上線后需要能接收未讀的消息。
## AccessServer
1. 負責跟客戶端建立連接。集群,不同的用戶socket隨機連接到不同的接入服務器。
2. 將客戶端連接信息上報給UserRouterServer。 定時上報。推
3. 接受客戶端消息: 生成msg_id: 自增,redis , 返回:已發送ACK
4. 轉發給GroupServer或P2pServer
5. 將來自UserRouterServer的消息轉發給客戶端
6. 同一個uid可以有多個客戶端連接
## UserRouterServer
1. 記錄uid和AccessServer的關系:內存緩存, RocksDB緩存(本服務的數據)
2. 將來自GroupServer或P2pServer的消息,轉發給AccessServer
3. 同一個uid可以有多個AccessServer
4. 提供用戶在線狀態的查詢接口。
5. uid和AccessServer的關系緩存,有細粒度的定時失效控制。用redis無法實現。
## GroupServer
1. 接收來自AccessServer的消息
2. 本地緩存Group中的 在線用戶UID。 定時從UserRouterServer集群中查詢。 定時查詢。拉
3. 將消息轉發給 UserRouterServer。
4. 根據GroupID負載均衡,同一個group消息,一定會路由到同一台GroupServer
## P2pServer
1. 接收來自AccessServer的消息
2. 將消息轉發給 UserRouterServer。
## 數據庫表:
1. 用戶會話列表
sessions: uid, session_id, session_type, session_name;
2. 用戶好友表:
friends: uid1, uid2 限制:(uid1<uid2)
3. 用戶基本信息表:
user_info: uid, nickname, avatar, phone, password
4. 用戶備注表:
friend_memo:uid, friend_uid, friend_alias
5. 群信息:
group:gid,group_name,group_avatar, created, updated
6. 群成員:
group_member: gid, uid , join_time
7. 群消息:根據group_id分表
group_message: gid, from_uid, .....
8. 群消息:根據 session_id 分表
p2p_message: session_id, from_uid, to_uid, .....
## Redis
1. session_last_msg: (session_id, msg_id, message_content....)
2. user_last_read: (session_id , msg_id )
## Q/A
1. - Q:加載會話列表過程
- A: 查詢sessions表,查詢session_last_msg(Redis), 查詢user_last_read(redis)
2. - Q: 加載會話列表的未讀數如何得到?
- A: 通過redis中的session_last_msg和user_last_read中的msg_id的差值計算。
3. - Q: P2P消息ACK狀態
— A: 發送中,已發送,已接收,已讀。
4. - Q: Group消息ACK狀態
— A: 發送中,已發送
5. - Q: 何時更新user_last_read?
- A:前端讀取消息時,都要發送已讀ACK,只不過Group消息無需更新消息狀態。
6. - Q:加載好友列表過程
- A:依次查詢 friends -> user_info -> friend_memo
7. - Q:發送P2P消息時,用戶離線。
- A:不做處理。只寫入p2p_message表即可。
8. - Q:發送Group消息時,用戶離線。
- A:不做處理。只寫入group_message表即可。
9. - Q:用戶重連時。做哪些事情?
- A:1 重新加載會話列表,計算未讀消息和未讀數 2. 拉取當前打開的會話的消息記錄。3.嘗試推送本地發送緩沖區。
10. - Q: P2P消息分兩種
- A: 端到端加密消息和普通消息。普通消息入庫。端到端加密消息不入庫,不能保證對方一定會收到,對方在線的狀態才能發送。
11. - Q: 如何防范聊天機器人
- A: 定時ping/pong機制,前端ping后端,ping的過程中會攜帶WASM制作的簽名信息(固定密鑰+uid+time)。超時服務端沒收到ping則close這個連接。
12. - Q: 丟消息的問題:
- A: 前端:發送消息緩沖區 / ACK機制 / PingPong機制 / 消息ID連續性檢查 / 最后一條消息檢查 。
服務端:服務groupServer , userRouter, accessServer 均有各自的發送消息緩沖區。
參考RocketMQ的實現方案,每一個階段,都需要收到對方ACK之后才從本地清除。(生產階段,消費階段)
13. - Q: 群消息接收順序的錯亂
- A: 無論是Group消息還是P2P消息,都不會發生錯亂。
因為消息路由的一致性:保證消息的順序不會發生錯亂。
發送方:同一個group 都會到達同一個 groupServer
接收方:同一個group 的消息,都來自同一個groupServer , userRouter, accessServer
14.
1. 聊天室
2. 一對一單聊
3. 小群:
已讀未讀狀態,消息可以加密。
寫復制,寫擴散。消息隊列。
4. 大群:沒有已讀狀態,服務端保存聊天記錄。
沒有寫復制,直接在數據庫中集中保存一份聊天記錄。
不保證消息到達的可靠性。通過定時檢查消息最后一條記錄來保重不丟消息
服務端是否保存聊天記錄:
1. 保存聊天記錄, 不加密
2. 不保存聊天記錄,消息加密。
15 。 可靠性(消息不重復,不丟失),
及時性: 消息無延時, 網絡層面,
安全性: 用戶之間端到端加密, 服務器與客戶端之間的端到端加密。
16
Q: 為什么P2P消息需要經過P2Pserver,而不是直接由Access發送到userRouter
A:因為P2PServer具有p2pSessionId的概念,同一個p2p會話發送的消息鏈路是一樣的。