网飞公司是如何使用Druid进行实时观测以确保高质量体验的?

全文共4381字,预计学习时长13分钟

来源:Pexels

随着时代的发展,软件的版本也在与时俱进,不断更新,以期更加满足用户的需求。

但我们怎么能信誓旦旦地保证版本的更新一定会让用户满意呢?我们的努力真的能让改变发生吗?

在保证良好的用户体验的同时不断进行技术创新,这对网飞公司来说并非易事。

通过使用重放设备里的实时日志作为事件源,我们导出度量,以便了解和量化用户设备处理浏览和回放的无缝程度。

Log to Metric Data Pipeline

一旦我们得到了这些指标,我们就把它们输入数据库。每一项指标都会标注出所使用设备的隐藏细节,例如,该设备是智能电视、iPad还是Android手机。这使我们能够根据不同方面进行设备分类和数据查看。这反过来又使我们能够排除掉可能只影响特定群体的问题,例如应用程序的版本、设备的特定类型或特定国家。

此聚合数据可立即通过控制面板或即席查询进行查询。这些指标还将持续检查报警信号,例如新版本是否影响某些用户或设备的回放或浏览。所有这些检查旨在提醒负责团队尽快解决问题。

在软件更新期间,我们为一部分用户提供新版本,并使用这些实时度量来比较新版本与以前版本的性能。度量中的任何回归/退化都会给我们传递一个信号,让我们中止更新,并将那些获得新版本的用户还原回以前的版本。

因为这些数据的处理速度可超过每秒200万次,想要将其放入一个可以快速查询的数据库是非常困难的。我们需要足够的维度以便有效隔离问题,这样每天可生成1150多亿行数据。在网飞,我们利用Apache Druid来帮助我们解决这个挑战。

Druid

“Apache Druid 是一个高性能的实时分析型数据库,它是为快速查询和快速摄入数据的工作流而设计的。Druid 擅长即时数据可视化、即席查询,运行分析和高性能并发处理。” —druid.io

如上所述,Druid非常适用于我们的用例。因为它可以快速查询和摄入具有高基数的对象数据。Druid isnot a relational Druid虽然不是关系数据库,但一些概念是可转移的。我们有数据源而非表。与关系数据库一样,这些表都是数据的逻辑组合,以列的形式呈现出来。但与关系数据库不同的是,它不支持join操作。因此,要确保我们想要筛选或分组的列包含于每个数据源中。

数据源中,列的属性可以分为3类:时间戳列,维度列和指标列。

Druid里的一切都是由时间决定的。每个数据源都有一个时间戳列,以此作为主要的分区机制。维度列用于筛选、查询或分组。指标列是用于聚合数据,一般为数值型。

Druid通过删除执行join操作的能力,并假设数据是由时间戳列键入,可对其存储、分发和查询数据的方式进行一些优化,这样就可以将数据源扩展到数万亿行,并且仍然可以在10毫秒内响应查询。

为了实现这种可伸缩性,Druid将存储的数据划分为时间块,并可根据数据和用例为时间块配置适当的持续时间。目前我们的数据和用例使用1小时的时间块。时间块内的数据存储在一个或多个segment中。每个segment保存的数据行都在由其时间戳列关键列确定的时间块内。Druid可以配置segment的大小,使行数或段文件的总大小有一个上限。

Example of Segments

在查询数据时,Druid将查询发送到集群中的所有节点,这些节点为查询范围内的时间块保存segment。在将中间结果发送回查询代理节点之前,每个节点都会在其保存的数据上并行处理查询。最后结果由代理节点合并后返回给调用方。

Druid Cluster Overview Druid

摄取

数据库的插入是实时的。事件(我们的例子中是指标列)是从Kafka流中读取的,而不是单独插入到数据源中。每个数据源使用一个主题。在Druid中,我们使用Kafka Indexing Tasks来创建多个分布在实时节点(中间管理器)中的索引工作器。

每个索引器都会订阅主题,并从工作流中读取其事件共享。索引器根据摄取规范从事件消息中提取值,并将创建的行累积到内存中。一旦创建了行,就可以对其进行查询。如果查询到达的时间块仍由索引器填充,则索引器本身将提供服务。由于索引任务基本上要执行两个作业,即接收和安排查询,因此要及时将数据发送到历史节点并以更优化的方式将查询工作转移给它们,这是非常重要的。

Druid可以在接收数据时进行roll-up操作,将需要存储的原始数据最小化。Roll-up是一种集合或预聚合的形式。在某些情况下,对数据执行roll-up可以显著减少需要存储的数据量,甚至可能按数量级减少行数。然而,这种存储缩减是有代价的:我们失去了查询单个事件的能力,只能在预定义的查询粒度下进行查询。对于我们的用例,我们选择了1分钟的查询粒度。

在摄取过程中,如果出现具有相同的维度的行,并且它们的时间戳在同一分钟(我们的查询粒度)内,那么这些行将被roll-up。这意味着行是由所有度量值相加后再加上计数器而组成的,这样一来我们就知道有多少事件对该行的值有贡献。这种形式的roll-up可以显著减少数据库中的行数,这样需要操作和聚合的行就会减少,从而加快查询速度。

