第十二章 设计聊天系统

理解问题并建立设计范围

  • 同时支持1对1和群组聊天
  • 手机应用和网络应用兼具
  • 支持5000万日活(5000DAU)
  • 群人数上限为100人
  • 功能包括 1对1聊天、群聊、在线状态、系统只支持文本消息
  • 文本长度应小于100000个字符
  • 聊天记录存储时间为永远
    了解需求之后总结聊天应用的特点:
  • 1对1聊天
  • 低延迟
  • 小组聊天
  • 在线状态
  • 多设备支持
  • 推送通知

提出高级设计

聊天系统本质是客户端们都连接到聊天服务,从而实现客户端与客户端通信。聊天服务支持以下功能:

  • 从其他客户端接收消息
  • 为每个消息找到正确的接收者,并将消息转发出去
  • 如果接收者不在线,服务器需要保存该消息直到接收者上线

网络协议的选择

HTTP协议这一常用的Web协议是发送方通信的不错选择,Facebook早期都是用它发送消息。客户端建立与聊天服务的HTTP连接后发送消息,启用keep-alve能允许客户端与聊天服务保持持久链接,同时减少TCP握手的数量。

由于HTTP是客户端起始的协议,从服务端发送消息相对会复杂一些。接下来讨论轮询、长轮询和WebSocket这些模拟服务器发起连接的技术。

轮询是一种客户端定期询问服务器是否有消息的技术。根据轮询频率,轮询可能会很昂贵,可能会消耗掉宝贵的服务器资源来回答一个大多数情况下不会提供答案的问题。

由于轮询可能效率低下,下一步是长轮询。客户端保持连接直到收到新的信息或到期。一旦接收到消息就会发送另一个请求给服务器,重启进程。长轮询有一些缺点:

  • 发送者和接受者在负载均衡下可能不会连接到相同的聊天服务器
  • 服务器没有有效方法来识别客户端是否断连
  • 不够高效,长连接在超时后周期性建立连接

WebSocket是从服务器向客户端发送异步更新的最常见解决方案。

  • 双向连接
  • 持久连接
  • 由HTTP连接“升级”而成,不受防火墙限制

高层设计

WebSocket因为其双向通信被选为客户端和服务器的主要通信协议,但是其余功能不一定。例如聊天应用中注册、登录、用户资料等都依赖于传统的HTTP的请求/响应方法。
聊天系统功能可以分为无状态服务、有状态服务、第三方集成三大类:

无状态服务

面向公众的传统请求/响应服务,用于管理登录、注册、用户配置文件等。
其位于负载平衡器之后,工作是根据请求路径将请求路由到正确的服务或者服务模块。
市面上已经有许多可以集成的服务了。接下来深入讨论的是服务之一是服务发现,也就是给客户端一个DNS主机名的列表,客户端可以连接到该列表中的聊天服务。

状态服务

该系统中唯一的状态服务是聊天服务,每个客户端都维护着雨聊天服务器的持久连接。在该服务中,只要服务器可用,客户端通常不会切换到另一个聊天服务器,服务发现与聊天服务密切协调以避免服务器过载,后续会深入讨论细节。

第三方集成

对于聊天应用而言,推送服务是最重要的三方集成,它保证应用没运行的时候也可以通知用户信息的到达。
ch12-1

服务器设计

  • 客户端: 与聊天服务器保持持久WebSocket连接,用于实时消息传递
  • 聊天服务器:发送/接收消息
  • 状态服务器:管理在线/离线状态
  • API服务器:处理所有操作,包括用户登录、注册、更改资料等
  • 通知服务器:发送推送通知
  • 键值对存储:存储聊天记录,当离线用户上线,可以看到之前所有聊天记录

存储

准备好服务器,服务运行正常且完成第三方集成后,通过检查数据类型读写模式确定数据库类型。
典型聊天系统中存在两种数据。

  1. 包括用户资料、设置、用户好友列表的通用数据,可以存储在健壮可靠的关系数据库中,使用复制和分片满足可用性和可扩展性。
  2. 聊天系统独有的聊天记录数据。对应读写模式如下:
    • 数据庞大
    • 最近的聊天记录访问频繁
    • 用户可能会使用需要随机访问数据的功能,例如搜索、查看@和跳转到特定消息等。由数据访问层支持。
    • 1对1聊天应用中读写比约为1:1
      推荐使用键值存储原因如下:
  • 键值存储可轻松实现水平扩展(即使用一致性哈希)
  • 可以提供非常低的数据访问延迟
  • 关系数据库无法很好处理长尾数据,索引很大时随机访问很慢
  • 已经被其他验证的可靠聊天应用程序采用,例如Facebook的Hbase和Discord的Cassandra

数据模型

数据消息由主键Message_ID、发送者、接受者、内容、创建时间组成。由于同时可能有两条消息被创建,不能依赖创建时间决定ID顺序。对应主键ID有着随时间顺序和唯一两个特性。实现这两个保证有auto_increment关键字,但是NoSQL数据库通常不支持。第二种就是使用64位序列号生成器,例如雪花算法。最后也可以使用本地序列号生成器,本地可以保证在一个组内唯一,可以实现在一对一信道或一个组信道中维护消息顺序。

