聊天服务器架构

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会话发送的消息链路是一样的。
posted on 2020-09-27 11:40  袜子破了  阅读(891)  评论(5编辑  收藏  举报