一旦累积的行数达到某个阈值,或者segment已打开太长时间,那么这些行就会被写入segment文件并卸载到深层存储中。随后索引器通知协调器该segment已准备好,以便协调器告诉一个或多个历史节点执行加载。一旦segment成功加载到历史节点中,它便从索引器中卸载,针对该数据的任何查询都将由历史节点提供服务。

数据管理

如您所想,随着维度基数的增加,在同一分钟内发生相同事件的可能性就会降低。roll-up后的管理基数是实现良好查询性能的有力手段。

为了达到所需的摄取量,我们运行了许多索引器实例。即使通过roll-up将索引任务中的相同行合并后,在索引任务的同一实例中获得这些相同行的可能性也非常低。为了解决这个问题并使roll-up操作最优化,我们安排了一个任务,将在给定时间块内的所有segment都移交给历史节点之后运行。

该压缩任务从深层存储中获取时间块内的所有segment,并通过map/reduce作业重新创建segment并实现完美roll-up。然后,新的segment将由历史节点加载和发布,以替换和取代原先roll-up不足的segment。在我们的例子中,通过使用额外压缩任务,行数提高了2倍。

想要弄清给定时间块内的所有事件在何时接收完毕并非易事。因为Kafka上可能有延迟到达的数据,或者索引器可能花时间将segment传递给历史节点。为了解决这个问题,我们在运行压缩之前会给予限制并执行检查。

首先,太晚到达的数据会被丢弃。我们认为这种做法太过老派,在实时系统中毫无用处。这种做法会为延迟的数据划定范围。其次,压缩任务的调度也会有延迟,这使得在正常流中的segment有足够的时间被卸载到历史节点上。最后,当给定时间块的压缩任务启动时,它会查询segment的元数据,以检查是否有任何相关segment仍在写入或传递。如果有,它将等待几分钟后再试,以确保压缩作业处理完所有数据。

如果没有采取这些措施,我们发现有时会丢失数据。压缩开始时仍在写入的segment将被新压缩的segment覆盖,这些segment具有更高版本,因此具有优先权。这种做法实际删除了那些尚未完成传递部分所包含的数据。

查询

Druid支持两种查询语言:Druid SQL查询和本地查询。在底层,Druid SQL查询被转换为本地查询。本地查询作为JSON提交到REST端点,这是我们的主要使用机制。

大多数对集群的查询都是由自定义的内部工具(如仪表板和警报系统)生成的。最初设计这些系统是用于我们内部开发和开源的时间序列数据库Atlas。因此,这些工具使用Atlas Stack查询语言。

为了加快Druid采用查询,并允许重复使用现有工具,我们添加了一个转换层,它接受Atlas查询,将它们重写为Druid查询,发出查询并将结果重新格式化为Atlas结果。这个抽象层不会改变现有工具的使用,并且使用户在不创建额外的学习曲线的情况下仍可访问Druid数据存储中的数据。

调整比例

在调整集群节点配置的同时,我们高速运行一系列可重复和可预测的查询,以获得每个给定配置的响应时间和查询吞吐量的基准。这些查询旨在隔离集群的各个部分,以检查查询性能的改进或回归/退化。

例如,我们对最新的数据执行目标查询,以便只对中间管理器进行查询。同样地,对于持续时间较长的数据,我们只查询较旧的数据,以确保只查询历史节点来测试缓存配置。并且再次使用高基数维度查询该组,以检查合并所受的影响。我们继续调整和运行这些基准测试,直到对查询性能满意为止。

在这些测试中,我们发现调整缓冲区的大小、线程数、查询队列长度和分配给查询缓存的内存对查询性能有显著影响。然而,压缩作业的引入对查询性能的影响更为显著,它通过完美的再次roll-up,对未充分roll-up的segment重新压缩,。

我们还发现在历史节点上启用缓存非常有用,而在代理节点上启用缓存则没有什么影响,因此我们便不在代理节点上使用缓存。这也可能是我们的用例的问题,但我们所做的几乎每一个查询都会遗漏代理上的缓存,这可能是因为查询通常包含最新的数据,而这些数据不会像往常一样出现在任何缓存中。

摘要

来源:Pexels

在为用例和数据速率进行了多次调整后,Druid已经证明了它就像我们最初期望的那样全能。

我们已经有了一个可靠好用的系统,但还有更多的工作要做。随着接收量和速率不断增加,查询数量不断增多,复杂性也不断增强。因为这些详细数据的价值需要更多的团队才能实现,我们经常添加更多的度量和维度来推动系统更加努力工作。我们必须继续监视和调整以保持查询性能的正常。

目前,我们以每秒接收超过200万个事件、查询超过1.5万亿行的速度获取用户体验服务的详细信息。所有这些帮助我们在保持高质量的网飞体验的同时实现不断创新。

以上是网飞公司的一个经验谈,相信能给予其他公司一定启发。

留言 点赞 关注

我们一起分享AI学习与发展的干货
欢迎关注全平台AI垂类自媒体 “读芯术”

(添加小编微信:dxsxbb,加入读者圈,一起讨论最新鲜的人工智能科技哦~)

发布了1031 篇原创文章 · 获赞 3012 · 访问量 68万+

猜你喜欢

转载自blog.csdn.net/duxinshuxiaobian/article/details/105443716