驱蚊器喵的翻译平台

Can you hear the gravity?

  1. 1. 如何从边缘服务器收集日志
  2. 2. 关于聚合
  3. 3. ClickHouse
  4. 4. 基础设施和数据整合
  5. 5. 数据可视化

原文地址:https://blog.cloudflare.com/how-cloudflare-analyzes-1m-dns-queries-per-second/
原文标题:How Cloudflare analyzes 1M DNS queries per second
原文作者:Marek Vavruša
原文写于:2017/05/11

译者:驱蚊器喵#ΦωΦ
翻译水平有限,有不通顺的语句,请见谅。


上周五,我们宣布为所有 Cloudflare 客户提供 DNS 分析服务。由于我们庞大的规模 - 当你读完这篇文章时,Cloudflare DNS 将处理数以百万计的 DNS 查询请求 - 所有我们必须在解决这个问题的过程中有所创新。在这篇文章中,我们将介绍组成 DNS 分析的系统,这些系统帮助我们每月梳理数万亿的 DNS 日志。

如何从边缘服务器收集日志

Cloudflare 已经有一个 HTTP 日志的数据管道。我们想利用该系统的功能来进行新的 DNS 分析。每当我们的一个边缘服务收到一个 HTTP 请求时,它就会生成一个 Cap’n Proto 格式的结构化日志消息,并将其发送到本地复用器服务。考虑到数据量,我们选择不记录完整的 DNS 消息有效载荷,只记录我们感兴趣的遥测数据,如响应代码、大小或查询名称,这使得我们平均每条消息只保留约 150 字节。然后将其与处理元数据融合,如定时信息和查询处理过程中触发的异常。在边缘服务器上融合数据和元数据的好处是,我们可以将计算成本分摊到数千台边缘服务器上,并且只记录我们绝对需要的数据。

多路复用器服务 (被称为 “日志转发器”),在每个边缘节点上运行,将来自多个服务的日志消息组装起来,并通过 TLS 安全通道将其传送到我们的仓库进行处理。在仓库中运行的对应服务接收日志并将其解复用到多个 Apache Kafka 集群中。多年来,Apache Kafka 已经被证明是一个非常宝贵的服务,它可以在生产者和下游消费者之间进行缓冲,防止在消费者故障或需要维护时造成数据丢失。自 0.10 版本以来,Kafka 允许机架感知分配副本,这提高了对机架或站点故障的弹性,使我们能够容错存储未处理的消息。

拥有一个带有结构化日志的队列,让我们能够在不需要访问生产节点的情况下,对问题进行追溯调查,但事实证明,这有时是相当费力的。在项目的早期,我们会浏览队列以找到我们需要的粗略时间段的偏移量,然后将数据提取到 Parquet 格式的 HDFS 中进行离线分析。

关于聚合

HTTP 分析服务是围绕流处理器生成聚合而建立的,所以我们计划利用 Apache Spark 将日志自动流到 HDFS。由于 Parquet 本身不支持索引,也不支持以避免全表扫描的方式安排数据,因此对于在线分析或通过 API 提供报告是不切实际的。有像 parquet-index 这样的扩展,可以在数据上创建索引,但不是即时的。考虑到这一点,最初的计划是只向客户展示汇总报告,并保留原始数据用于内部故障排除。

聚合摘要的问题是,它们只对低基数(唯一值的数量)的列有效。通过聚合,在给定的时间框架内,每一列都会爆发出与唯一条目数相等的行数,所以对于响应代码这种只有12个可能值的列上进行聚合是可行的,但例如查询名就不行。域名是受流行度影响的,所以如果,一个流行的域名在一分钟内被查询了1000次,那么可以期望实现每分钟聚合减少1000倍的行数,然而实际情况并非如此。

由于 DNS 缓存的工作方式,解析器将在 TTL 的持续时间内,不经过权威服务器,而从缓存中回答相同的查询。TTL 往往会超过一分钟。因此,虽然权威服务器会多次看到相同的请求,但我们的数据却偏向于非缓存查询,如错别字或随机前缀子域攻击。在实践中,当按查询名进行聚合时,我们看到行数会减少 0 - 60 倍,因此将聚合存储在多个聚合条件中几乎可以否定行数的减少。聚合也是在多个聚合条件和键组合的情况下进行的,所以在高基数列上聚合甚至会导致比原始的行数更多。

