即时消息技术剖析与实战
袁武林
微博研发中心技术专家
立即订阅
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讲)
结束语 | 真正的高贵,不是优于别人,而是优于过去的自己
即时消息技术剖析与实战
登录|注册

02 | 消息收发架构:为你的App,加上实时通信功能

袁武林 2019-08-30
你好,我是袁武林。
前一篇文章中,我们从使用者的直观角度和从业者的实现维度,了解一个 IM 系统都应该具备哪些要素。但实际上,从我的角度来看,我更倾向于把“IM”看做是一门可以融入到各种业务系统中,为业务系统提供“实时交互”能力的技术模块。
比如,极客时间想在它的 App 中增加一个互动模块,支持用户点对点的实时聊天功能。那么,我们就可以相应地通过一些 IM SDK 的方式,快速地把即时消息的技术引入到已有的业务系统中。
同样的,一个传统的视频网站如果想让自己的视频支持弹幕功能,也可以通过引入即时消息的技术,来让视频弹幕的参与者能实时、高效地和其他观看者进行各种互动。
所以,从某种程度上看,随着移动网络的快速发展以及资费的快速下降,即时消息技术也越来越多地被广泛应用到各种业务系统中,用于提升用户实时互动的能力。
那么,接下来,我们就一起从即时消息更细化的实现角度来看一看,给一个已有系统增加即时消息功能,大致上都有哪些具体工作。
如果为原有的业务系统增加实时消息模块,在不需要重建账号体系的前提下,整体上大概包括几块内容:
一般来说首先需要制定好消息内容和未读数的存储,另外需要建立比原业务系统更加高效实时的消息收发通道,当然也包括依托第三方辅助通道来提升消息到达率。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《即时消息技术剖析与实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(51)

  • 王棕生
    1. 消息存储中,内容表和索引表如果需要分库处理,应该按什么字段来哈希? 索引表可以和内容表合并成一个表吗?
    答: 内容表应该按主键消息ID来哈希做分库分表处理,这样便于定位某一条具体的消息;索引表应该按索引的用户UID来哈希做分库分表处理,这样可以使得当前用户的所有联系人都落在一张表上,减少遍历所有表的麻烦。
            索引表可以与内容表合成一张表,好处是显而易见的,能减少拉取历史消息时的数据库IO,不好的地方就是消息内容冗余存储,浪费了空间。

    2. 能从索引表里获取到最近联系人所需要的信息,为什么还需要单独的联系人表呢?
    答: 如果从索引表中获取一个用户的所有联系人信息(包括最后一条聊天内容和时间)的话,SQL语句中会有分组后取top 1的操作,性能不理想; 另外当前用户与单个联系人之间的未读数需要维护,用联系人表的一个字段来存储,比用索引表方便许多。

    作者回复: 👍

    2019-08-30
    1
    54
  • Julien
    每节课的思考题可以在下一节课开始之前揭晓吗?谢谢。
    2019-08-30
    24
  • hgf
    可能行为:
    1. 发送消息
    2. 查看(历史)消息
    3. 联系人列表最新一条消息
    4. 未读消息数

    内容表应该按照消息ID索引来hash,原因:使得数据分布均匀,相对使用产生时间维度做hash更能均衡系统的查询压力,很少有人查询到几个月前的数据,绝大部部分人查看历史,都会查看最近1~3天内的。


    消息索引表按照索引用户ID来hash,原因:将索引用户相关的消息记录hash到相同的表,处理比较方便,避免跨表查询。


    为什么不要将消息内容表和消息索引表合并?
    会造成消息存储冗余,特别是群聊的时候。

    为什么消息索引表需要同时记录发送方-> 接收方;接收方-> 发送方的索引?因为在分库分表的情况下,可能依据索引用户ID进行hash,如果仅记录一条记录,例如,仅记录了发送方-> 接收方索引,那么所有发送法相同的索引会hash到同样的表,那么接受方在查询会话记录时,需要从多个库表中查询,反之,也是类似情况。

    为什么需要单独的联系人表?
    联系人列表中,仅仅需要最新一次的消息,如果从消息索引表中查询所有联系人的最新一次的消息,那么需要执行group by,order by 等操作,因为在消息索引表的记录数是远大于联系人表的,查询效率上差距明显,存储端的压力也较大
    2019-09-07
    1
    5
  • 666
    想了解一下像微博这类消息系统如果解决大V用户的消息收发的?比如用户给大v点赞、评论或者是私信

    作者回复: 点赞、评论这些目前采用的拉取模式,用户打开具体的赞箱和评论箱时才从服务端获取;私信是支持在线推送的,不过为了减少对大V的骚扰,对于未关注人私信没有系统push。

    2019-09-13
    2
  • 小祺
    消息表和索引表合并后表结构:
    发消息用户ID ,收消息用户ID, 消息状态, 消息内容,消息类型,消息产生时间
    其中消息状态 0: 正常 1:发消息用户已删除 2: 收消息用户已删除
    如果两个用户都删除消息,那这条记录就被delete掉。
    既可以满足合并消息,又可以满足单个用户删除消息,请问这样设计是否可行?

    作者回复: 处理删除和合并上没问题哈,需要考虑一下分库分表场景下,收发双方查询历史消息应该怎么查呢?

    2019-09-03
    3
    2
  • Colin
    请教各位评论区的大神们:消息未读数更新时机大家都是怎么设计的?比如说:发一条消息,未读消息+1这个操作大家都是如何更新的?如果是在数据库更新,再同步到客户端,可能会出现消息到达了,未读数还没更新的现象,如果是在客户端+1,再保存到数据库,别人写个脚本就能把数据给改乱了,不安全。应该如何设计比较合理?
    2019-08-30
    3
    2
  • 挨踢菜鸟
    老师,请问websocket如何多实例部署,如何解决实例重启造成连接断开的问题

    作者回复: 没太理解到您的意思哈,websocket网关只有是无状态的多实例部署没啥问题的呀;实例重启断开连接是肯定的,需要解决的是断开后客户端需要有重连机制以及如果尽量减少实例重启的概率。

    2019-08-30
    7
    2
  • hlai
    最后张图,客户端发消息到服务是通过API, 请问为什么不可以长连接么发过去呢?

    作者回复: 这里说一下:消息的收发是可以通过同一个长连通道来实现的,对于普通的聊天场景我甚至更推荐这种架构。对于消息下行扇出压力较大的场景(比如:直播、大型聊天室),考虑到下行通道的压力较大,稳定性方面保障会差一些。这种情况可以将上行独立拆分出来,保证用户发消息不会受到消息下推压力大的影响。

    2019-11-29
    1
  • Dxn
    索引表为啥要插入两条记录?插入一条也是一样的啊

    作者回复: 主要是考虑各自索引维护时的独立性:比如一方删除了消息不影响另一方查看类似的需求。

    2019-11-03
    1
    1
  • 大智
    感谢老师分享。如分享中所说一条点对点消息的存储包括消息内容记录插入,双方各一条索引记录的插入,和最近联系人表的更新。如果消息内容插入其成功,其中一方索引记录插入成功,但另外一方索引记录插入失败的异常是如何处理的?多谢

    作者回复: 一般会先写缓存层,缓存层都成功的情况下,如果写有索引失败的情况可以先把失败的索引先写入到一个“失败队列”,由其他线程轮询尝试来写入。一般情况下,缓存层可以抗住db重试期间的数据可用性。

    2019-10-22
    1
  • Geek_9b1a66
    消息存储有什么推荐的数据库吗

    作者回复: 这个需要看具体的业务场景吧,比如考虑访问模型,数据量大小,读写的比例等等。在我们自己的场景里mysql和hbase,pika都有在使用。

    2019-08-30
    1
  • 小可
    1.由于索性表与内容表有关联,分库时两张表都应该按内容表id哈希,如果按用户id哈希,如果记录与内容不在一个库,获取消息时还要夸库查询,增加了系统复杂度,也会影响性能;
    不能合并,一般IM系统都有群发消息功能,如果内容表合并到索引表,那内容数据冗余就太多了,从而占用存储空间

    2.索引表与联系人表两个的功能设计不同
    索引表主要存储消息历史,联系人存储用户关系,如果两个表合并,那么获取联系人时要对所有索引表消息进行聚合才能获取到,性能大大降低;另外如果进行删除联系人操作,必须要将与该联系人的所有消息删除才可以,而联系人表独立的话只许将联系人删除,历史消息可根据需求是否就行删除,当然删除历史消息就可以异步执行了

    作者回复: 这里需要考虑一个问题哈:我们在查询两个人之间的历史消息的时候是用户维度的查询还是消息维度的查询?如果按消息id哈希,查询两个人之间的历史消息在只有uid的情况下该怎么查呢?

    2019-08-30
    2
    1
  • 小小小丶盘子
    老师,我之前用两个用户id,根据某种规则生成一个唯一会话id,然后外加一个发送人id,这样索引表和内容表用一个表就可以,这么做是否可行?有什么优缺点?

    作者回复: 单表单记录也是可行的,只是需要考虑清楚具体的使用场景:比如会话的某一方删除消息该怎么处理?如果业务需要类似邮箱的发件箱和收件箱这种设计是否方便索引查询?

    2019-08-30
    2
    1
  • 飞翔
    老师 组聊天 应该怎么存储呀
    2019-12-09
  • 康斯坦丁
    1. 消息存储中,内容表和索引表如果需要分库处理,应该按什么字段来哈希? 索引表可以和内容表合并成一个表吗
    用户id。
    不可以, 接收方 和 发送方 是不同的人. 有各自的业务.

    2. 能从索引表里获取到最近联系人所需要的信息,为什么还需要单独的联系人表呢?
    为了性能,如果通过索引表获取,需要进行聚合排序

    作者回复: 👍

    2019-11-05
  • Flash
    老师您好,请问联系人表,不需要存最近联系人的时间吗,
    最近联系人列表应该是有多条,最新的排在前面吧。

    作者回复: 这里说的联系人表其实就是存的最近联系人以及和这个联系人最新的一条消息,排序的话会按照最新一条消息来排。

    2019-10-20
  • threedr3am
    1. 用uid哈希,方便拉取聊天记录的时候,不需要跨库查询;可以合并,只要在消息内容表加两个字段,一个是发送uid,一个是接收uid就行了。
    2. 因为索引表里面会存在用户A发给用户B的所有聊天记录索引,如果想要获取到最近联系人列表以及最后一条消息,就需要先where查询出所有发送者为用户A的索引数据,然后再对接收者字段进行group by聚合,最后取最新消息,这样的操作在数据量庞大的表中时,性能会很差吧。
    2019-10-09
  • 飞翔
    老师 在开始聊天之前大家应该先成为好友吧。好友关系 怎么存呢

    作者回复: 好友关系存储上比较好处理,一般只需要双方uid,建立关系的时间,考虑到好友列表的使用场景(某个uid的所有好友列表),类似redis的sorted set结构是一个不错的选择。

    2019-09-28
  • null
    课后习题2:
    联系人列表中,只有小部分人是聊天的高频对象,需要频繁更新联系人表数据(更新最后的聊天记录时,只是锁行记录,不会影响其他用户的更新操作),而绝大部分用户都是读多写少的情况。
    另外,索引表的记录随着时间越来越多,每次都从索引表获取联系人列表,耗时也会越来越长,同时获取联系人列表,会阻塞消息写入,降低并发量。

    作者回复: 👍

    2019-09-27
    1
  • 天天平安
    老师您好,如果不想自己实现一个IM,考虑市面上收费的产品,请问哪个比较好,因为我现在急需在我们的App 上面加上聊天功能

    作者回复: 现在市面上很多IM云服务商呀,比如网易,腾讯,阿里,环信等等。

    2019-09-25
收起评论
51
返回
顶部