不管是MQ(Msg Queue)的消息投递,还是单人实时聊天的消息投递,都需要通过应用层的超时、重传、确认、去重来保证消息的可靠投递。

但是,如果没有打开手机,没有登录微信,好友发给我的微信消息,有没有可能丢失呢?这是今天和大家分享的话题。

画外音:初步一想,离线消息存库不就好了么?往后看,会比你想象的更复杂。

接收方不在线,消息发送流程是怎么样的?

如上图所述,A给B发了一条消息,而B不在线,离线消息存储的流程如下:

画外音:在服务端会存储B的状态为offline。

(1) 用户A发送消息给用户B,通过server中转;

画外音:你猜,有没有可能不通过server中转,而AB直连呢?

(2) server查看用户B的状态为offline;

(3) server将消息存储到DB中;

(4) server返回用户A发送成功;

画外音:没毛病,对于发送方而言,消息落地DB就认为发送成功。

离线消息表如何设计?

很容易想到,消息业务有这样的一些关键属性:

t_offline_msg(

receiver_uid,  // 离线消息接收方

msg_id,  // 消息ID

time,  // 消息发送时间

sender_uid, // 消息发送方

msg_type, // 消息类型

msg_content, // 消息内容

);

根据业务模式设计表结构。

画外音:下半句是,根据访问模式设计索引结构。

那么,离线消息的拉取过程如何呢?

B要拉取A给ta发送的离线消息,只需在

(receiver_uid(B), sender_uid(A))

上查询,然后把离线消息删除,再把消息返回B即可。

画外音:根据这个访问模式,要建立一个联合索引。

整体流程如上图所述:

(1) 用户B拉取用户A发送给ta的离线消息;

(2) server从DB中拉取离线消息;

(3) server从DB中把离线消息删除;

(4) server返回给用户B想要的离线消息;

画外音:没毛病,这个过程也不难想到。

BUT,用户B登录微信的时候,其实不止要拉取A发给他的离线消息,还需要拉取所有其他好友发给他的离线消息呀!

OK!如果用户B有很多好友,登陆时客户端需要对所有好友进行离线消息拉取。

客户端伪代码:

get B's friend-list;  // 拉取B的好友列表

for(all uid in B's friend-list){    // 遍历所有好友uid

get_offline_msg(B,uid);   // 拉取离线消息

}

我去,如果有1000个好友,难道要拉取1000次?有没有减少拉取次数的优化方法呢?

画外音:我的微信好友已满员,大家猜微信好友上限是多少?

先拉取各个好友的离线消息数量,真正查看离线消息时,才往服务器发送拉取请求,即按需拉取。

画外音:手机端为了节省流量,经常会使用这个按需拉取的优化。

除了减少流量的“按需拉取”优化,还有减少拉取次数的优化方案么?

当当当当!!!!

可以一次性通过receiver_uid即接收方ID,拉取所有好友发送给用户B的离线消息,把登录时与服务器的交互次数降低为了1次。

画外音:这样的话,离校消息表的访问模式就变为,只需要按照receiver_uid来查询了。

到客户端本地再根据sender_uid进行计算。

问题又来了,用户B一次性拉取所有好友发给ta的离线消息,消息量很大时,一个请求包很大,速度慢怎么办?

画外音:

老板,怎么这么难伺候呢?

请求次数多,你说瓶颈是网络,慢!

1次请求,你说报文大,瓶颈是带宽,又说慢!

到底要闹哪样?

分页拉取,是一种常见的优化方案。根据业务需求,先拉取最新的一页消息,再按需一页页拉取。

画外音:这是一个包大小与拉取次数的折衷。

任何设计方案都是折衷。

新的问题,离线消息会不会丢失,用户会不会收不到呢?

例如,上述步骤第三步执行完毕之后(删除了离线消息),第四个步骤离线消息返回给客户端过程中,服务器挂掉,路由器丢消息,或者客户端crash了,那离线消息岂不是丢了么。

画外音:数据库已删除,用户却还没看到。

如何保证离线消息的可达性?

如同在线消息的应用层ACK机制一样,离线消息拉时,不能够直接删除数据库中的离线消息,而必须等应用层的离线消息ACK,等客户端真的收到离线消息,才能删除数据库中的离线消息。

画外音:ACK机制,是消息可靠性传递的常见玩法,不管是在线消息,还是离线消息。

新的问题又出现了!

画外音:刨根问底,才是严谨的治学态度。

如果用户B拉取了一页离线消息,却在ACK之前crash了,下次登录时会拉取到重复的离线消息么?

拉取了离线消息却没有ACK,服务器不会删除之前的离线消息,故下次登录时系统层面还会拉取到。但在业务层面,可以根据msg_id去重,让用户无感知。

画外音:SMC理论,系统层面无法做到消息不丢不重,业务层面可以做到对用户无感知。

另一个问题,假设有N页离线消息,现在每个离线消息需要一个ACK,那么岂不是客户端与服务器的交互次数又加倍了?有没有优化空间?

画外音:优化是无止境的。

其实,不用每一页消息都ACK,在拉取第二页消息时相当于第一页消息的ACK,此时服务器再删除第一页的离线消息即可,最后一页消息再ACK一次。这样的效果是,不管拉取多少页离线消息,只会多一个ACK请求,与服务器多一次交互。

总结

“离线消息”的玩法,可能比大家想象的要复杂,常见的优化有:

(1) 对于同一个用户B,一次性拉取所有用户发给ta的离线消息,再在客户端本地进行发送方分析,相比按照发送方一个个进行消息拉取,能大大减少服务器交互次数;