由于这些原因,我们一开始只在域级别上汇总日志,这对趋势分析已经足够,但对根本原因分析来说还是太粗糙了。例如,在一个案例中,我们正在调查我们的一个数据中心的短暂不可用性。有了未分类的数据,我们可以将问题缩小到经历延迟峰值的特定 DNS 查询,然后将查询与配置错误的防火墙规则相关联。像这样的情况,如果只使用聚合数据,调查起来会困难得多,因为它只影响了一小部分请求,而这些请求会在聚合数据中丢失。

于是我们开始研究 OLAP (Online Analytical Processing,在线分析处理) 系统。我们研究的第一个系统是 Druid。它的功能和前端 (Pivot 和以前的 Caravel )如何对数据进行切分,生成任意维度的报告给我们留下深刻的印象。Druid 已经在一天处理超过千亿事件的类似环境中部署了,所以我们对它的工作很有信心,但在对样本数据进行测试后,我们无法证明数百个节点的硬件成本是合理的。大约在同一时间,Yandex 开源了他们的 OLAP 系统 ClickHouse

ClickHouse

ClickHouse 的系统设计要简单得多 - 集群中的所有节点功能平等,只使用 ZooKeeper 进行协调。我们建立了一个由几个节点组成的小集群,开始测试,发现性能相当惊人,与分析型 DBMS 的性能对比中宣传的结果相符,于是我们开始着手建立概念验证。第一个障碍是缺乏工具和社区规模小,所以我们深入研究了 ClickHouse 的设计,了解它的工作原理。

ClickHouse 不支持直接从 Kafka 摄取,因为它只是一个数据库,所以我们使用 Go 编写了一个适配器服务。它从 Kafka 读取 Cap’n Proto 编码的消息,将其转换为 TSV,并通过 HTTP 接口分批插入到 ClickHouse 中。后来,我们使用了一个 Go 库,重写了这个服务,使用原生的 ClickHouse 接口来提升性能。从那以后,我们又为项目贡献了一些性能改进。在摄取性能评估过程中,我们了解到的一件事是,ClickHouse 的摄取性能高度依赖于批次大小 - 你一次插入的行数。为了了解原因,我们进一步研究了 ClickHouse 如何存储数据。

ClickHouse 存储最常用的表引擎是 MergeTree 系列。它在概念上类似于 Google 的 BigTable 和 Apache Cassandra 中使用的 LSM 算法,然而它避免了中间的内存表,直接写入磁盘。这使它具有极好的写入吞吐量,因为每一个插入的批次只需按 “主键” 排序、压缩,然后写入磁盘,形成一个段。由于没有内存表,也没有任何数据 “新鲜度” 的概念,这也意味着它只支持追加,不支持数据修改或删除。目前删除数据的唯一方法是按日历月删除数据,因为段永远不会重叠在一个月的边界。ClickHouse 团队正在积极努力使之可配置。另一方面,这使得写入和段合并不存在冲突,所以摄取吞吐量会随着并行插入的数量线性扩展,直到 I/O 或内核饱和。然而,这也意味着它不适合微小的批次,这也是为什么我们依靠 Kafka 和插入服务程序来进行缓冲的原因。ClickHouse 就会在后台不断地合并段,所以很多小的部分会被合并并写入更多的次数(从而增加写入放大率),而过多的未合并部分会触发插入的阈值,直到开始合并。我们发现,作为实时摄取和摄取性能之间的权衡,每秒按表插入若干次效果最好。

表读取性能的关键是索引和磁盘上数据的排列。无论处理速度有多快,当引擎需要从磁盘上扫描 TB 级的数据,并且只使用其中的一小部分时,都会花费时间。ClickHouse 是一个列式存储,所以每个数据文件都包含了每一列的文件,每一行的排序值。这样就可以跳过查询中不存在的整列,然后用向量化执行的方式并行处理多个数据文件。为了避免全面扫描,每个数据文件也有一个稀疏的索引文件。鉴于所有的列都是按 “主键” 排序,索引文件只包含每第 N 行的标记(捕获的行),以便即使对于非常大的表,也能将其保存在内存中。例如,默认设置是每 8,192 行做一个标记。这样一来,只需要 122,070 个标记,就可以稀疏地索引一个有1万亿行的表,很容易就能放在内存中。请参阅 ClickHouse 中的主键,深入了解其工作原理。

当使用主键列查询表时,索引返回所考虑的行的大致范围。理想情况下,这些范围应该是宽的、连续的。例如,典型的用法是,为各个域生成报告时,将域放在主键的第一个位置上,将导致每列中的行按域排序,使得各个域的磁盘读取是连续的,如果主要按时间戳排序则不连续。行只能以一种方式进行排序,所以主键的选择必须考虑到典型的查询负载。在我们的案例中,我们对各个域的读取查询进行了优化,并且有一个单独的表,里面有采样数据,用于探索性查询。经验教训是,我们做了几个表,而不是去试图优化所有目的的索引,然后将其分开。