深入探讨设计

在系统设计需要深入探讨高级设计中的一些组件。对于聊天系统,服务发现、消息传递流程和在线/离线指标是值得关注的组件。

服务发现

其主要作用是根据地理位置和服务器容量等标准,为客户端推荐最佳聊天服务器。Apache Zookeeper就是一个开源服务方案,它注册所有可用的聊天服务器,并根据预定义的标准为客户端选择最佳聊天服务器。运行流程如下:

  1. 用户A尝试登录到应用程序
  2. 负载均衡将请求发送到API服务器
  3. 后端验证用户后,调用服务发现为UserA找到最佳聊天服务器,并返回服务器信息
  4. 用户A通过WebSocket连接到聊天服务器

消息流

本小节探索1对1聊天流程、跨多设备的消息同步和群聊流程。

1对1聊天流程

  1. 用户A发送聊天消息到聊天服务器1
  2. 聊天服务器1用ID生成器成生一个消息ID
  3. 聊天服务器将其发送到消息同步队列
  4. 消息存储到k-v存储中
  5. 如果用户B在线,消息会被发送到用户B所连接的服务器,然后通过持久websocket连接传递。
  6. 如果用户B离线,会通过消息推送服务器发送推送通知

多设备之间的消息同步

同一个用户的多个设备都会与聊天服务器建立WebSocket连接。每个设备只需要维护一个变量来维护当前设备追踪的最近的消息ID。当消息满足以下条件可视为新的消息:

  • 接受者ID与当前登录用户ID相同
  • k-v存储中的消息ID比当前维护的消息ID大
    有着不同最新ID的设备也可以通过消息同步从KV存储中获取到新消息

群聊流程

来自用户A的消息会被同时加入到成员B和成员C对应的消息同步队列中。每个成员存在自己的消息同步队列,这对于小群十分有利:

  • 简化了消息同步流,每个客户端只需要从自己的盒子里获取消息
  • 当成员较少的时候,每个成员盒子中复制一份消息代价并不大
    微信就使用了类似的方法,其限制群大小为500。对于大群来说,这个复制代价过高。

在线状态指示器

在高级设计中,“在线服务器”负责管理在线状态,并通过WebSocket与客户端进行通信,我们终点关注触发在线状态变化的流程。

用户登录

用户登录流程在“服务发现”部分进行了说明。在客户端与实时服务建立WebSocket连接后,用户A的在线状态和last_active_at时间戳保存在KV存储中。存在指示符在用户登录后显示用户在线。

用户登出

用户登出时候,用户通过API服务器与在线服务器交互,将KV存储中的在线状态修改为离线状态,存在指示符会显示用户为离线状态。

然而互联网并不会保持一致性和可靠性,所以我们需要额外处理这个问题。传统方式是当用户从互联网断开,客户端与服务器之间的持久连接丢失就将用户标记为离线,并重新连接时表示为在线。然而当用户经常在短时间内频繁断开和重新连接时,例如使用VPN时候,过于频繁更改状态会影响用户体验。

解决方法是引入心跳机制。在线客户端定期向服务器发送心跳事件,如果服务器在一定时间内收到心跳,则认为用户在线,否则离线。

在线状态分发

在线服务器维护着一个发布-订阅模型,每一对朋友关系都存在一个信道,当用户A的在线状态发生改变时,就会向A-B,A-C和A-D三条信道发送消息。这三条信道分别被用户BCD订阅。朋友与服务器的连接也依赖于实时的WebSocket,也能轻易获取到在线状态的变更。

对于少用户群,以上设计十分有效,例如微信限制群人数小于500。对于更大群的情况,一个可能的解决方案是仅在用户进入群组或手动刷新好友列表时才检索在线状态。

总结

本章介绍了支持1对1聊天和群组聊天的聊天系统架构。使用WebSocket用于实时通信。系统包含:

  • 用于实时聊天的聊天服务器
  • 用于管理在线状态的在线服务器
  • 用于发送推送通知的通知服务器
  • 用于聊天历史记录持久化的键值对服务器
  • 用于其他功能的API服务器
    可额外扩充的部分:
  • 扩展应用程序到媒体文件,如照片和视频。由于媒体文件更大,压缩、云存储和缩略图是需要考虑的重点
  • 端到端加密,保证只有发送者和接收者可以阅读消息的端到端加密
  • 在客户端缓存消息来减少客户端的通信压力
  • 改进加载时间。Slack构建地理分布式网络来缓存用户数据和频道等
  • 错误处理
  • 聊天服务器离线。使用服务发现来处理聊天服务器离线的后续处理
  • 消息重发机制。使用重试和队列是重发消息的重建技术
posted @ 2025-07-24 00:01  tanch25  阅读(23)  评论(0)    收藏  举报