原思路
- 私聊:
- 表设计: 用一张private_msgs表记录所有私聊记录,并在表中添加is_send字段记录消息是否被发送给接收方。
- 消息发送逻辑:在私聊中,如果消息接收方在线则将消息直接转发,并记录is_send字段为已发送,否则记为未发送。
- 离线消息拉取逻辑:在用户与服务器建立websocket链接后,服务器推送当前用户在私聊表中所有未发送消息,并将消息记为已发送。
- 群聊:
- 表设计: group_msgs表记录用户在群聊中的发送的消息,group_msg_status记录服务器是否将群聊消息发送给用户-跟group_msgs为一对多关系,group_member记录群成员
- 消息发送逻辑:在群聊中,服务器收到群聊消息后,首先获取群成员列表,对于在线成员直接将消息进行转发,对于N个离线成员则将向group_msg_status消息存入N条记录
- 离线消息拉取逻辑:在用户与服务器建立websocket链接后,服务器推送当前用户在私聊表中所有未发送消息,并将消息记为已发送。
- 上述设计存在问题:
- 群聊中,group_msg_status数据量庞大,对于一个百人发群,一百条消息就会在表中存储一万条记录。
- 消息拉取逻辑不适合离线消息较多的情况,一次性推送太多消息会导致客户端卡
- 服务器获取消息的时候还要对不同发送方的消息进行分组,逻辑麻烦,想要实现类似微信、QQ这种能够显示某个对话中的未读消息数量以及最近的一条消息也麻烦
优化思路
- 采用离线消息计数器表,用于统计某个会话中的未发送消息数量、最近一条消息的内容、以及未发送消息的id范围。
- 私聊:
-
表设计: 用一张private_msgs表记录所有私聊记录,并在表中添加is_send字段记录消息是否被发送给接收方。新增private_msg_counts表
-
消息发送逻辑:在私聊中,如果消息接收方在线则将消息直接转发,并记录is_send字段为已发送,否则记为未发送,同时更新private_msg_count表。
-
离线消息拉取逻辑:在用户与服务器建立websocket链接后,服务器推送private_msg_counts表中的离线消息记录。当用户进入某个会话后,再通过http来分页查询private_msgs表获取未发送消息。客户端当获取完所有未读消息后,再显示缓存在本都的已获取消息。
-
- 群聊:
- 表设计: group_msgs表记录用户在群聊中的发送的消息,移除group_msg_status,新增group_msg_counts。这张表会比private_msg_counts多一个first_unsend_msg_id,原因是private_msgs表中有is_send字段来判断消息是否发送,利用last_msg_id和is_send字段就能确认未发送消息id的范围,但是group_msgs表没有,所以就引入了first_unsend_msg_id来记录未发送消息id的范围
- 消息发送逻辑:在群聊中,服务器收到群聊消息后,首先获取群成员列表,对于在线成员直接将消息进行转发,对于N个离线成员则更新其在group_msg_counts中的记录
- 离线消息拉取逻辑:在用户与服务器建立websocket链接后,服务器推送当前用户的离线群聊消息计数器即可,后续客户端在通过http来获取具体消息。
- 表设计: group_msgs表记录用户在群聊中的发送的消息,移除group_msg_status,新增group_msg_counts。这张表会比private_msg_counts多一个first_unsend_msg_id,原因是private_msgs表中有is_send字段来判断消息是否发送,利用last_msg_id和is_send字段就能确认未发送消息id的范围,但是group_msgs表没有,所以就引入了first_unsend_msg_id来记录未发送消息id的范围
- 方案存在问题:
- 如果你的消息id方案是递增的,因为消息在网络中传递会存在延迟,一个会话中同时发送多条消息时会出现计数器中的last_unsend_msg_id被较小的消息id覆盖,导致消息丢失。
- 举个例子:用户1给用户2发送了msgA,用户2处于离线状态,所以这两个消息都会出发更新计数器的逻辑。服务器线程1收到了msgA并存入了private_msgs,msgA分配的id为20。这时服务器线程2收到msgB并存入了private_msgs,msgB分配的id为23,并且线程2先更新了计数器。接着线程1更新计数器,使last_msg_id未20。正巧这个时候用户2上线了,那么msgB就丢失了,用户1就只能收到id为20以前的未发送消息。
- 解决方案:在更新计数器时时确保当前的msg_id>last_msg_id在进行更新
- 如果你的消息id方案是递增的,因为消息在网络中传递会存在延迟,一个会话中同时发送多条消息时会出现计数器中的last_unsend_msg_id被较小的消息id覆盖,导致消息丢失。