其中一个这样的专业化是具有域聚合的表。跨所有行的查询明显更昂贵,因为没有机会将数据排除在扫描之外。这使得分析师在长时间上计算基本的聚合不太实用,所以我们决定使用物化视图来增量计算预定义的聚合,如计数器、去重和量化。物化视图利用批量插入时的排序阶段来做生产性工作 - 计算聚合。所以在新插入的段进行排序后,也会产生一个表,表中的行代表维度,列代表聚合函数状态。聚合状态和最终结果的区别在于,我们可以使用任意的时间分辨率来生成报表,而不需要实际将预计算的数据存储在多个分辨率中。在某些情况下,状态和结果可以是一样的 - 例如基本的计数器,通过求和每分钟的计数可以生成每小时的计数,然而求和独立访客(UV)或延迟量化是没有意义的。这时聚合状态就有用得多,因为它允许有意义地合并更复杂的状态,比如 HyperLogLog (HLL) 位图,通过分钟聚合产生每小时的独立访客(UV)估计数值。缺点是存储状态可能比存储最终值的花费要昂贵得多 - 上面说到的, HLL 状态在压缩后往往是 20-100 字节/行,而一个计数器只有 8 字节(平均压缩 1 字节)。然后,这些表格被用来快速可视化各域或站点的总体趋势,也被我们的 API 服务用来进行简单的查询。将增量聚合和非聚合数据放在同一个地方,使我们能够通过完全放弃流处理来简化架构。

基础设施和数据整合

我们一开始使用的是 RAID-10,每个节点上有 12 块 6TB 的机械磁盘,但在第一次不可避免的磁盘故障后,我们重新评估了它。在第二次迭代中,我们迁移到了 RAID-0,原因有二。首先,不可能只热插拔有问题的磁盘,其次,阵列重建需要几十个小时,这降低了 I/O 性能。替换一个故障节点并通过网络(2x10GbE)来内部复制填充数据,比等待阵列重建完成要快得多。为了补偿较高的节点故障概率,我们改用 3 路复制,并将每个碎片的副本分配到不同的机架上,并开始规划复制到一个单独的数据仓库。

另一个磁盘故障凸显了我们使用的文件系统的问题。最初我们使用的是 XFS,但它在 2 个对等体同时复制的过程中会加锁,因此在完成复制之前就中断了段的复制。这个问题表现为随着断裂的部分被删除,I/O 活动很多,磁盘使用量增加不多,所以我们逐渐迁移到没有同样问题的 ext4 上。

数据可视化

当时我们仅仅依靠 PandasClickHouse 的 HTTP 接口来进行即席(ad-hoc)分析,但我们想更方便的分析和监控。由于我们从 Druid 的实验中认识了 Caravel (现在已经改名为 Superset ), 我们开始着手进行 Superset 与 ClickHouse 的整合。

Superset 是一个数据可视化平台,设计直观,允许分析师交互式地对数据进行切分,而无需编写一行 SQL。它最初是由 AirBnB 为 Druid 搭建和开源的,但随着时间的推移,它已经获得了基于 SQL 的后端支持,使用 SQLAlchemy,这是一种针对数十种不同数据库方言的抽象和 ORM。所以我们写了一个 ClickHouse 方言 并开源,最后集成到原生的 Superset,前几天已经合并了。

Superset 对我们的临时可视化有很好的作用,但对于我们的监控用例来说,它仍然不够完善。在Cloudflare,我们是 Grafana 的重度用户,我们所有的指标都是可视化的,所以我们写了一个 Grafana 集成,并将其开源。

Grafana 使我们能够用新的分析数据无缝扩展我们现有的监控仪表盘。我们非常喜欢 Grafana,所以我们想给我们的用户,提供同样的能力来查看分析数据。因此,我们建立了一个 Grafana 应用程序来可视化 Cloudflare DNS 分析的数据。最后,我们让它在您的 Cloudflare 面板分析中可用。随着时间的推移,我们将添加新的数据源,维度和其他有用的方式来可视化您从 Cloudflare 获取到的数据。

本文作者 : meow
This blog is under a CC BY-NC-SA 4.0 Unported License
本文链接 : https://translation.meow.page/post/how-cloudflare-analyzes-1m-dns-queries-per-second/

本文最后更新于 天前,文中所描述的信息可能已发生改变