事件通知是现代互联网业务的必要组成部分之一,对用户体验有非常重要的影响。本文介绍了 Netflix 打造跨平台事件通知系统的经验,介绍了系统的架构、功能、设计原则等,帮助我们理解如何设计一个好的事件通知系统。原文:Rapid Event Notification System at Netflix[1]
简介
Netflix 有超过 2.2 亿活跃用户,每个用户都会在访问 Netflix 的时候做出各种各样的操作,也许是重命名个人资料,或者是观看视频内容。Netflix 需要几乎实时的对这些操作做出反应,并保持不同设备体验的一致性,从而为用户提供最好的用户体验。考虑到用户可以执行的操作数量相当庞大,并且需要支持种类繁多的设备,要完成这一目标并不容易。为此,我们开发了快速事件通知系统(RENO, Rapid Event Notification System),用来支撑那些需要发起与设备通信的用例,为服务器提供可伸缩和可扩展的解决方案。
本文将概述 Netflix 的快速事件通知系统,并分享我们在实现过程中获得的经验。
动机
随着用户基数的快速增长以及系统日益复杂,Netflix 的体系架构已经演变为一种异步的体系架构,同时支持在线和离线计算。要在各种平台(iOS、Android、智能电视、Roku、Amazon FireStick、浏览器)和各种设备(手机、平板电脑、电视、电脑、机顶盒)上提供无缝且一致的体验,需要的不仅仅是传统的请求-响应模型。随着时间推移,越来越多的用例表明,后端系统需要发起与设备的通信,从而将用户驱动的变更以及用户体验更新以快速一致的方式通知到设备。
用例
- 观看活动(Viewing Activity)当用户开始观看节目时,应该在所有设备上同步更新“继续观看”列表,以反映观看情况。
- 个性化的体验刷新(Personalized Experience Refresh)Netflix 推荐引擎不断给每个会员更新推荐,更新后的推荐需要及时发送到设备,以获得最佳的用户体验。
- 会员计划变更(Membership Plan Changes)会员经常改变计划类型,导致体验发生变化,这些变化必须立即反映在所有设备上。
- 会员“我的列表”更新(Member “My List” Updates)当用户通过添加或删除视频更新“我的列表”时,这些变化应该反映在所有的设备上。
- 会员资料修改(Member Profile Changes)当会员更新帐户设置,如添加/删除/重命名配置文件或更改内容的首选分级时,这些更新必须反映在所有的设备上。
- 系统诊断信号(System Diagnostic Signals)在特殊场景中,需要向设备上的 Netflix 应用程序发送诊断信号,以帮助排查问题并启用跟踪功能。
设计决策
我们在设计系统时做了几个关键的决定,帮助塑造了 RENO 的架构:
- 单一事件源(Single Events Source)
- 事件优先级(Event Prioritization)
- 混合通信模式(Hybrid Communication Model)
- 定向交付(Targeted Delivery)
- 高性能(Managing High RPS)
单一事件源(Single Events Source)
我们想要支持的用例来源于各种内部系统和用户操作,所以需要监听来自多个不同微服务的事件。在 Netflix,近实时事件流(near-real-time event flow)是由一个名为 Manhattan 的内部分布式计算框架管理的[2]。我们利用 Manhattan 的事件管理框架创建了一个中间层,作为 RENO 的单一事件源。
事件优先级(Event Prioritization)
考虑到用例来源和重要性的范围很大,因此我们对进行事件分类处理。例如,对于“在配置中修改内容分级”这样的由用户触发的事件,应该比“系统诊断信号”拥有更高的优先级。因此,我们为每个用例分配了一个优先级,并通过路由到特定的优先级队列和相应的事件处理集群来切分事件流量。这种分类允许我们针对不同的事件优先级和流量模型单独调整系统配置和伸缩策略。
混合通信模型(Hybrid Communication Model)
正如本文前面提到的,像 RENO 这样的服务面临的一个关键挑战是对多平台的支持。移动设备几乎总是可以连接到互联网,而智能电视只有在使用时才可以连网。这种网络连接的异构性让我们很难选择一致的交付模型。例如,如果完全依赖于 Pull 模式,即设备经常向服务端查询更新,将导致移动应用程序被频繁唤起,而这又会反过来触发 iOS 和 Android 平台实施的应用通信限流(还需要考虑低带宽连接)。另一方面,如果只使用 Push 推送机制,又会导致智能电视在一天中大部分时间处于关机状态时错过通知。因此,我们选择了 Push 和 Pull 混合通信模型,服务器尝试使用 Push 机制向所有设备实时发送通知,而设备在应用生命周期的不同阶段查询服务端。
使用推拉模型的组合同样可以支持只有单一通信模型的设备,包括不支持推送通知的旧设备。
定向交付(Targeted Delivery)
考虑到事件源和目标设备类型的范围很广,我们构建了对特定设备的通知交付支持,允许根据不同的用例通知特定类型的设备。当可操作的事件到达时,RENO 应用特定于用例的业务逻辑,收集符合接收该通知的条件的设备列表,并尝试发送,从而有助于控制发送流量。
高性能(Managing High RPS)
由于拥有超过 2.2 亿的会员,我们意识到像 RENO 这样的服务需要在用户观看过程中处理许多事件。在高峰时段,RENO 每秒需要处理大约 15 万个事件。在一天中的特定时间,如此高的 RPS 可能会造成严重的惊群问题(thundering herd problem)[3],并给内外部的下游服务带来压力。因此,我们实施了一些优化措施:
- 事件过期(Event Age)许多需要通知给设备的事件都是时间敏感的,需要立即发送,否则就没有价值。因此我们将过时过滤器作为门禁检查,从而避免处理旧事件。如果事件时间超过了配置的阈值,则不进行处理。在处理开始处理事件的时候,过滤器会过滤掉对设备没有价值的事件,从而保护队列不会由于处理过时的上游事件而被消耗光。
- 在线设备(Online Devices)为了减少流量占用,通知只发送给当前在线的设备,我们利用 Zuul 实时更新注册表[4]。
- 扩容测量(Scaling Policies)为了解决严重的惊群问题并将延迟保持在可接受的阈值以下,集群扩容策略的配置比缩容策略更激进,从而使计算能力在队列增长时能够快速赶上。
- 事件去重(Event Deduplication)iOS 和 Android 平台都严格限制后台应用的活跃水平,这就是为什么 RENO 需要删除重复数据的原因。在高 RPS 的情况下可能会发生重复事件,如果不会导致设备丢失上下文信息,就将其合并到一起。
- 隔离交付包括苹果的 Apple Push Notification Service (APNS)[5]和谷歌的 Firebase Cloud Messaging (FCM)[6]等外部平台在内,有多个下游服务用于向不同的设备平台推送通知。为了防止下游服务拖垮整个通知服务,我们在不同的平台上并行交付事件,尽量利用每个平台的能力。当下游服务或平台下发通知失败时,不会影响到其他设备接收通知。
架构
如上图所示,RENO 服务可以分解为以下组件。
事件触发器(Event Triggers)
在设备上刷新用户体验的操作以及系统驱动的更新。
事件管理引擎(Event Management Engine)
Netflix 的近实时事件流管理框架(Manhattan)可以配置为侦听特定事件并将事件转发到不同的队列。
事件优先级队列(Event Priority Based Queues)
在 Manhattan 建立基于优先级的事件转发规则配置的 Amazon SQS 队列,从而允许基于优先级的流量分片。
事件优先级集群(Event Priority Based Clusters)
订阅相同优先级队列的 AWS 实例集群,处理到达这些队列的所有事件,并为设备生成可操作的通知。
消息发送系统(Outbound Messaging System)[7]
Netflix 向会员发送应用程序内推送通知的消息系统,被用来在最后向移动设备发送 RENO 生成的通知。
对于 Web、电视和其他流媒体设备的通知,我们构建了被称为 Zuul Push 的自建通知解决方案,提供了与在线设备的持久连接[8]。
持久化存储(Persistent Store)
RENO 为每个设备发出的所有通知都存储在 Cassandra 数据库中,并允许设备以自己的节奏轮询消息。
可视化
在 Netflix,我们非常重视为系统构建健壮的监控,以提供系统健康状况的清晰视图。对于 RENO 这样的高性能服务,依赖于多个上游系统作为流量源,同时为不同的内外部下游系统产生大量的流量,用一个强有力的系统组合指标、告警和日志记录非常重要。除了标准的系统健康指标(如 CPU、内存和性能)外,我们还添加了许多“服务边缘(edge-of-the-service)”指标和日志记录,以捕获来自上游或下游系统的任何异常。此外,除了实时告警,我们还为重要指标添加了趋势分析,以帮助捕获长期退化。我们用一个名为 Mantis 的实时流处理应用构建 RENO 的仪表盘[9],它可以帮助我们以特定设备为粒度实时跟踪事件,从而使调试更容易。最后,我们发现针对特定平台(iOS, Android 等)的告警有助于更快找到问题的根源。
收获
- 能够轻松支持新的用例
- 支持水平扩展,具有更高的吞吐量
当我们开始构建 RENO 时,目标仅限于产品的“个性化体验刷新”用例。随着 RENO 的发展,对新用例的支持成为可能,RENO 很快被定位为 Netflix 所有产品的中心化快速通知服务。
我们在早期所做的设计决策获得了回报,例如添加了“即插即用”的新用例解决方案,并提供了跨所有平台的混合交付模型。我们能够以较快的速度添加新的产品用例,从而消除了对创新的阻碍。
在构建这个平台的过程中,一个重要的收获是确保 RENO 能够随着时间的推移,随着更多类型的事件和更高吞吐量的需求而水平扩展。这一能力主要是通过允许基于事件类型或优先级的分片,以及使用异步事件驱动处理模型来实现的,该模型可以通过简单的添加更多机器来进行事件处理。
下一步
随着 Netflix 会员基数持续快速增长,像 RENO 这样的服务有助于为会员提供最好和最新的 Netflix 体验。从会员相关的更新到个性化的上下文,我们在不断创新会员体验的同时,也在不断改进通知组合。在架构上,我们正在评估构建更多特性的机会,比如可靠消息传递和消息批处理,从而可以支持更多用例,并有助于减少 RENO 的通信开销。
References:[1] Rapid Event Notification System at Netflix: https://netflixtechblog.com/rapid-event-notification-system-at-netflix-6deb1d2b57d1
[2] System Architectures for Personalization and Recommendation: https://netflixtechblog.com/system-architectures-for-personalization-and-recommendation-e081aa94b5d8
[3] Thundering Herd Problem: https://en.wikipedia.org/wiki/Thundering_herd_problem
[4] Zuul: https://netflixtechblog.com/tagged/zuul
[5] Apple Push Notification Service: https://developer.apple.com/go/?id=push-notifications
[6] Firebase Cloud Messaging: https://firebase.google.com/docs/cloud-messaging
[7] Building a cross platform in-app messaging orchestration service: https://netflixtechblog.com/building-a-cross-platform-in-app-messaging-orchestration-service-86ba614f92d8
[8] Scaling Push Messaging for Millions of Devices @Netflix: https://qconnewyork.com/ny2018/presentation/architectures-youve-always-wondered-about-presentation
[9] Open sourcing Mantis: A platform for building cost effective realtime operations focused: https://netflixtechblog.com/open-sourcing-mantis-a-platform-for-building-cost-effective-realtime-operations-focused-5b8ff387813a