【编者的话】作为一家在线影片租赁提供商,Netflix 需要保证其流数据的服务质量和网站主页的吸引力。那么,Netflix 是如何对网站的重大改变进行测试和评估的呢?近日,Netflix 的工程师 Steve Urban 等就在官方博客中发表文章,介绍了 Netflix 的实验平台,对平台中所采用的 A/B 测试进行了详细解释,希望读者可以从中有所收获。
您是否曾考虑过 Netflix 是如何保证高质量视频和最小回放中断等良好流数据服务质量的呢?其实,这多亏了公司的工程师和数据科学家。他们对自适应流和内容传输网络算法的任何修改都会进行A/B 测试。那么,如果是 UI 布局的完全重新设计或者新的定制化主页等明显的变化呢?这些更是要经过A/B 测试。
实际上,对于每一次的产品改变,Netflix 都会在其成为默认的用户体验之前走完严密的A/B 测试流程。之前所提到的重大设计变动通过允许用户更快地找到他们想看的内容,而极大地改善了我们的服务。然而,如果没有扩展的A/B 测试来证明新的用户体验更好,这些改变就太冒险而无法使用了。此外,如果你曾怀疑我们是否真的对所有可能的情况进行了测试,考虑一下这项工作的潜力:在与很多名称相关联的图像进行A/B 测试后,有时甚至会导致该名称的浏览量增加20%-30% 。
这样的结果进一步说明了我们为什么热衷于A/B 测试。通过使用这种实验方法,我们保证了产品的改变不是由Netflix 的员工随便“拍脑袋”得出,而是由实际的数据驱动,使得会员可以指导我们应该如何改善服务。
在这篇文章中,我们将讨论实验平台:使得每一个Netflix 工程团队都可以在一个特殊工程团队的帮助下实现A/B 测试的服务。我们将从有关A/B 测试的一些高层次内容讲起,然后讲述当前平台的架构以及其它服务是如何与该平台交互从而将A/B 测试引入到生活中的。
概况
A/B 测试背后最基本的概念就是完成带有一个控制组和一个或多个备选实验组(Netflix 内部称其为“cell”)的实验。每一个会员在一个指定的实验中只属于一个 cell,而其中一个 cell 被指定为“默认 cell”。该 cell 代表了控制组,与不在测试中的所有 Netflix 会员享有同样的体验。一旦测试开始,我们会追踪特定的重要量度,通常为流小时和持续时间。一旦我们有足够多的参与者来得出统计意义上的结论,我们就可以读取每个测试 cell 的功效,并希望能找到一个优胜者。
从参与者的角度来说,如果测试不相互冲突(这里冲突的含义为两个测试以不同的方式修改同一个 Netflix 应用的相同地方),每一个会员通常在任何给定的时间都是若干 A/B 测试的一部分。为了帮助测试优胜者找到可能的冲突测试,我们会在平台的前端 ABlaze 中提供一个测试调度图。该工具使得它们可以从不同的维度来过滤测试,从而找到可能影响测试优胜者所修改地方的其它测试。
在我们讨论细节之前,还有一个问题需要解决:会员是如何被分配到一个给定的测试的呢?我们支持两种主要的分配形式——分批和实时。
分批分配给了分析人员更多的灵活性,允许他们利用定制化的、简单或复杂的查询来进行测试。这些查询针对一个固定已知的会员集,然后将这些会员添加到测试中。这种方法的坏处是它缺乏分配新客户的能力,而且不能根据实时的用户行为进行分配。而且,当被分配的会员的数目已知时,不能保证所有被分配的会员会经历此次测试(例如,如果我们正在测试 iPhone 上的一个新功能,我们不能保证每一个被分配的会员在测试期间会通过 iPhone 来访问 Netflix)。
实时分配提供了根据用户与 Netflix 的实时交互情况配置规则的能力。只有满足特定规则且不在冲突测试中的合适用户会被实时分配到测试中。因此,该方法克服了分批分配方法存在的弱点。然而,实时分配的主要缺点是,调用应用会因为等待分配结果而引入额外的延迟。幸运的是,我们经常可以在应用等待其他信息的时候,并行运行这些调用。另外一个问题就是,评估预想数目的成员被分配到一个测试所需要的时间是非常困难的。这就导致分析人员很难决定他们多久能够评估出测试结果。
一个典型的 A/B 测试工作流
介绍完背景知识,我们接下来将要深入介绍 A/B 测试。图像选择测试所用的工作流很好的解释了调用实验平台(图表中简称为 A/B)的典型工作流。需要注意的是,尤其是在扮演外部 Netflix 应用和内部服务之间网管角色的 Netflix API 层的架构方面,二者存在细小的差异,但我们不准备深入解释。
在该例子中,我们运行了一个虚拟的 A/B 测试,以找到促使最多会员观看某个特定视频名称的图片。每一个 cell 代表了一个候选图片。在图中,我们还假设了一个真实产品中经常出现的、来自运行在 PS4 上的 Netflix 应用的调用流。
- Netflix PS4 App 调用 Netflix API。作为调用的一部分,它负责传输一个包含用户及其设备相关的会话层信息的 JSON 负载。
- 调用被 PS4 App 团队编写的脚本处理。该脚本运行在 Netflix API 的 Client Adaptor Layer,而该层包含了 Client App 团队所编写的与其应用相关的脚本。每一个这样的脚本都配备了他们自己的独特 REST 端点。这使得 Netflix API 可以在保证每一个应用对特定逻辑控制权的同时,拥有大部分应用都拥有的通用功能。PS4 App Script 调用我们团队维护的、封装在 Netflix API 内部的 A/B Client 库。该库允许与我们的后端服务器和其他内部 Netflix 服务进行通信。
- A/B Client 调用其他服务集来收集有关会员和设备的其他相关信息。
- 然后,A/B Client 调用 A/B 服务器进行评估,将所有收集到的相关信息发送到服务器。
- 在评估阶段:
a. A/B 服务器找到该会员已经被分配到的所有测试 /cell 组合。
b. 对于采用分批分配方法的测试,具体分配情况在该阶段已经明确。
c. 对于利用实时分配方法的测试,A/B 服务器会评估相关信息来决定会员是否应该被分配到其他测试中。如果的确是这样,会员就被分配过去。
d. 一旦所有的评估和分配完成,A/B 服务器将完整的测试及 cell 集发送到 A/B Client,后者再将其传递到 PS4 App Script。需要注意的是,PS4 App 并不知道用户是否已经在给定的测试中体验了若干周还是只有若干秒。它也不需要知道或者关心这个信息。 - 在收到测试 /cell 组合后,PS4 App Script 根据当前客户端请求可用的测试采取行动。在我们的例子中,它会利用该信息来选择由拥有名称元数据的服务返回的、与需要显示的名称相关联图片的合适部分。需要注意的是,实验平台实际上并不控制这一行为:它依赖于实际实现给定测试中每一个体验的服务。
- PS4 App Script(通过 Netflix API)告诉 PS4 App 显示哪张图片以及 PS4 App 为了正确渲染 UI 需要执行的是哪些其他操作。
现在,我们已经理解了调用流。接下来,就让我们更加详细地了解“A/B 服务器”。
实验平台
上一节中所描述的分配和获取请求会通过 REST API 端点传递到我们的服务器。包含分配规则在内的每个测试的测试元数据存储在 Cassandra 数据库中。正是通过将这些分配规则与 A/B Client 传递的上下文信息进行比对,来决定一个会员是否适合参与到测试中(例如,用户是否在澳大利亚,利用 PS4,而且从来没有用过 PS4 应用的这个版本)。
会员分配也保存在 Cassandra 中。为了减少 Cassandra 的直接调用数目,我们在其前端以 EVCache 集群的形势添加了一个缓冲层。当一个应用发出针对当前分配的请求时,A/B Client 首先检查 EVCache 来获取与该会员相关的分配记录。如果该信息在过去的 3 个小时(缓冲的 TTL 时间)内被请求过,EVCache 将返回一份分配的拷贝。否则,A/B 服务器直接访问 Cassandra,而后者将分配传递给 A/B Client,同时在 EVCache 中进行缓存。
当一个 A/B 测试的分配发生时,我们需要决定放置每一位会员的那个 cell。为了能够从测试中得出统计意义上的结论,cell 中的元素要尽可能的同源。因此,这一步的处理一定要非常小心。同源的程度是通过一个包含国家、设备类型(如智能电视、游戏终端等)等关键信息集来进行评估的。因此,我们的目标是保证每一个 cell 包括了来自每个国家的相似比例的会员,会员使用相似比例的设备类型等。纯粹的随机抽样可以通过在一个 cell 中分配较多的澳大利亚游戏终端的用户来使得测试结果有所偏重。为了避免该问题,我们采用了能够维护跨多个关键维度的同源性的分层抽样法。由于分层抽样的实现非常复杂,我们计划以后通过一篇专门的文章进行介绍。
在分配过程的最后一步,我们将分配的细节放在了 Cassandra,并使得与该会员相关的 A/B 缓存失效。结果,在下次我们收到有关该会员的分配请求时,我们会遇到一次缓存丢失,并执行之前提到的缓存相关的步骤。
我们还将分配事件同步发布到了 Kafka 数据流水线,从而转存到若干个数据仓库中。对于定制化的分析和 Netflix 的 A/B 测试可视化及分析工具 Ignite,发布到 Hive 表的转存操作提供了一个数据源。正是在 Ignite 内,测试者分析感兴趣的量度,并评估测试结果。有关 Ignite,你可以期待我们会在未来的博客中对其进行详细介绍。
我们技术栈的最近更新就是添加了 Spark Streaming。它负责在从 Kafka 流中摄入数据,并将数据转换后送入 ElasticSearch 进行留存,使得我们可以在 ABlaze 中以近乎实时的方式展示更新。我们当前的用例只牵涉到了简单的量度,使得用户可以根据多个兴趣点实时浏览测试分配情况。然而,这些内容为我们不久的将来进行复杂的实时分析打好了基础。
未来工作展望
以上描述的架构至今一直工作很好。我们会继续支持更加广泛的领域集:UI、推荐、回放、搜索、邮件、注册等等。通过自动扩展,我们可以轻易应对每秒 15 万 -45 万个请求的平台典型流量。从响应性来看,提取现有分配的延迟范围为 8ms(当缓存还没热起来时)到小于 1ms(缓存热起来以后)。实时评估相对花费的时间要长一些,大概为 50ms。
然而,随着会员基数不断迅速扩张,A/B 测试的速度和类型也在迅速增加。从某些角度来看,我们之前所描述的架构一直停留在 2010 年左右(Kafka 等时例外)。从 2010 年到现在,
- Netflix 的服务范围已经从 2 个国家增加到 190 多个
- 我们的会员数目从 1 千多万增加到了 8 千万
- 所支持的设备从几十增加到了上千,很多设备还拥有了自己的 Netflix 应用
跨国扩展是设备类型迅速增加的一部分原因。尤其是移动设备数量的增加,更是使得 Netflix 的流业务不断增长。在该领域,我们主要依靠分批分配——我们的实时分配方法无法应对该情形:移动设备的带宽不够稳定,使得用户在我们决定好服务哪种体验之前就关闭了一直处于加载状态的应用。
此外,一些新的创新领域会在更短的时间范围内进行 A/B 测试。专注于 UI 改变和推荐算法等的测试通常都会运行若干周,才能测量出改变对用户行为的明确影响。然而,在本篇文章开头提到的自适应流测试通常只运行若干小时,以满足内部用户对结果快速反馈的需求。
因此,我们计划对架构的若干方面进行大的修改。例如,既然实时分配机制允许粒度控制,评估就需要更快,而与移动设备的交互也需要更加高效。
我们还计划使用通过 Spark Streaming 的数据流动来预测每个测试在给定分配规则下的分配速度。其目标是解决实时分配方法的第二大缺点——无法预测将足够的会员分配到一个测试所需要的时间。在分析人员能够预测分配速度后,测试计划和协调就可以更加精确。
这些只是我们将遇到的若干挑战。如果你只是想了解我们如何应对这些挑战,请关注以后的博客文章。然而,如果解决这些挑战和帮助我们构建下一代 Netflix 实验平台的想法让你觉得兴奋,我们热烈欢迎有志之士加入我们的团队!
查看原文链接: http://techblog.netflix.com/2016/04/its-all-about-testing-netflix.html
编后语
《他山之石》是 InfoQ 中文站新推出的一个专栏,精选来自国内外技术社区和个人博客上的技术文章,让更多的读者朋友受益,本栏目转载的内容都经过原作者授权。文章推荐可以发送邮件到 editors@cn.infoq.com。
感谢陈兴璐对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ , @丁晓昀),微信(微信号: InfoQChina )关注我们。
评论