(2)按需拉取,是无线端的常见优化;

(3)分页拉取,是一个请求次数与包大小的折衷;

(4)应用层的ACK,应用层的去重,才能保证离线消息的不丢不重;

(5)下一页的拉取,同时作为上一页的ACK,能够极大减少与服务器的交互次数;

思路比结论更重要,希望大家有收获。

架构师之路-分享可落地的技术文章

相关推荐:

你丢过离线消息么?

画外音:“好看”是一种习惯,谢谢。

java 微信给好友发信息吗,不在线,好友发给我的微信消息,会不会丢?相关推荐

  1. 服务器微信了早上好,微信早晨好问候语句动态图片 早上好发给朋友的微信早安问候语简短...

    原标题:微信早晨好问候语句动态图片 早上好发给朋友的微信早安问候语简短 嘀嘀嘀嘀,我的短信到啦.用关心方式,要你多注意休息:用体贴方式,要你轻松而快乐:用祝福方式,要你一切都过的好:用短信方式,告诉你 ...

  2. 微信定时向好友发信息(循环发信息)

    确保自己电脑系统时间准确: 1.打开 浏览器: 2.访问微信网页版:https://wx.qq.com/,并扫描登录: 3.在左侧找到该联系人,选中后对话,右侧会显示进入聊天窗口: 4.把你要发的内容 ...

  3. 利用Java实现微信公众号发送信息提醒通知

    项目场景: 项目场景:利用Java实现微信公众号发送信息提醒通知! Java实现方法: 1.首先创建实现公众号消息发送的方法: public ReturnDO<String> sendTe ...

  4. 不注册微服务号如何使用Java实现每日给女友微信发送早安等信息?

    前言 据说这个功能最近在抖音上很火,我没有抖音,没有看到. 但是我在掘金和CSDN上看了,相关案例确实很多,但是大家都是借助于了微信服务号,在我看来,效果很不佳. 其实我原来的初衷是这样的,每天定时给 ...

  5. 获取小程序用户信息+java_java获取微信小程序用户信息

    第一步:获取openid.session_key等信息. /** * 获取微信小程序的用户openid和session_key/unionid * 返回格式:{"session_key&qu ...

  6. 微信小程序通过code去获取微信用户的加密信息

    今天项目要获取用户信息,我发现官方给的解密demo就是没有java  我ca,我就找了这哥们的  (多谢   @未来之路   这哥们的答案) 一.微信小程序 第一步:调用 wx.login获取code ...

  7. java计算机毕业设计学生学籍信息管理系统源码+mysql数据库+lw文档+系统+调试部署

    java计算机毕业设计学生学籍信息管理系统源码+mysql数据库+lw文档+系统+调试部署 java计算机毕业设计学生学籍信息管理系统源码+mysql数据库+lw文档+系统+调试部署 本源码技术栈: ...

  8. java计算机毕业设计建筑公司工程信息管理系统源码+mysql数据库+系统+lw文档+部署

    java计算机毕业设计建筑公司工程信息管理系统源码+mysql数据库+系统+lw文档+部署 java计算机毕业设计建筑公司工程信息管理系统源码+mysql数据库+系统+lw文档+部署 本源码技术栈: ...

  9. JAVA微信公众号完整版教程扫一扫登录/自动回复/客服消息

    微信公众号开发完整版 开篇整理 Maven依赖 配置公众测试号 后续方法中使用的wxService 配置服务器所需接口 获取微信accessToken 开篇整理 之前写过一篇关于微信/企业微信/钉钉授 ...

最新文章

  1. 低调的 Linux 文件系统家族
  2. 斯洛文尼亚接受BCH支付的商家达343家,日本和北昆士兰州对BCH接受度增长迅速
  3. linux java top_linux top命令 监测系统性能
  4. 交换次数c语言,插入排序 - C中的比较和交换计数
  5. Android笔记 - Android studio如何添加arr库
  6. The requested lisk key xxx could not be resolved as a collection type.
  7. 人人都是测试经理:如何进行测试风险分析并制定策略
  8. 四川取消英语计算机考试,2020年起,四川将不再承接全国英语等级考试,已有多省份停考!...
  9. 使用github创建个人网站
  10. java来电_java串口 来电显示
  11. ma5671怎么设置_电信/联通/移动,更换华为MA5671光猫详细教程。
  12. 国产MCU替代STM8S003方案汇总
  13. 张文宏教授再发“霸气”言论!面对疫情,一个真正的大国是什么样子?
  14. 【不知道发啥】Win7网页版使用方法
  15. Rademacher复杂度和VC-维
  16. java计算机毕业设计网上宠物商城管理系统源码+系统+数据库+lw文档+mybatis+运行部署
  17. Android入门教程 (一) Android简介和android studio安装
  18. 2022年登高架设考试练习题及在线模拟考试
  19. 简单工厂模式 - Unity
  20. 【《自动控制原理(田玉平)》|课本知识点整理(一)】第 2 章 控制系统的输入 - 输出模型

热门文章

  1. 高通about.html 文件,高通case提交指南2015Oct(4)(1)
  2. 视频会议室装修部署指南
  3. DZone每日必读-news: 指导软件团队取得成功的 4 种方法
  4. 秋招C++开发学习之路day10
  5. Java网络编程基础--Netty预备知识
  6. 快速入门Opentracing-cpp
  7. Flooded! POJ - 1877 模拟题
  8. oracle 人民币符号,人民币的符号的正确表示法?一杠?两杠?
  9. 网易大神app ios和android,网易大神app是干嘛的?网易大神有什么用?
  10. (java)跳台阶:一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。