系统设计-Ch10-设计一个通知系统

设计一个通知系统

近年来,通知系统已经成为许多应用中非常流行的功能。通知提醒用户重要信息,如突发新闻,产品更新等。
它已成为我们日常生活中不可或缺的一部分。在本章中,您被要求设计一个通知系统。

通知一共有三种类型,分别是手机推送通知,短信和电子邮件,下图显示了每种通知的示例:

Step1:Understand the problem and establish design scope

对于”设计一个通知系统“这个系统设计话题,我们可以向面试官提出以下问题:

  • Q:系统支持哪些类型的通知?

    A:手机推送通知,短信和电子邮件

  • Q:这是一个实时系统吗?

    A:可以说是一个软实时系统。我们希望用户尽快收到通知,但当系统处于高负载的情况下允许轻微的延迟

  • Q:系统支持哪些设备?

    A:iOS设备、android设备和笔记本电脑/台式。

  • Q:通知由什么触发?

    A:通知可以由客户端应用程序触发,也可以在服务器端进行调度。

  • Q:用户可以选择关闭通知吗?

    A:是的,选择关闭通知的用户将不再收到通知。

  • Q:每天发出多少份通知?

    A:1000W条手机推送通知、100W条短信、500W封7电子邮件

Step2:Propose high-level design and get buy-in

本小节展示了支持各种通知类型的高级设计:

  • 不同类型的通知(Different types of notification)
  • 用户联系方手收集流程(Contact info gathering flow)
  • 通知发送/接收流程(Notification sending/receiving flow)

Different types of notification

首先来看不同类型的通知:

iOS push notification

我们主要需要三个组件来发送iOS推送通知:

  • Provider:供应商构建通知请求并将其发送到APNS(Apple Push Notification Service),要构建推送通知,供应商需要提供以下数据:
    • Device Token(设备令牌):用于发送推送给通知的唯一标识符

    • Payload():是一个JSON字典,包含推送的有效负载。下面是一个payload示例:

      {
          "to": [
            {
                  "user_id": 123456
              }
          ],
          "from": {
              "email": "from_address@example.com"
          },
          "subject": "Hello World!",
          "content": [
              {
                  "type": "text/plain",
                  "value": "Hello World!"
              }
          ]
      }

      你可以把payload理解为一系列信息中最为关键的信息

      例如:胶囊里的药粉就是payload,外面的皮是为了保证中间药粉的完好有效。

  • APNS:苹果公司提供的一项远程服务,用于向iOS设备发送通知
  • iOS:是接收推送通知的客户端

Android push notification

安卓采用了与iOS类似的通知推送流程。只不过中间的是FCM(Firebase Cloud Messaging) 而不是APNS。

SMS message

对于SMS(短信服务),通常使用第三方SMS服务,如Twilio、Nexmo,都是商业(付费)服务。

Email

对于电子邮件,公司可以自建电子邮件服务器,但是也有许多公司选择了商业(付费)电子服务,如Sendgrid和Mailchimp,它们提供了更好的推送效率和数据分析。

Contact info gathering flow

要发送通知,我们需要收集移动设备令牌,电话号码或邮箱地址,如下图所示:

当用户首次安装注册我们的应用时,API服务器会收集用户联系方式并将其存储到数据库中。


下图显示了用于存储联系人信息的简化数据库表:

邮箱(email)和电话号码(phone_number)存储在用户表,移动设备令牌存储在设备表(通过user_id关联),这表明一个用户可能有多个设备,因此可以向多个设备发通知。

Notification sending/receiving flow

我们首先介绍一下通知推送的初始high-level设计,然后推荐一些优化方式。

Hig-level Design

下图展示了通知推送系统的high-level design,你会看到一些必要的系统组件:

  • Service 1 to N:服务可以是微服务,cron job,或是一个用于触发通知推送事件的分布式系统。例如,计费服务发送电子邮件以提醒客户应该付款,或者购物网站告诉客户他们的包裹将在明天到达快递点等等。
  • Notification System:通知系统(Notification System)是发送/接收通知的核心。从简单的事情开始,只是用一个通知服务器。它为Service 1 to N提供API,并且为第三方服务构建Notification payload。
  • Tird-party Services:第三方服务负责向用户发送通知。在与第三方服务集成时,需要格外注意可扩展性,当前使用的第三方服务可能在未来不可用或被其它第三方服务替代,良好的可扩展性意味着可插拔的第三方服务。
  • iOS,Amdroid,SMS,Email:用户在他们自己的设备上收到通知推送。

