Skip to content

四火的唠叨

一个纯正程序员的啰嗦

Menu
  • 所有文章
  • About Me
  • 关于四火
  • 旅行映像
  • 独立游戏
  • 资源链接
Menu

常见分布式应用系统设计图解(一):即时消息系统

Posted on 08/30/202010/14/2023 by 四火

在自己学习各种各样软件系统,特别是分布式系统的过程中,我做了一些笔记,有许多常见的、经典的系统,是非常值得学习和总结的。它们数量不算多,但具有典型意义,可能这样的系统也就十几个。

我在回顾这些笔记的时候发现,有时候一张简单的图,包含最核心的几个设计,就可以很大程度地帮助理解和记忆。所以我想把这些笔记和图解的结合通过文章的形式发出来,预计每篇文章都很短,基本上一张图,加上一点说明性的文字。

Disclaimer:这些都来自我自己的阅读和理解,肯定有着相当的改变和简化,因此它并不代表任何系统实际的样子。

今天是第一篇,即时消息系统,但是基本上好多即时通讯软件都属于这一类,比如微信,比如 QQ,比如 Facebook Messenger,比如 WhatsApp。

  • 用户发送消息,直接发给 Chat Service,它会做少量的处理并持久化,然后放到基于 channel 的队列中,每一个对话(thread)都会有一个 channel,这个过程中,它并不关心这个对话有多少人参与(支持群聊)。
  • 队列有两类消费者,一类是 Message Service 用来服务在线用户。由于单台机器和客户端的连接数量有限(比如小于 2^16=65536),因此 Message Service 需要有很多机器,根据用户的 id 来 sharding,它们去订阅自己感兴趣的频道,有新的消息就发送给用户。
  • Message Service 获取客户端的心跳,保持来自客户端的连接(long polling 或者 socket)为了实时性肯定要用 push 模型。因此它知道用户的当前在线状态,也知道最后一条发送成功消息的时间戳(状态)。这个状态可以用于决定用户离线时消息是否要通过其它方式通知用户。
  • 还有一类是 Notification Service 用来发离线通知。
  • Chat Service 有两个职责,一个是处理发消息的请求,一个是接纳读取历史消息的请求,这两个功能可以分成两个组件,也可以一个组件,我放在一起了。
  • 右侧的消息数据库,RDB 往往不太适合,因为消息数量太大,对于一组对话(thread)的展示,需要找到该对话 N 条最近的记录,行数据库效率较低,可以考虑列数据库,比如 HBase。这种方式下,同一 thread 下的消息都是按时序存放在一起的,读的效率非常高,写因为基本是 append,也很方便。
  • 用户数据的存储,可以使用 RDB,也可以使用 KV 数据库。 这里面存放的数据库表包括:用户表;对话表;用户对话关联表:二者是 M:N 的关系,并且每个用户都可以有对于特定对话的设置,例如设置对话中的昵称,是否屏蔽消息通知等等。
  • 对于图中基于 Channel 的队列,把数据 fanout 给下游,它有几个作用,一个是解耦,把消息发送和消息接收分离开,消息发送者可能只管把消息发到群里,但是并不关心这个群应该有几个用户得到通知;第二个是缓冲,无论是为离线用户服务的 Notification Service,还是为在线用户服务的 Message Service,它们消费数据的速率是无法确定的。
  • 对于用户上线、下线的实现,其实也类似,上线、下线的事件可以推送到一个特定的 Channel 里面。用户的好友,也就是感兴趣的 Notification Service 的个体去订阅消息;还有一种思路是把状态更新到用户表里面,这样所有人都可以查询得到,这后一种方式适合非好友也要查看用户状态的情况。上、下线需要保留缓冲时间,容许一定状态的延迟,没必要,也不应过于实时。

这是《常见分布式系统设计图解》系列文章中的一篇,如果你感兴趣,请参阅汇总(目录)寻找你其它感兴趣的内容。

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《四火的唠叨》

×Scan to share with WeChat

你可能也喜欢看:

  1. 常见分布式应用系统设计图解(八):文件同步分享系统
  2. 常见分布式应用系统设计图解(十):电商秒杀系统
  3. 常见分布式应用系统设计图解(十五):支付系统
  4. 常见分布式应用系统设计图解(十四):日志系统
  5. 常见分布式应用系统设计图解(九):协同编辑系统

