近年来,通知系统已经成为许多应用程序中非常流行的功能。通知向用户提供重要信息,如突发新闻、产品更新、事件、产品等。它已经成为我们日常生活中不可或缺的一部分。
在本章中,你将设计一个通知系统。
通知不仅仅是手机推送通知。三种通知格式:移动推送通知、短信通知和电子邮件通知。图10-1显示了这些通知的一个示例
第一步 -理解需求并且建立设计的范围
建立一个每天发送数百万条通知的可扩展系统并不是一件容易的事。这需要对通知生态系统有深刻的理解。面试问题被故意设计成开放式和模棱两可的,你有责任问问题来澄清要求。
候选人:系统支持什么类型的通知?
面试官:推送通知、短信和电子邮件。
候选人:是实时系统吗?
面试官:我们可以说它是一个软实时系统。我们希望用户能够尽快收到通知。但是,如果系统处于高工作负载下,轻微的延迟是可以接受的。
候选人:支持的设备是什么?
面试官:iOS设备、android设备和笔记本/台式机。
候选人:是什么触发了通知?
面试官:通知可以由客户端应用程序触发。它们也可以在服务器端进行调度。
候选人:用户可以选择关闭吗?
面试官:是的,选择关闭的用户将不再收到通知。
候选人:每天发出多少通知?
面试官:1000万条手机推送通知,100万条短信,500万条电子邮件
第二步 - 提出顶层设计并获取支持
本节展示了支持多种通知类型的高级设计:iOS推送通知、Android推送通知、短信通知和Email通知。它的结构如下:
- 不同类型的通知
- 信息收集流程
- 通知发送/接收流程
不同类型的通知
我们首先看看每种通知类型在高层上是如何工作的。
我们主要需要三个组件来发送iOS推送通知: 提供商构建并向Apple推送通知服务(APNS)发送通知请求。为了构造推送通知,提供程序提供以下数据:
-
设备令牌:这是用于发送推送通知的唯一标识符。
-
有效载荷:这是一个JSON字典,包含通知的有效载荷。下面是一个例子:
-
APNS:这是苹果提供的远程服务,用于向iOS设备传播推送通知。
-
iOS设备:它是接收推送通知的终端客户端。
Android推送通知 Android采用了类似的通知流程。Firebase Cloud Messaging (FCM)通常用于向android设备发送推送通知,而不是使用apn。
短信消息 对于短信,通常使用第三方短信服务,如Twilio、Nexmo等。其中大多数是商业服务。
邮件消息 虽然公司可以建立自己的电子邮件服务器,但他们中的许多人选择商业电子邮件服务。Sendgrid[3]和Mailchimp[4]是最受欢迎的电子邮件服务,它们提供了更好的投递率和数据分析。
包含所有第三方服务后的设计如图10-6所示。
联系人信息发送收集流程
要发送通知,我们需要收集移动设备令牌、电话号码或电子邮件地址。如图10-7所示,当用户安装我们的应用程序或首次注册时,API服务器收集用户的联系信息并将其存储在数据库中。
存储联系人信息的简化数据库表如图10-8所示。电子邮件地址和电话号码存储在用户表中,而设备令牌存储在设备表中。一个用户可以有多个设备,即一个推送通知可以发送到所有的用户设备。
消息发送/接收 flow
我们将首先展示初步设计;然后,提出一些优化建议。
顶层设计
如图10-9所示,系统各组成部分如下图所示。
Service 1 ~ N:服务可以是微服务、cron作业或触发通知发送事件的分布式系统。例如,账单服务发送电子邮件提醒客户到期付款,或者购物网站通过短信告诉客户他们的包裹将于明天送达。
通知系统:通知系统是发送/接收通知的核心。从简单的开始,只使用一个通知服务器。它为服务1到N提供api,并为第三方服务构建通知有效负载。
第三方服务:第三方服务负责向用户发送通知。在与第三方服务集成时,我们需要特别注意可扩展性。良好的可扩展性意味着一个灵活的系统,可以很容易地插入或拔掉第三方服务。另一个重要的考虑因素是,第三方服务可能在新市场或未来不可用。例如,FCM在中国是不可用的。因此,第三方服务如Jpush、PushY等在这里被使用。
iOS, Android, SMS, Email:用户在他们的设备上接收通知。
在此设计中确定了三个问题:
-
单点故障(SPOF):单个通知服务器意味着SPOF。
-
难以扩展:通知系统在一台服务器上处理与推送通知相关的所有内容。独立地扩展数据库、缓存和不同的通知处理组件是一项挑战。
-
性能瓶颈:处理和发送通知可能是资源密集型的。
例如,构建HTML页面并等待来自第三方服务的响应可能需要时间。在一个系统中处理所有事情可能会导致系统过载,尤其是在高峰时段。
高级设计(改进)在列举了初始设计中的挑战之后,我们对设计进行了如下改进:
-
将数据库和缓存移出通知服务器。
-
添加更多的通知服务器,并设置自动水平扩展。
-
引入消息队列来解耦系统组件。
改进后的高层设计如图10-10所示。
理解上图的最佳方法是从左到右: 服务1到N:它们表示通过通知服务器提供的api发送通知的不同服务。
通知服务器:它们提供以下功能:
-
为发送通知的服务提供api。这些api只能在内部访问或由经过验证的客户端访问,以防止垃圾邮件。
-
进行基本验证,以验证电子邮件,电话号码等。
-
查询数据库或缓存以获取呈现通知所需的数据。
-
将通知数据放到消息队列中进行并行处理。
下面是一个发送电子邮件的API示例:POST api.example.com/v/sms/send请…
缓存:缓存用户信息、设备信息、通知模板。
DB:它存储有关用户、通知、设置等的数据。
消息队列:它们解耦组件之间的依赖关系。当要发送大量通知时,消息队列充当缓冲区。每种通知类型都分配了一个不同的消息队列,因此一个第三方服务的中断不会影响其他通知类型。
Workers: Workers是一组服务器,它们从消息队列中提取通知事件并将其发送给相应的第三方服务。
第三方服务:在初始设计中已经解释过了。
iOS, Android, SMS, Email:在最初的设计中已经解释过了。
接下来,让我们检查每个组件如何一起工作以发送通知:
-
服务调用通知服务器提供的api来发送通知。
-
通知服务器从缓存或数据库获取元数据,如用户信息、设备令牌和通知设置。
-
通知事件被发送到相应的队列进行处理。例如,将iOS推送通知事件发送到iOS PN队列。
-
工作人员从消息队列中提取通知事件。
-
工作人员向第三方服务发送通知。
-
第三方服务向用户设备发送通知。
第三步-底层设计实现
在高级设计中,我们讨论了不同类型的通知、联系信息收集流和通知发送/接收流。我们将深入探讨以下内容:
-
可靠性。
-
其他组件和注意事项:
-
通知模板,通知设置,限流机制,重试机制,推送通知中的安全性,监视队列通知和事件跟踪。
-
更新设计。
可靠性
在设计分布式环境下的通知系统时,我们必须回答几个重要的可靠性问题。
如何防止数据丢失?
通知系统中最重要的要求之一是不能丢失数据。
通知通常可以延迟或重新排序,但永远不会丢失。为了满足这一需求,通知系统将通知数据持久化到数据库中,并实现重试机制。包括通知日志数据库,用于数据持久化,如图10-11所示。
收件人只会收到一次通知吗?
简短的回答是否定的。尽管大多数情况下通知都只传递一次,但分布式的特性可能会导致重复的通知。为了减少重复数据的发生,我们引入了重复数据删除机制,并对每个故障情况进行了认真的处理。这里有一个简单的重复数据删除逻辑:当通知事件第一次到达时,我们通过检查事件ID来检查它之前是否见过。
如果以前见过,则丢弃。否则,我们将发出通知。有兴趣的读者可以参考参考资料[5],了解为什么我们不能精确地进行一次交付。
其他组件和注意事项
我们已经讨论了如何收集用户联系信息、发送和接收通知。通知系统远不止于此。这里我们将讨论其他组件,包括模板重用、通知设置、事件跟踪、系统监控、速率限制等。
通知模板
大型通知系统每天发送数百万条通知,其中许多通知都遵循类似的格式。引入通知模板是为了避免从头构建每个通知。通知模板是一种预格式化的通知,通过自定义参数、样式、跟踪链接等来创建您独特的通知。下面是一个推送通知的示例模板。
你在做梦。我们敢于这样做。[项目名称]回来了,直到[日期]。
CTA:现在订购。使用通知模板的好处包括保持格式一致,减少页边距错误,节省时间。
通知设置
用户通常每天都会收到太多的通知,他们很容易感到不知所措。因此,许多网站和应用程序为用户提供了对通知设置的细粒度控制。此信息存储在通知设置表中,包含以下字段:user_id bigInt channel varchar # push notification, email或SMS opt_in boolean # opt-in to receive notification在任何通知发送给用户之前,我们首先检查用户是否选择接收这种类型的通知
速度限制
为了避免过多的通知淹没用户,我们可以限制用户可以接收的通知数量。这一点很重要,因为如果我们发送得太频繁,接收方可能会完全关闭通知。
重试机制
当第三方服务发送通知失败时,会将该通知添加到消息队列中等待重试。如果问题仍然存在,将向开发人员发送警报。
推送通知的安全性
对于iOS或Android应用,使用appKey和appSecret来保护推送通知api[6]。只有经过身份验证或验证的客户端才允许使用我们的api发送推送通知。感兴趣的用户可参考参考资料[6]。
监视排队通知
要监视的一个关键指标是排队通知的总数。如果数量很大,则工作人员处理通知事件的速度不够快。为了避免通知传递的延迟,需要更多的工作人员。图10-12(归功于[7])显示了要处理的排队消息的示例
事件追踪
通知指标,如打开率、点击率和参与度对于理解客户行为非常重要。分析服务实现事件跟踪。通常需要在通知系统和分析服务之间进行集成。图10-13显示了一个可能用于分析目的而跟踪的事件示例。
更新设计
图10-14展示了更新后的通知系统设计。
在此设计中,与之前的设计相比,增加了许多新的组件。
-
通知服务器配备了两个更关键的功能:身份验证和用户接收信息的速率限制。
-
我们还添加了一个重试机制来处理通知失败。如果系统发送通知失败,则将它们放回消息传递队列中,工作人员将重试预定次数的通知。
-
此外,通知模板提供一致和有效的通知创建过程。
-
最后,增加了用于系统健康检查和未来改进的监控和跟踪系统。
第四步-总结打包
通知是必不可少的,因为它们让我们随时了解重要信息。它可以是关于你在Netflix上最喜欢的电影的推送通知,关于新产品折扣的电子邮件,或者关于你在线购物付款确认的消息。
在本章中,我们描述了一个可扩展的通知系统的设计,该系统支持多种通知格式:推送通知、SMS消息和电子邮件。我们采用消息队列来解耦系统组件。
除了高层次的设计,我们还深入研究了更多的组件和优化。
可靠性:我们提出了一个健壮的重试机制,以尽量减少故障率。
-
安全性:使用AppKey/appSecret对来确保只有经过验证的客户端才能发送通知。
-
跟踪和监控:这些在通知流的任何阶段实现,以捕获重要的统计数据。
-
尊重用户设置:用户可以选择不接收通知。我们的系统在发送通知之前首先检查用户设置。
-
速率限制:用户会喜欢收到通知的频率上限。
恭喜你走了这么远!现在给自己点鼓励吧。好工作!