消息推送服务-离线消息处理
在上一篇文章,小编浅谈了即时通讯消息的投递机制,但有人会问,如果用户不在线的时候,消息应该要怎么处理呢?现在小编就来谈谈,如果用户不在线时,如何保证消息的不丢失。
即时通讯消息的可靠投递
我们来看看下边的流程:
即时通讯消息的可靠投递
这是即时通讯消息的可靠投递流程,这里边没有考虑到的一种情况就是,如果用户不在线的时候,这个消息要如何处理呢?于是,我们需要引入离线消息储存机制。
离线消息储存机制
我们先来看看我们改进后的流程
消息可靠投递+离线消息存储
图中便是我们整个消息机制的流程:
用户在线:
- 用户A向 Im-server 发送了一个消息请求包,即 Msg:R
- Im-server 成功处理之后,给用户A一个消息响应包,即 Msg:A
- Im-server 去 User-status-cache 缓存中获取用户B的在线情况
- User-status-cache 缓存告诉 Im-server 用户在线
- Im-server 向用户B推送一个消息通知包,即 Msg:N
- 用户B接收成功之后,向 Im-server 发送一个消息请求包,即 Ack:R
- Im-server 接收成功之后,给用户B发送一个消息响应包:即 Ack:A
- Im-server 最后给用户A发送一个消息通知包:即 Ack:N
用户不在线:
- 用户A向 Im-server 发送了一个消息请求包,即 Msg:R
- Im-server 成功处理之后,给用户A一个消息响应包,即 Msg:A
- Im-server 去 User-status-cache 缓存层中获取用户B的在线情况
- User-status-cache 缓存层告诉消息服务器用户不在线
- Im-server 向 Im-Db 数据层发送一条保存消息请求
- Im-Db 数据层 成功保存消息后,给 Im-server 发送一条保存成功通知
- 随后 Im-server 给用户A发送一个用户B已收到消息的通知包:即 Ack:N
引入离线消息储存,就能解决用户不在线的时候,保证消息不丢失的问题。
问题
用户B 登录之后,如何获取离线消息呢?
离线消息的获取
首先我们来看看小编的离线消息表:
// 离线消息表(根据个人业务创建)
CREATE TABLE `offline_message` (
`id` int(1) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`msg_id` varchar(32) DEFAULT NULL COMMENT '消息标识(发送人随机生成,不唯一)',
`send_uid` int(1) NOT NULL COMMENT '发送人ID',
`received_uid` int(1) NOT NULL COMMENT '接收人ID',
`msg_type` tinyint(1) NOT NULL COMMENT '消息类型',
`msg_content` varchar(500) NOT NULL COMMENT '消息内容',
`send_time` int(1) NOT NULL COMMENT '发送时间',
`received_time` int(1) NOT NULL COMMENT '成功接收时间',
`is_receive` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否接收,默认0否,1是',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='离线消息表';
以上就是我们的离线消息表了,这样,我们就可以在用户上线之后,拉取用户的离线消息了。
拉取离线消息
- 用户B向 server 发送一个拉取数据的请求
- server 接收到请求后向 Im-DB 发送拉取数据请求
- Im-DB 接收到请求后,查询数据并返回给 server
- server 拿到数据后,发送给用户B
- 用户B成功处理好消息之后,给 server 发送接收成功请求
- server 接收到成功请求之后,给 Im-DB 发送删除离线数据请求
- Im-DB 接收到请求后,删除数据
上述的流程中,如果用户B有多个用户(或者群)的离线消息,则每次拉取都会循环拉取大量信息,而这些信息中,部分信息用户B可能不会去查看,这就造成了极大的性能浪费,如:
<?php
// 所有消息记录
$messages = array();
// 临时记录
$message = array();
foreach ($friend_ids as $value) {
$message['uid'] = $value;
$message['list'] = get_friend_message($value);
// 放入所有消息记录数组中
$messages[] = $message;
}
?>
优化方案
由于用户是只有点击好友聊天之后,才会看到该好友的聊天记录,所以,我们可以先获取每个好友的消息数量,我们可以做以下改进:
用户登录,拉取离线消息
用户点击好友聊天之后,再来取该好友的离线消息:
获取指定用户的离线消息
我们将获取离线消息的步骤,改进之后,就可以大大的减少我们服务器不必要的消耗。但这是不是就是最完美的方案呢?小编认为不是,因为以上步骤中,如果第5步成功接收的数据包丢失之后,Im-DB 中该不会删除用户已经获取的消息,这就造成数据多余,所以我们还需要进一步的改进:
获取指定用户的离线消息(增加删除确认消息)
- 当用户B成功接收消息后,向 server 发送成功接收请求包,并将改请求放入队列中
- server 收到请求后,向 Im-DB 发送删除数据请求包
- Im-DB 收到请求后,删除数据,并向 server 发送删除成功请求包
- server 接收到请求后,向用户B发送删除成功请求包
- 用户B将这次的请求从队列中移除(del_id来判断)
- 如果用户B在一定时间内没有收到删除成功请求,则从队列中取出相应的请求包,发送给 server,直至收到删除成功的请求包为止
以上就是小编觉得比较完整的离线消息储存读取机制的流程了。
结论
- 增加离线消息储存表,确保离线消息不丢失
- 先拉取用户离线消息的数量,再根据需要拉取用户的详细离线消息,降低服务器消耗
- 增加删除确认机制,确保离线消息成功删除,避免出现多余的消息