系统设计面试笔记 第 10 章

第 10 章 设计通知系统

简介

通知系统(Notification System)对现代应用至关重要,它能及时推送各类更新,例如产品通知、活动、优惠和提醒。通知可以通过以下方式发送:

  1. 推送通知(Push notifications)(移动端或桌面端),
  2. 短信(SMS messages),以及
  3. 电子邮件(Emails)。

本章重点设计一个可扩展的系统,能够每天发送数百万条通知。


第 1 步:理解问题

需求

  • 通知类型: 推送通知、短信和电子邮件。
  • 送达: 软实时(Soft real-time)系统,延迟尽可能小。
  • 平台: iOS、Android 和桌面端。
  • 触发方式: 通知可以由客户端应用触发,也可以在服务端定时发送。
  • 规模:
    • 推送通知: 每天 1000 万条,
    • 短信: 每天 100 万条,
    • 电子邮件: 每天 500 万条。
  • 支持退订(Opt-out): 用户可以关闭特定类型的通知。

第 2 步:高层设计

组件

  1. 通知类型:

    • iOS 推送通知: 使用 Apple 推送通知服务(Apple Push Notification Service,APNS)。
    • Android 推送通知: 使用 Firebase 云消息传递(Firebase Cloud Messaging,FCM)。
    • 短信: 使用 Twilio 或 Nexmo 等第三方服务。
    • 电子邮件: 使用 SendGrid 或 Mailchimp 等商业邮件服务。
  2. 联系信息收集:

    联系信息收集

    • 在应用安装或用户注册时收集设备令牌(device token)、电话号码或电子邮件地址。
    • 将联系信息存储在数据库中:
      • 设备令牌表: 用于推送通知。
      • 用户表: 用于电子邮件和电话号码。
  3. 通知发送流程:

    高层设计

    • 触发服务(Trigger Services):
      • 生成事件以发起通知(例如账单提醒、物流更新)。
      • 一个服务可以是一个微服务、一个定时任务(cron job),或者一个触发通知发送事件的分布式系统。
    • 通知服务器(Notification Server):
      • 为各服务提供发送通知的 API。
      • 进行基本校验,验证电子邮件和电话号码。
      • 查询数据库或缓存,获取渲染通知所需的数据。
    • 第三方服务: 将通知送达用户。

初始设计中的问题

  • 单点故障(Single Point of Failure,SPOF): 一台通知服务器宕机就可能导致整个系统崩溃。
  • 扩展性问题: 难以对数据库、缓存和处理组件分别进行独立扩展。
  • 性能瓶颈: 发送通知需要消耗大量资源。

改进后的设计

改进后的设计
  • 将数据库和缓存移出通知服务器。
  • 引入水平扩展(horizontal scaling),部署多台通知服务器。
  • 使用消息队列(message queue)解耦系统组件。
    • 当需要发送大量通知时,消息队列充当缓冲区。
  • 增加工作节点(worker),从消息队列中拉取通知事件,并将其发送给相应的第三方服务。

第 3 步:深入设计

可靠性

  1. 防止数据丢失:

    数据丢失

    • 将通知数据持久化到数据库中,并实现重试机制。
    • 引入通知日志数据库来实现数据持久化。
  2. 去重(Deduplication):

    • 检查事件 ID,避免发送重复通知。
    • 当一个通知事件首次到达时,通过检查事件 ID 判断之前是否见过它。 如果见过则丢弃,否则发送该通知。

其他组件

事件追踪
  1. 通知模板: 预先格式化的模板,使通知保持一致并提高效率。
  2. 通知设置:
    • 用户可以选择订阅或退订特定渠道(推送、短信或电子邮件)。
    • 存储在专门的通知设置表中。
  3. 限流(Rate Limiting): 限制向用户发送通知的频率。
  4. 重试机制: 当第三方服务失败时,重新发送通知。
  5. 监控队列: 追踪排队中的通知数量,以动态扩缩工作节点。
  6. 事件追踪: 收集打开率、点击率和参与度等指标。

安全

  • 使用 AppKey 和 AppSecret 对推送通知的 API 进行认证和保护。

通知流程

更新后的设计
  1. 触发服务调用 API 发送通知。
  2. 通知服务器校验请求,并从缓存或数据库中获取元数据。
  3. 通知事件被发送到消息队列。
  4. 工作节点处理事件,并与第三方服务交互。
  5. 第三方服务将通知送达用户。

关键优化

  1. 水平扩展: 增加通知服务器以分摊负载。
  2. 消息队列: 解耦处理流程,以应对大流量。
  3. 缓存: 缓存频繁访问的数据以降低延迟。
  4. 分布式抓取: 在地理上优化消息投递,以获得更好的性能。