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会话发送的消息链路是一样的。