02 | 消息收发架构:为你的App,加上实时通信功能
02 | 消息收发架构:为你的App,加上实时通信功能
讲述:袁武林
时长16:28大小11.29M
消息存储
消息索引和消息内容
联系人列表
消息收发通道
消息发送通道
消息接收通道
消息未读数
小结
赞 13
提建议
精选留言(73)
- 王棕生2019-08-301. 消息存储中,内容表和索引表如果需要分库处理,应该按什么字段来哈希? 索引表可以和内容表合并成一个表吗? 答: 内容表应该按主键消息ID来哈希做分库分表处理,这样便于定位某一条具体的消息;索引表应该按索引的用户UID来哈希做分库分表处理,这样可以使得当前用户的所有联系人都落在一张表上,减少遍历所有表的麻烦。 索引表可以与内容表合成一张表,好处是显而易见的,能减少拉取历史消息时的数据库IO,不好的地方就是消息内容冗余存储,浪费了空间。 2. 能从索引表里获取到最近联系人所需要的信息,为什么还需要单独的联系人表呢? 答: 如果从索引表中获取一个用户的所有联系人信息(包括最后一条聊天内容和时间)的话,SQL语句中会有分组后取top 1的操作,性能不理想; 另外当前用户与单个联系人之间的未读数需要维护,用联系人表的一个字段来存储,比用索引表方便许多。展开
作者回复: 👍
共 5 条评论102 - Julien2019-08-30每节课的思考题可以在下一节课开始之前揭晓吗?谢谢。38
- hlai2019-11-29最后张图,客户端发消息到服务是通过API, 请问为什么不可以长连接么发过去呢?
作者回复: 这里说一下:消息的收发是可以通过同一个长连通道来实现的,对于普通的聊天场景我甚至更推荐这种架构。对于消息下行扇出压力较大的场景(比如:直播、大型聊天室),考虑到下行通道的压力较大,稳定性方面保障会差一些。这种情况可以将上行独立拆分出来,保证用户发消息不会受到消息下推压力大的影响。
16 - hgf2019-09-07可能行为: 1. 发送消息 2. 查看(历史)消息 3. 联系人列表最新一条消息 4. 未读消息数 内容表应该按照消息ID索引来hash,原因:使得数据分布均匀,相对使用产生时间维度做hash更能均衡系统的查询压力,很少有人查询到几个月前的数据,绝大部部分人查看历史,都会查看最近1~3天内的。 消息索引表按照索引用户ID来hash,原因:将索引用户相关的消息记录hash到相同的表,处理比较方便,避免跨表查询。 为什么不要将消息内容表和消息索引表合并? 会造成消息存储冗余,特别是群聊的时候。 为什么消息索引表需要同时记录发送方-> 接收方;接收方-> 发送方的索引?因为在分库分表的情况下,可能依据索引用户ID进行hash,如果仅记录一条记录,例如,仅记录了发送方-> 接收方索引,那么所有发送法相同的索引会hash到同样的表,那么接受方在查询会话记录时,需要从多个库表中查询,反之,也是类似情况。 为什么需要单独的联系人表? 联系人列表中,仅仅需要最新一次的消息,如果从消息索引表中查询所有联系人的最新一次的消息,那么需要执行group by,order by 等操作,因为在消息索引表的记录数是远大于联系人表的,查询效率上差距明显,存储端的压力也较大展开共 1 条评论13
- 小祺2019-09-03消息表和索引表合并后表结构: 发消息用户ID ,收消息用户ID, 消息状态, 消息内容,消息类型,消息产生时间 其中消息状态 0: 正常 1:发消息用户已删除 2: 收消息用户已删除 如果两个用户都删除消息,那这条记录就被delete掉。 既可以满足合并消息,又可以满足单个用户删除消息,请问这样设计是否可行?
作者回复: 处理删除和合并上没问题哈,需要考虑一下分库分表场景下,收发双方查询历史消息应该怎么查呢?
共 6 条评论4 - 关汉聪2019-12-18老师,您的一个回复中写到:“一般会先写缓存层,缓存层都成功的情况下,如果写有索引失败的情况可以先把失败的索引先写入到一个“失败队列”,由其他线程轮询尝试来写入。一般情况下,缓存层可以抗住db重试期间的数据可用性。” 想问下,缓存一般都是缓解读压力用的,这里是为了缓解写压力,那么: 1. 缓存服务器也是要主从或者集群吗,这样每次写入都保证主从缓存都写入后,在写入DB之前,返回消息发送方,成功的ACK,对吗? 2. 如果缓存成功,索引写入DB失败,在“失败队列”被轮询的过程中,消息发送方接下来的每一条消息,为了保证消息的顺序,是不是都要等待之前失败的消息写入DB后,接下来才能发送给消息接收方? 谢谢老师。展开
作者回复: 1. 缓存在这里的作用主要是快速响应用户的写入请求,规避同步写db的重操作。缓存服务器是否需要采取主从集群可以根据实际访问量来评估,一般写完主后即可返回,由主负责同步消息到从。 2. 由于消息id和产生时间在写入缓存前就已经生成好,失败队列重试导致的顺序错乱不影响最终消息到达客户端后的重排序。
共 2 条评论3 - Dxn2019-11-03索引表为啥要插入两条记录?插入一条也是一样的啊
作者回复: 主要是考虑各自索引维护时的独立性:比如一方删除了消息不影响另一方查看类似的需求。
共 3 条评论3 - 6662019-09-13想了解一下像微博这类消息系统如果解决大V用户的消息收发的?比如用户给大v点赞、评论或者是私信
作者回复: 点赞、评论这些目前采用的拉取模式,用户打开具体的赞箱和评论箱时才从服务端获取;私信是支持在线推送的,不过为了减少对大V的骚扰,对于未关注人私信没有系统push。
3 - Alber2019-08-30消息存储有什么推荐的数据库吗
作者回复: 这个需要看具体的业务场景吧,比如考虑访问模型,数据量大小,读写的比例等等。在我们自己的场景里mysql和hbase,pika都有在使用。
3 - Colin2019-08-30请教各位评论区的大神们:消息未读数更新时机大家都是怎么设计的?比如说:发一条消息,未读消息+1这个操作大家都是如何更新的?如果是在数据库更新,再同步到客户端,可能会出现消息到达了,未读数还没更新的现象,如果是在客户端+1,再保存到数据库,别人写个脚本就能把数据给改乱了,不安全。应该如何设计比较合理?共 4 条评论3
- 小袁2019-09-07索引表按照聊天对像的id来哈希,这样和某个对象的聊天记录可以落在一个库中,没问题。 内容表我认为可以按时间分库,实际查询时一般都是从最近的聊天记录开始往前翻,或者看某一天的记录,这样按连续时间段来查询,不会只单独看一条记录吧。 如果按消息id分库那岂不是翻某天的聊天记录可能要查好几个库了吗?展开
作者回复: 按时间段来查询没问题,但是消息写入的时候就会都落到一个表里,写入压力会有问题,但内容表实现上确实可以在hash完后按时间进行分表。
2 - 挨踢菜鸟2019-08-30老师,请问websocket如何多实例部署,如何解决实例重启造成连接断开的问题
作者回复: 没太理解到您的意思哈,websocket网关只有是无状态的多实例部署没啥问题的呀;实例重启断开连接是肯定的,需要解决的是断开后客户端需要有重连机制以及如果尽量减少实例重启的概率。
共 9 条评论2 - 小可2019-08-301.由于索性表与内容表有关联,分库时两张表都应该按内容表id哈希,如果按用户id哈希,如果记录与内容不在一个库,获取消息时还要夸库查询,增加了系统复杂度,也会影响性能; 不能合并,一般IM系统都有群发消息功能,如果内容表合并到索引表,那内容数据冗余就太多了,从而占用存储空间 2.索引表与联系人表两个的功能设计不同 索引表主要存储消息历史,联系人存储用户关系,如果两个表合并,那么获取联系人时要对所有索引表消息进行聚合才能获取到,性能大大降低;另外如果进行删除联系人操作,必须要将与该联系人的所有消息删除才可以,而联系人表独立的话只许将联系人删除,历史消息可根据需求是否就行删除,当然删除历史消息就可以异步执行了展开
作者回复: 这里需要考虑一个问题哈:我们在查询两个人之间的历史消息的时候是用户维度的查询还是消息维度的查询?如果按消息id哈希,查询两个人之间的历史消息在只有uid的情况下该怎么查呢?
共 3 条评论2 - 阿基米德2020-07-18根据咱们的分库分表方式,用户在获取历史消息的时候接口如何设计呢,因为用户展示的历史既有收件箱的消息也有发件箱的消息,这两个消息要按照消息id顺序展示,这个合并的过程是客户端做还是服务端坐呢1
- 大智2019-10-22感谢老师分享。如分享中所说一条点对点消息的存储包括消息内容记录插入,双方各一条索引记录的插入,和最近联系人表的更新。如果消息内容插入其成功,其中一方索引记录插入成功,但另外一方索引记录插入失败的异常是如何处理的?多谢
作者回复: 一般会先写缓存层,缓存层都成功的情况下,如果写有索引失败的情况可以先把失败的索引先写入到一个“失败队列”,由其他线程轮询尝试来写入。一般情况下,缓存层可以抗住db重试期间的数据可用性。
1 - asdf1002019-09-02对于内容表根据主键字段分库分表的话,如果用户查看最近一段的消息的时候,不是从多个地方分别获取数据再聚合了么,这样也有弊端的吧?
作者回复: 是个好问题,内容表的获取在拿到消息id后可以采取并发获取的方式哈,但是一页一般消息id是有限的(比如20),但分库分表的表数量会很多,上千都很常见。
1 - iMARS2019-09-02按照消息ID取模进行负载分摊,如果按照用户ID会出现分布不均的问题。其次,不建议把消息内容和索引表合并。对于消息内容存储的需求和关系型数据库不一定相同,分离后,可考虑使用非关系型数据库管理,当然会有一些运维和数据关联查询上的需求。
作者回复: 考虑下消息索引表的访问模式哈,对于索引表最终要求的是尽量能提升获取的效率,所以如果需要有索引表,应该是uid维度的查询会更高效一些。
1 - Leon📷2019-09-02之间面试被问到怎么设计存储群聊的聊天记录,保证聊天记录的有序性,没答出来,老师帮忙解答下
作者回复: 一般来说群聊消息的存储上主要是考虑存储冗余的问题,一个群的消息只需要存一份即可,每条消息id通过自增序号或者“时间维度相关”的发号器来生成,通过这个id排序即可。群聊相关的话题后续课程还会细讲哈
1 - 小小小丶盘子2019-08-30老师,我之前用两个用户id,根据某种规则生成一个唯一会话id,然后外加一个发送人id,这样索引表和内容表用一个表就可以,这么做是否可行?有什么优缺点?
作者回复: 单表单记录也是可行的,只是需要考虑清楚具体的使用场景:比如会话的某一方删除消息该怎么处理?如果业务需要类似邮箱的发件箱和收件箱这种设计是否方便索引查询?
共 3 条评论1 - 追风筝的人2022-09-09 来自陕西1. 发送方 user ID,不能合并 2.分库分表 提高查询效率