作者:Brandon Lonac,高级软件开发主管Raymond Khalife,高级软件开发主管Kalyani Narayanan,首席技术项目经理Travis McCann,产品管理总监Justin Goad,高级技术程序管理主管

大约一年前,在此博客上,我们概述了如何扩展实时服务的规模,以应对诸如March Marchness之类的大型流行事件。
从那时起,Hulu在美国已增长到超过2500万用户,并且我们几乎在每一个重大事件中都不断打破并发记录。 进入2019年,我们知道这个超级碗将再次打破纪录,因此我们不得不提高自己的游戏水平,为年度最大的体育赛事做准备并扩大规模-而且它得到了回报:Hulu的现场直播并发观众增加了四倍相比于2018年,今年是超级碗比赛,我们成功地通过观众喜爱的设备向观众提供了稳定,高质量的大型比赛转播。
去年,随着我们改进整体准备工作方法,Hulu技术团队专注于三个关键领域:
- 改善负载预测 :提高我们针对任何给定事件准确预测系统负载的能力。
- 快速扩展我们的系统 :按需精简关键系统,或者在无法选择的情况下提供冗余。
- 作战和作战准备 :为准备大型直播电视活动而改进最佳实践。
使用历史观看数据改善并发投影
今年,我们利用上一季的数据来预测同时收看的观众。 通过获取历史收视率数据并将其与预计的订户增长估计值相结合,我们能够对每周并发预测进行建模。 在整个2018–2019赛季中,与实际相比,我们的估计错误率为+/- 10%。 我们还建立了并发收视率矩阵,并将其推断为关键路径系统的每秒请求目标数。 将这两项新功能结合在一起,使我们能够更好地了解哪些系统需要扩展,扩展多少以及何时扩展。
该模型为我们提供了预测范围的最小值,最大值和均值的置信范围。 当我们接近实际日期时,置信区间会越来越小,直到得出类似以下结果的结果:

使用混合云方法扩展我们的平台
Hulu的大多数服务都运行在我们称为Donki的内部PaaS上,该PaaS在我们的数据中心利用Apache Mesos / Aurora,并在云中利用容器编排系统。 Donki将服务打包到容器中,并且相同的容器可以在我们的数据中心和云中运行。 尽管我们最近将一些流量最高的服务移到了云中运行,但我们仍能够在可能的故障转移场景中利用我们的数据中心。 Donki使我们能够根据特定服务的需求轻松地将部署编排到任一环境中。 我们专注于利用云中的自动扩展功能来更好地处理流量意外增长,以保持系统正常运行。
我们通过两种方式利用自动缩放:
- 扩展托管服务的群集。
- 扩展服务本身。
托管服务的群集通过添加或删除计算机自动扩展。 自动缩放会根据规则进行,以根据CPU和内存预留阈值进行缩小或增长。
服务本身将根据度量标准(例如,每个实例每分钟的请求数,每个实例的cpu使用率或每个实例的内存使用率)通过添加或删除实例来自动扩展。 我们负责可观察性和模拟(混乱)的生产工程团队会进行负载测试,以查找每个实例的容量,并与团队合作设置适当的自动缩放规则。
我们需要扩展的关键服务领域之一是增强我们的用户发现体验的堆栈。 我们采用了一种灵活的方法,根据流量预测和自动的常规性能测试来对系统进行扩展,以逐步加载和增加完整的端到端堆栈。 为了帮助服务团队实现这一目标,我们的生产准备组提供了两个功能:
- 负载测试工具,用于具有真实测试模拟的团队。
- 跨整个服务堆栈的端到端压力测试的协调。
这是一项巨大的努力,使团队可以腾出时间专注于特定的扩展需求。

我们的系统具有不同的体系结构域,需要分别进行整体扩展。 我们从压力测试开始,以发现这些领域和整个系统中的薄弱环节。 当我们巩固系统的每个域时,这导致了一个中断/修复周期。 自动化的系统范围测试每周运行多次。 这样可以快速迭代验证先前运行中发现的团队修复程序。 各个团队还能够单独进行压力测试,以在进行大规模测试之前验证改进。 由于域中的所有服务都使用我们的PaaS Donki,因此微调每个应用程序集群的大小非常容易。 然后,可以将精力集中在应用程序优化以及调整应用程序集群和规模参数上。
一旦系统能够处理预期的负载,我们便进行峰值测试,以模拟大量用户在游戏开始时进行登录或模拟回放失败。

