即时消息技术剖析与实战
袁武林
微博研发中心技术专家
立即订阅
6503 人已学习
课程目录
已完结 24 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 搞懂“实时交互”的IM技术,将会有什么新机遇?
免费
基础篇 (8讲)
01 | 架构与特性:一个完整的IM系统是怎样的?
02 | 消息收发架构:为你的App,加上实时通信功能
03 | 轮询与长连接:如何解决消息的实时到达问题?
04 | ACK机制:如何保证消息的可靠投递?
05 | 消息序号生成器:如何保证你的消息不会乱序?
06 | HttpDNS和TLS:你的消息聊天真的安全吗?
07 | 分布式锁和原子性:你看到的未读消息提醒是真的吗?
08 | 智能心跳机制:解决网络的不确定性
场景篇 (4讲)
09 | 分布式一致性:让你的消息支持多终端漫游
10 | 自动智能扩缩容:直播互动场景中峰值流量的应对
11 | 期中实战:动手写一个简易版的IM系统
12 | 服务高可用:保证核心链路稳定性的流控和熔断机制
进阶篇 (10讲)
13 | HTTP Tunnel:复杂网络下消息通道高可用设计的思考
14 | 分片上传:如何让你的图片、音视频消息发送得更快?
15 | CDN加速:如何让你的图片、视频、语音消息浏览播放不卡?
16 | APNs:聊一聊第三方系统级消息通道的事
17 | Cache:多级缓存架构在消息系统中的应用
18 | Docker容器化:说一说IM系统中模块水平扩展的实现
19 | 端到端Trace:消息收发链路的监控体系搭建
20 | 存储和并发:万人群聊系统设计中的几个难点
21 | 期末实战:为你的简约版IM系统,加上功能
22 | 答疑解惑:不同即时消息场景下架构实现上的异同
结束语 (1讲)
结束语 | 真正的高贵,不是优于别人,而是优于过去的自己
即时消息技术剖析与实战
登录|注册

09 | 分布式一致性:让你的消息支持多终端漫游

袁武林 2019-09-16
你好,我是袁武林。今天我们开始进入场景篇的部分,在这个部分中,我会介绍在几种典型的垂直业务场景下,IM 系统具体是如何实现的。
在即时消息的场景里,消息的多终端漫游是一个相对比较高级的功能,所谓的“多终端漫游”是指:用户在任意一个设备登录后,都能获取到历史的聊天记录。
这个功能对于有多个手机的用户来说是一个非常有用的功能,试想一下用户在交叉使用多个手机进行聊天后,如果不能在多个终端间自动同步所有的聊天记录,使用体验也不会太好。
但并不是所有的即时消息 App 都支持这个特性,比如微信虽然支持多端登录,但不知道出于什么考虑并不能在多端同步历史消息,这可能也是微信为数不多被诟病的一个小问题吧。
而 Telegram 和 QQ 却很好地支持了“多终端漫游”,使得用户在任意端登录都能获取到所有最近收发的消息。

如何实现多终端消息漫游

那接下来我们看一下,怎么才能让收发的消息能在多个终端漫游。要支持消息多终端漫游一般来说需要两个前置条件:一种是通过设备维度的在线状态来实现,一种是通过离线消息存储来实现。

设备维度的在线状态