这个设计中存在三个问题:

  1. Single point of failue(SPOF,单点故障):单个通知服务器意味着单点故障
  2. Hard to scale(难以扩展):通知系统在一台服务器中处理与推送通知相关的一切。独立扩展数据库、缓存和不同的通知处理组件具有挑战性。
  3. Performance bottleneck(性能瓶颈):处理和发送通知可能会占用大量资源。例如,构建HTML页面和等待第三方服务的响应可能会占用大量时间。

(本质上都是单点服务器的问题,喊着你上分布式呢!)

Hig-level Design(improved)

我们对设计进行了改进,如下图所示:

  • 将数据库(DB)和缓存(Cache)移出服务器:
  • 添加更多通知服务器,并设置自动横向扩展:
  • 引入消息队列解耦系统组件:我们给每种通知类型都分配了消息队列,当要发送大量通知时,消息队列充当缓冲区,因此第三方服务的中断不会影响其它通知类型。
  • 引入Worker:worker是从消息队列中提取通知事件并将其发送到相应第三方服务的服务器。

经过improved后,现在的通知推送流程如下:

  1. Service调用Notification Server提供的API来发送通知
  2. Notification Server从缓存(Cache)或数据库(DB)中获取元数据,如用户信息,设备令牌和通知设置。
  3. 将通知事件(notification events)发送到相应的消息队列等待处理
  4. Worker从消息队列中提取通知事件
  5. Worker向第三方服务发送通知
  6. 第三方服务向用户设备发送通知

Step3:Design deep dive

我们将在Design deep dive中讨论如下内容:

  • Reliability:
  • Additional compent and considerations
  • Updated design

Reliability

在分布式环境中设计系统时,我们必须回答几个重要的可靠性问题。

How to prevent data loss?

通知系统最重要的要求之一就是不能丢失数据。通知可以延迟或者重新发送,但是不能永远不会丢失。为了满足这一要求:通知数据保存在数据库中,并实现重试机制(通知日志数据库用于数据持久化)。

Will recipients receive a notification exactly once?

收件人会只收到一次通知吗?答案当然是No。尽管大多数情况下通知只发送一次,但分布式性质可能会导致重复的通知。为了减少重复的发生,我们引入了重复数据消除机制,以下是一个简单的数据消除机制的实现逻辑:

当通知事件第一次到达时,我们通过检查事件ID来检查它以前是否被查看过,如果它以前被查看过,它将被丢弃;否则,我们将发送通知。

Additional compnents and considerations

这里我们讨论通知系统的其它组件,包括模板重用,通知设置、事件跟踪、系统监控和速率限制等。

Notification template

一个大型通知系统每天发送数百万个通知,其中许多通知采用类似的格式。引入通知模板可以快速构建一个通知,以避免从头开始构建每个通知。

Notification setting

用户通常每天都会收到很多通知,他们很容易因此感到不知所措。

为此,许多网站和应用都为用户提供了对通知设置的细粒度控制,比如设计一个通知设置表,包含以下字段:

user_id bigInt  # 用户id
channel varchar # 频道类型:SMS或邮件或手机推送
opt_in boolean  # 用户是否接收此类通知

在向用户发送任何通知之前,首先检查用户是否已选择接收此类通知。

Rating limiting

速率限制:为了避免用户收到过多通知,我们可以限制用户在一定时间内可以接收通知的数量。这一点很重要,因为如果我们发送得太频繁,用户很可能会完全关闭通知权限。

Security in push notifications

对于iOS或Andriod应用程序,appKey和appSecret用户保护推送通知API。只有经过身份验证的客户端才允许使用我们的API发送推送通知。

Monitor queued notifications

系统需要监控的关键指标之一是消息队列中的通知总数,如果这个数量很大,则说明Worker处理通知事件的速度不够快,需要部署更多的Worker以避免消息延迟。

Events tracking

通知系统中的一些指标,如打开率,点击率和参与度等,对于理解用户行为非常重要。分析服务需要事件跟踪(Events tracking)来实现,下图显示了出于分析目的可能被跟踪的事件示例:

Update design

把design deep dive中提到的组件加上,更新后的通知系统设计图如下所示:

  • 通知服务器添加了两个关键功能:身份验证(authentication)和速率限制(Rate-Limiting)
  • 我们还添加了一个重试机制来处理通知失败。如果系统未能发送通知,它们将被放回消息队列中,Worker将重置预定义的次数。
  • 此外,通知模板提供了一致且高效的通知创建过程
  • 最后,增加了监测和跟踪系统,用户系统健康检查和未来改进

Step4:Warm up

一点文字总结,没什么看的。