不同的领域以不同的方式扩展。 发现体验专注于个性化的,元数据丰富的响应。 当扩展到数百万个用户时,这可能会很奇怪。 目的是在那时为用户提供最佳的响应。 我们专注于缓存基线响应,然后在此之上进行个性化设置,以确保观看者找到他们想要的内容。 我们从头开始将优美的降级内置到系统中。 为了达到系统所需的规模,我们做出了这些体系结构设计决策。
- 使用异步/非阻塞应用程序框架
- 使用断路器,速率限制和减载模式
- 同时使用本地和分布式缓存
- 凝聚的客户行为
我们的API网关和边缘服务使用基于JVM的异步事件驱动的应用程序框架和断路器。 这样就可以一次针对一个应用程序实例打开数千个连接。 如果太多请求保持打开时间太长,则可能导致内存压力。 所有应用程序都将变得无响应。 我们使用压力和峰值测试来微调对系统的速率限制请求,以防止系统受到过多流量的攻击。 这使系统能够继续运行,并在自动缩放时在极端峰值期间为用户提供服务,而不是在压力下瘫痪且无人使用。 如果用户流量超出了我们的速率限制,我们的系统将开始减轻负载。 如果需要发出请求,我们API层中的断路器将跳闸并将请求发送到后备集群。 这是我们核心客户端应用程序体验的高度缓存版本,可支持对唯一用户的请求。 发现体验的主要目标是返回响应,这种模式组合有助于确保这一点。
为了实现低延迟响应并实现发现体验的可扩展性,严重依赖缓存。 节点同时使用基于本地JVM的缓存和分布式缓存。 节点基于MRU和TTL缓存响应和元数据。 如果发生高速缓存未命中或逐出,则从分布式高速缓存中获取数据。 不同缓存的组合使用有助于使回退体验与正常响应几乎无法区分。
这将我们带到最后一点,即具有凝聚力的客户行为。 使用已定义且一致的服务器API,客户端也可以帮助扩展。 客户端尊重HTTP响应代码和标头,可以帮助防止在错误情况下进行轰炸并在错误情况下产生更多的负载。 我们已经看到了过去各种客户端中不一致的错误处理逻辑可以做什么。 调用API时使用指数补偿和可变时间量等策略是客户端可以帮助扩展的简单方法。 这似乎是一种合理的方法,但需要与我们拥有的众多客户进行协调。 它还需要有关API早期传达方式的最佳实践。
准备故障转移流并进行操作准备和冗余
我们花了很多时间对系统进行压力测试和扩展,但是事情并非总是按计划进行。 这就是为什么我们总是计划冗余,尤其是在视频播放等关键领域,这是我们系统的核心。 我们的目标是确保源流本身的最高可用性。 基于内容合作伙伴,源流路径的体系结构可能会大不相同,并且每个工作流程都会面临不同的挑战。 因此,绝对有必要确保我们为实时流实现多个故障转移选项。
实施这些额外的信号通路需要与我们的信号提供商和合作伙伴紧密合作。 我们在此处遵循的一些最佳实践是为实时流建立多个不相交的信号路径,确保故障转移脚本经过良好测试,并可以在几秒钟内在源流之间执行切换,并保留实时和DVR体验在发生故障转移时不会给用户带来任何麻烦。
除了准备大型活动所需的所有技术外,还必须为我们的组织做好准备。 随着我们规模的不断扩大,我们采用的一项新计划是定期进行桌面练习,以对测试失败的情况施加压力,以帮助团队更好地准备并从潜在的故障中恢复。 实践证明,Wargaming是一个非常有教育意义的过程,可以建立持续的操作准备文化,并确保我们的运行记录簿全部固定。 该实践还发现了我们需要为其准备缓解计划的更多失败方案。 我们确定了许多团队需要做的短期和长期工作,我们将继续跟踪这些工作。
超越超级碗-自动化我们的准备工作
我们一直在提高对大型活动的服务扩展的严格性和纪律性。 整个技术组织的团队已经花费了无数时间来为越来越多的观众准备我们的系统。
我们获得的一些关键要点是:
- 通过自动化我们的生产负载测试,我们能够建立始终保持准备状态并不断前进的一致性。
- 持续改进我们的预测确实有助于推动团队需要交付的成果。
- 通过提供用于负载测试的中央平台,我们释放了工程师的精力,专注于如何最好地设计和重构系统以进行扩展。
这些努力的高潮导致今年对超级碗的信心增强,但是这个过程仍然涉及一些手动要素。 随着我们进入2019年剩余时间,我们计划将更多时间用于这些领域的自动化和改进。 最终,我们希望我们的预测能够自动优化容量预测,以考虑更多变量。 我们还计划将更多的负载测试集成到我们的CI / CD管道中,并在一致的基础上扩展测试场景,以实现更高的可靠性。 尽管大型活动可能总是需要一些监督,但我们的目标是尽可能地准备和自动化,以使我们的团队更高效,更简化此过程。
如果您有兴趣从事此类项目并在Hulu中发挥作用,请 在此处 查看我们当前的职位空缺 。