对于在多个终端同时登录并在线的用户,可以让 IM 服务端在收到消息后推给接收方的多台设备,也推给发送方的其他登录设备。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《即时消息技术剖析与实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(24)

  • 王棕生
    老师,对消息的终端漫游,我这边还有一个云消息的实现方案,望点评!
    1 所有的消息,全部持久化存储;
    2 用户在任意一个终端设备登陆后,通过用户的uid、联系人uid去服务端拉取云消息;
    3 拉取云消息的时候,是按每条消息的时间戳倒序拉取,首次拉取可以取当前时间戳。

    作者回复: 整体上没啥大问题哈,首次拉取可以不带时间戳,默认服务端就获取最新的N条即可。
    另外时间戳是服务端应用服务器生成的么?多服务器间的时钟差异能接受吗?也可以考虑搭建一套全局的“时间相关的序号生成器”来生成消息ID,使用消息ID作为拉取的cursor,这样时间生成上也相对稳定一些。

    2019-09-16
    5
  • K.Zhou
    微信不能多端同步消息是因为消息没存在服务端吧?

    作者回复: 这个可能是个结果不是个原因,可能微信产品的设计上就是不想支持多终端消息漫游,所以消息不需要长时间存储在服务端。

    2019-09-16
    2
    3
  • javaworker
    有几个问题没想明白,望老师帮解答下,谢谢
    1.文中说如果离线消息存储容量超过限制,部分增量消息被淘汰掉了,会导致根据客户端最新版本号获取增量消息失败。
    这个问题有些不明白,如果部分增量消息被淘汰,个人觉得也没关系吧,服务端每次把比客户端版本号大的消息都发给客户端就行了啊,怎么会失败呐?

    2.文中说的离线消息表中该怎么存?比如A用户和B用户各有两个终端,但都只登陆了一个终端,这时A用户给B发送了一条消息,是按照A的userid存一条消息,按照B的userid也存一条一样的消息,A端上线拉取A的离线,B端上线拉取B的离线,也就是说A的离线消息有自己发出去的,也有别人发给A的消息,统一按照版本号存下来,A上线后都一起会拉走?

    作者回复: 第一个问题如果容量超限被淘汰掉仍然取大于版本号的消息下推的话可能会导致消息漏推。比如:客户端当前版本号是1,接下来的消息是2,3,4,假如2被淘汰,那么只会下推3和4,导致2漏推。
    第二个问题,是的,离线消息按照用户维度来存储,发送方和接收方都会存,因为发送方也可能有多台设备。

    2019-09-24
    2
  • 一步
    离线消息什么情况下进行存储呢?当用户A给用户B发送一条消息,发现没有用户B的连接信息,这个时候才进行离线消息的存储吗,还是只要有消息发送给B都会进行离线消息的存储,因为这些消息对于用户B的其他设备来说属于离线消息?

    作者回复: 出于可靠性考虑,一般来说服务端接收到消息不管接收方是否在线都会先存到离线存储中,同步再进行消息在线推送,如果接收方不在线或者在线推送失败,等下次接收方再次上线时会从这个离线存储中获取消息进行补推。

    2019-09-17
    2
  • 王棕生
    如果用户的离线消息比较多,有没有办法来减少用户上线时离线消息的数据传输量?
    答: 用户所有的离线消息对用户来说,并不都是关心和感兴趣的,用户可能只是看了与某个最近联系人的最近的几条消息后,之前的都不想看了,所以这个时候如果将之前的离线消息都拉到本地是非常浪费资源的。通常的做法是:
    1 将用户的所有离线消息,按联系人进行分开;
    2 用户登录后进入与联系人的聊天窗口时,首先加载与该联系人的最近的10条离线消息;
    3 当用户用手滑动手机屏幕的时候,再分页拉取10条。

    作者回复: 是的,推拉结合是一种不错的折中方式。

    2019-09-16
    2
  • YidWang
    拉去消息量大 主要优化方式:打包压缩或者按需拉取
    2019-09-16
    1
  • 于欣磊
    p2p的方式可以用来同步离线消息到多个设备上么?既然服务端有在线设备列表
    2019-12-03
  • 于欣磊
    最后的发送方设备同步的问题,没太明白。怎么确定发送设备呢?服务端还有一份设备消息的关系表么?

    作者回复: 是的,对于支持多设备同时登陆的场景,用户上线建立长连时是以uid + 设备id 为维度来记录和连接的映射的,所以发消息时携带设备id,服务端就可以区分出具体是用户的哪个设备发送的,下推时就可以区别处理了。

    2019-12-03
  • vearne
    延迟加载也是一个办法吧

    作者回复: 延迟加载能够一定程度缓解突发峰值流量,但对于持续高流量缓降作用有限。可以从提升写入能力角度去考虑,比如写合并。

    2019-11-11
  • GeekAmI
    多端漫游,感觉是为了解决一个问题,而引入很多问题的解决方案。
    让不用手动端到端同步聊天记录,岂不是更好吗[Facepalm][Facepalm][Facepalm]

    作者回复: 多终端漫游目前也是有比较成熟的解决方案呀,技术上并不存在问题。

    2019-11-06
  • GeekAmI
    "但并不是所有的即时消息 App 都支持这个特性,比如微信虽然支持多端登录,但不知道出于什么考虑并不能在多端同步历史消息,这可能也是微信为数不多被诟病的一个小问题吧。"
    1. 如果2台设备(mac和手机)都登录的情况下,消息会同步的;
    2. 支持从一台设备上把历史聊天消息无损的同步到另一台设备。
    我认为微信的方案即不复杂,也完美的解决了问题,很好啊
    希望老师解答下,微信的这种方案缺陷是?不太明白

    作者回复: 1. mac和手机同时登陆能够同步离线消息这个特性应该上了没多久吧,目前也仅仅是同步一段时间的离线消息,并不是能完全同步所有消息的。
    2. 支持一台设备同步到另一台的前提是需要人工干预,不是自动的,实际上是把一台设备的消息打包发送给另一台。
    并不是说这种实现方式不好,看个人的需求程度。如果你希望能够随时随地通过任意终端都能看到所有的历史聊天,那微信目前的方案可能存在一定的问题。

    2019-11-06
    4
  • null
    课后思考题:
    分页获取,用户上线时,只返回最新一页的联系人列表。针对联系人的聊天记录,同样也只返回最新的一页消息列表。用户持续下拉更新 联系人列表/消息列表 时,再获取上一页 联系人列表/消息列表 返回。
    2019-10-05
  • 东东🎈
    老师,关于服务端消息打包压缩,用的是啥方法呢,比如从api拉取离线消息20条,这个怎么进行打包压缩返回给客户端,客户端怎么处理?

    作者回复: 返回的header标识告知客户端这个消息是一个压缩过的打包消息,客户端接收到之后对body解压就可以啦。

    2019-09-20
    1
  • Geek_912fa9
    老师,用户上线时,离线消息的推送是一次性的,还是分批次下发到客户端的?如果是分批次推送的话,该用户在接收离线消息的过程中收到了其他用户发来的新消息,是否需要更新当前最新版本号,或者还是等到离线消息都推送完了,再判断是否需要更新。

    作者回复: 离线消息不多的情况下,建议打包压缩后一次性推送,如果消息很多,分批也是可以的。离线消息的下推和在线消息的下推不冲突,可以并行的,接收端根据携带的版本号比对来更新收到的最新版本号就可以,一般都是收到一条端上判断后就进行一次更新。

    2019-09-20
  • A:春哥大魔王
    老师我们im采用账号纬度的消息存储,对于离线消息同步采用设备纬度推拉结合方式,当设备上线时,客户端会收到服务端推送的一条消息,里面携带了最新的消息版本号,之后客户端采用拉的方式入参为这个版本号获取版本号之前的消息,实现增量同步,消息保留一周,历史消息只能通过主动查询了,请问下我们这种实现方式还有什么需要完善考量的吗。🙏

    作者回复: 推拉结合没问题的,历史消息的存储如果后续请求量增加的话,最好有会话和最近联系人两个维度,”空间换时间“冗余一点,这样查询联系人的时候性能好一些。

    2019-09-19
  • 墙角儿的花
    老师 离线库是包含了一个人所有的消息吧 包括自己发的聊天消息 应收的聊天消息 和 应收的信令消息 上线时从这个库拉取就好 好像这时就不用利用收发箱的数据了啊

    作者回复: 是的,离线库包括所有应该同步给这个用户设备的消息和信令。如果不考虑消息多终端同步的情况是没问题的,类似目前微信的机制,应该就只是有一个离线库。对于多终端消息同步的场景,比如一个新设备要查看和某人的所有消息,这个时候离线库针对接收人的设计就不太方便进行查询了。

    2019-09-19
  • Geek_a2849d
    老师你好,我们在拉取离线消息的时候并没有单独的版本号服务去维护版本号,而是直接通过递增的消息id去替代这个版本号 然后比对消息id去获取离线消息 这样设计是否合理呢?

    作者回复: 也是可以的,递增id的问题是如何保证任何情况下这个id能够连续递增,否则可能不太好做消息连续性检查。

    2019-09-18
  • 儿戏
    老师,我想问下客户端与服务端的通信用哪种方式比较好,http轮询还是websocket,还是有别的更好的方式?

    作者回复: 看具体业务场景吧,http轮询实现比较简单,但是资源消耗会多一些,如果有一定的研发能力,可以考虑基于mqtt进行简单的改造来实现,另外,websocket也是一个不错的选择。

    2019-09-17
  • 🐾
    袁老师好,这里有个疑问,不明白为什么需要离线消息表?即时通讯系统里会有哪些操作信令是无法在索引表中标记的?像常见的删除和撤回两个信令,其实都是可以通过索引表来实现:

    1、如果用户A只是删除了与某一联系人B的某一条聊天消息,程序的流程是,先在本地客户端删除该消息,再把该消息的删除状态发送回服务端,服务端收到后,在消息索引表找到这条消息,并标记该消息为删除状态,同时修改该消息对应的版本号,然后返回给客户端这个新版本号,客户端同步修改被删除消息的版本号。
    后来用户A在另一设备登陆,打开与联系人B的会话界面,会触发拉取消息记录的操作(目的是同步消息),因为此时用户A与联系人B的消息版本号在新登陆设备要比服务端的小(被删除的消息的版本号是最后更新的),所以拉取的消息记录包含了被删除的消息,客户端检测到该消息被删除,同步消息状态和版本号至本地客户端,然后聊天界面控制不显示被删除的消息。

    2、对于撤回消息信令,其流程跟删除消息信令差不多,只是除了要标记该消息的撤回状态和修改版本号外,还需要同步修改该消息的接收方对应的消息索引表记录,修改撤回状态和版本号。消息发送方和接收方拉取消息记录是和删除信令的判断一样。

    作者回复: 有的信令不是消息维度的,比如删除某一个会话,或者删除所有消息等等,这些信令不适合存储在索引表里。另外,索引表是会话维度的,而获取离线消息实际上是需要用户维度的所有消息,所以通过索引表来获取离线消息会比较麻烦和低效。

    2019-09-17
  • 墙角儿的花
    老师 我们做im时没有专门设置一个离线数据库 是直接根据客户端的时间戳从收发索引表里查的 当然 这会漏掉您说的信令消息 但是 并没有觉得漏掉信令消息是问题 因为 信令消息一般都是改群成员和个人信息 我们在登录时做一次整体同步就好了。其他的信令消息也就只有删除消息这个场景,如果要做的好的话确实需要做离线消息,但是这个对一些产品定位低点的好像不太重要。另外,我们以前做过一个版本,是把聊天消息和信令消息都作为普通消息记到消息索引表里,这样不需要离线消息库了。

    作者回复: 业务上能接受的话是可以的呀,微博消息箱早期也是没有离线消息buffer的,逐步演进后后慢慢加起来的。

    2019-09-16
收起评论
24
返回
顶部