8 thoughts on “常见分布式应用系统设计图解(一):即时消息系统”

  1. lyf says:
    04/26/2021 at 5:00 PM

    第一条有错误,单个机器连接限制不是 2^16, 连接标识是一个五元组,(协议,收发 ip 和端口)

    Reply
  2. 井底之蛙 says:
    11/07/2020 at 10:32 PM

    谢谢您的总结。真的总结的太好了。我在这里有个问题:
    为什么 send message 是直接 send 给 chat service 而不走 socket 呢?messaging 应该是一个读写平衡的 service。感觉没有特别大的理由需要把 read 和 write 的逻辑分开?
    谢谢!

    Reply
    1. 四火 says:
      11/12/2020 at 9:22 AM

      好问题,我觉得都可以吧。我这样画只有一些不太强的理由,比如我觉得这个消息发送并不算是那种典型的小数据大量发送的情形,因此发送不需要长连接(但在另外的一些系统中,比如 https://www.raychase.net/6429 就如你所愿了)。我不知道有没有更好的想法。

      Reply
  3. Alan says:
    08/31/2020 at 2:27 PM

    Channel/Queue 这里可以深入一点, 是类似 kafka 一样的 queue 吗?比如在用户离线的情况下如何处理 queue 里积压的信息?

    Reply
    1. 四火 says:
      08/31/2020 at 10:58 PM

      我的理解是,和 Kafka 这样的比起来,还是明显有区别的,因为 ChatService 负责所有消息的持久化了,要是这里再做一遍就重复了,这里其实主要还是一个 channel 的功能,消息应该是暂存(缓存)给 PushService 即时读取之用的,因为数据 push 过程对于速率无法保证,那么数据消费的速率也就无法保证,同时数据也可能被多台 PushService 机器使用(比如群聊的情况),因此使用这个 channel 来共享、缓存待推送新鲜数据。为此我给文中增加了一点描述。

      因此离线情况下,消息可以通过其它方式推送,上面的 channel 里面不会积压信息,即便丢失也没有关系,因为消息是持久化在右侧的数据库里面的。等到用户再上线的时候,会发送给 ChatService 一个时间戳(这个戳可以是客户端保存,也可以存放在服务端的用户系统里),这样 ChatService 就可以推送相应的该时间戳之后的积压的消息。

      Reply
      1. cjl says:
        03/09/2021 at 6:54 PM

        Notification Service 获取客户端的心跳,保持来自客户端的连接(long polling 或者 socket)为了实时性肯定要用 pull 模型,不能用 push 模型。 这里有点不理解,为了实时性不是更应该用 push 模型吗?

        Reply
        1. 四火 says:
          03/09/2021 at 11:15 PM

          你说的对,我写错了。这就修改

          Reply
      2. 四火 says:
        08/06/2022 at 10:44 PM

        Update:做了修改,现在的图示应该更加合理了。

        Reply

Leave a Reply to Alan Cancel reply

Your email address will not be published. Required fields are marked *

订阅·联系

四火,啰嗦的程序员一枚,现居西雅图

Amazon Google Groovy Hadoop Haskell Java JavaScript LeetCode Oracle Spark 互联网 亚马逊 前端 华为 历史 同步 团队 图解笔记 基础设施 工作 工作流 工具 工程师 应用系统 异步 微博 思考 技术 数据库 曼联 测试 生活 眼界 程序员 管理 系统设计 缓存 编程范型 美股 英语 西雅图 设计 问题 面向对象 面试

分类

  • Algorithm and Data Structure (30)
  • Concurrency and Asynchronization (6)
  • System Architecture and Design (43)
  • Distributed System (18)
  • Tools Frameworks and Libs (13)
  • Storage and Data Access (8)
  • Front-end Development (33)
  • Programming Languages and Paradigms (55)
  • Testing and Quality Assurance (4)
  • Network and Communication (6)
  • Authentication and Authorization (6)
  • Automation and Operation Excellence (13)
  • Machine Learning and Artificial Intelligence (6)
  • Product Design (7)
  • Hiring and Interviews (14)
  • Project and Team Management (14)
  • Engineering Culture (17)
  • Critical Thinking (25)
  • Career Growth (57)
  • Life Experience and Thoughts (45)

推荐文章

  • 谈谈分布式锁
  • 常见分布式系统设计图解(汇总)
  • 系统设计中的快速估算技巧
  • 从链表存在环的问题说起
  • 技术面试中,什么样的问题才是好问题?
  • 从物理时钟到逻辑时钟
  • 近期面试观摩的一些思考
  • RSA 背后的算法
  • 谈谈 Ops(汇总 + 最终篇):工具和实践
  • 不要让业务牵着鼻子走
  • 倔强的程序员
  • 谈谈微信的信息流
  • 评审的艺术——谈谈现实中的代码评审
  • Blog 安全问题小记
  • 求第 K 个数的问题
  • 一些前端框架的比较(下)——Ember.js 和 React
  • 一些前端框架的比较(上)——GWT、AngularJS 和 Backbone.js
  • 工作流系统的设计
  • Spark 的性能调优
  • “残酷” 的事实
  • 七年工作,几个故事
  • 从 Java 和 JavaScript 来学习 Haskell 和 Groovy(汇总)
  • 一道随机数题目的求解
  • 层次
  • Dynamo 的实现技术和去中心化
  • 也谈谈全栈工程师
  • 多重继承的演变
  • 编程范型:工具的选择
  • GWT 初体验
  • java.util.concurrent 并发包诸类概览
  • 从 DCL 的对象安全发布谈起
  • 不同团队的困惑
  • 不适合 Hadoop 解决的问题
  • 留心那些潜在的系统设计问题
  • 再谈大楼扔鸡蛋的问题
  • 几种华丽无比的开发方式
  • 我眼中的工程师文化
  • 观点的碰撞
  • 谈谈盗版软件问题
  • 对几个软件开发传统观点的质疑和反驳
  • MVC 框架的映射和解耦
  • 编程的未来
  • DAO 的演进
  • 致那些自嘲码农的苦逼程序员
  • Java 多线程发展简史
  • 珍爱生命,远离微博
  • 网站性能优化的三重境界
  • OSCache 框架源码解析
  • “ 你不适合做程序员”
  • 画圆画方的故事

近期评论

  • panshenlian.com on 初涉 ML Workflow 系统:Kubeflow Pipelines、Flyte 和 Metaflow
  • panzhixiang on 关于近期求职的近况和思考
  • Anonymous on 闲聊投资:亲自体验和护城河
  • 四火 on 关于近期求职的近况和思考
  • YC on 关于近期求职的近况和思考
  • mafulong on 常见分布式基础设施系统设计图解(四):分布式工作流系统
  • 四火 on 常见分布式基础设施系统设计图解(八):分布式键值存储系统
  • Anonymous on 我裸辞了
  • https://umlcn.com on 资源链接
  • Anonymous on 我裸辞了
© 2025 四火的唠叨 | Powered by Minimalist Blog WordPress Theme
Menu
  • 所有文章
  • About Me
  • 关于四火
  • 旅行映像
  • 独立游戏
  • 资源链接