Pull 模式无法扩展 - 真的吗?

2016 年 7 月 23 日作者 Julius Volz

我们来谈谈一个流传已久的迷思。每当讨论监控系统并提及 Prometheus 基于拉取(pull)的指标收集方法时,总会有人插话说,拉取模式“根本无法扩展”。他们给出的理由通常很模糊,或者只适用于与 Prometheus 根本不同的系统。事实上,我们在最大规模的环境中使用过基于拉取的监控系统,这种说法与我们自己的运维经验背道而驰。

我们已经有一个关于为什么 Prometheus 选择拉取而非推送的常见问题解答条目,但它没有专门关注扩展性方面。让我们来仔细看看围绕这个说法常见的误解,并分析它们是否以及如何适用于 Prometheus。

Prometheus 不是 Nagios

当人们想到一个主动拉取数据的监控系统时,他们通常会想到 Nagios。Nagios 以扩展性不佳而闻名,部分原因在于它会为主动检查(active checks)生成子进程,这些子进程可以在 Nagios 主机上运行任意操作以确定某个主机或服务的健康状况。这种检查架构确实扩展性不佳,因为中央 Nagios 主机很快就会不堪重负。因此,人们通常将检查配置为每隔几分钟才执行一次,否则就会遇到更严重的问题。

然而,Prometheus 采用了一种完全不同的方法。它不执行检查脚本,而只是通过网络从一组被监控的目标(instrumented targets)收集时间序列数据。对于每个目标,Prometheus 服务器只是通过 HTTP 获取该目标所有指标的当前状态(使用 goroutines 高度并行化),并且没有其他与拉取相关的执行开销。这就引出了下一点。

谁发起连接并不重要

就扩展性而言,由谁发起用于传输指标的 TCP 连接并不重要。无论哪种方式,建立连接的成本与指标负载和其他所需工作相比都微不足道。

你可能会说,但基于推送的方法可以使用 UDP,从而完全避免连接建立!的确如此,但在 Prometheus 中,TCP/HTTP 的开销与 Prometheus 服务器为摄取数据所做的其他工作(尤其是将时间序列数据持久化到磁盘)相比仍然可以忽略不计。用一些数字来证明:一个大型的 Prometheus 服务器可以轻松存储数百万个时间序列,记录到每秒接收 80 万个样本(这是在 SoundCloud 使用真实生产指标数据测得的)。假设抓取间隔为 10 秒,每个主机有 700 个时间序列,这允许你用单个 Prometheus 服务器监控超过 10,000 台机器。这里的扩展瓶颈从来都与拉取指标无关,而通常在于 Prometheus 服务器将数据摄取到内存,然后可持续地在磁盘/SSD 上持久化和清理数据的速度。

此外,尽管如今网络相当可靠,但使用基于 TCP 的拉取方法可以确保指标数据可靠到达,或者至少在网络故障导致指标传输失败时,监控系统能立即知晓。

Prometheus 不是基于事件的系统

一些监控系统是基于事件的。也就是说,它们在每个独立事件(如一个 HTTP 请求、一个异常)发生时,立即将其报告给中央监控系统。这个中央系统要么将事件聚合为指标(StatsD 是这方面的典型例子),要么单独存储事件以供后续处理(ELK 堆栈就是这样一个例子)。在这样的系统中,拉取确实会带来问题:被监控的服务必须在两次拉取之间缓冲事件,并且拉取必须非常频繁,才能模拟出与推送方法相同的“实时性”,且不会导致事件缓冲区溢出。

然而,Prometheus 同样不是一个基于事件的监控系统。你不会向 Prometheus 发送原始事件,它也无法存储这些事件。Prometheus 的业务是收集聚合后的时间序列数据。这意味着它只关心定期收集一组给定指标的当前*状态*,而不是导致这些指标生成的底层事件。例如,一个被监控的服务不会在处理每个 HTTP 请求时都向 Prometheus 发送消息,而只是在内存中对这些请求进行计数。这个计数过程可以每秒发生数十万次,而不会产生任何监控流量。然后,Prometheus 每 15 或 30 秒(或你配置的任何间隔)向服务实例询问当前的计数器值,并将该值与抓取时间戳一起作为样本存储。其他指标类型,如仪表盘(gauges)、直方图(histograms)和摘要(summaries),也以类似方式处理。由此产生的监控流量很低,在这种情况下,基于拉取的方法也不会产生问题。

但是现在我的监控系统需要知道我的服务实例了!

采用基于拉取的方法,你的监控系统需要知道哪些服务实例存在以及如何连接到它们。一些人担心这会给监控系统带来额外的配置,并将其视为一个运维上的可扩展性问题。

我们会说,在任何严肃的监控设置中,你都无法逃避这种配置工作:如果你的监控系统不知道世界*应该*是什么样子,哪些被监控的服务实例*应该*存在,它怎么能判断出一个实例是从未上报数据、因故障宕机,还是真的不再应该存在?只有当你完全不关心单个实例的健康状况时,这种情况才是可以接受的,比如当你只运行一些临时的 worker,只要有足够多的 worker 上报结果就足够了。大多数环境并非完全如此。

如果监控系统无论如何都需要知道世界的期望状态,那么基于推送的方法实际上需要*更多*的配置。不仅你的监控系统需要知道哪些服务实例应该存在,你的服务实例现在也需要知道如何访问你的监控系统。拉取方法不仅需要的配置更少,还使你的监控设置更加灵活。通过拉取,你可以直接在笔记本电脑上运行一个生产监控的副本进行实验。它还允许你使用其他工具获取指标,或手动检查指标端点。为了实现高可用性,拉取允许你并行运行两个配置完全相同的 Prometheus 服务器。最后,如果你需要移动监控系统的访问端点,拉取方法不需要你重新配置所有指标源。

在实践层面,Prometheus 通过其对云提供商和容器调度系统的多种服务发现机制的内置支持,使得配置世界的期望状态变得容易:Consul、Marathon、Kubernetes、EC2、基于 DNS 的服务发现、Azure、Zookeeper Serversets 等等。如果需要,Prometheus 还允许你插入自己的自定义机制。在微服务世界或任何多层架构中,如果你的监控系统使用与你的服务实例发现其后端相同的方法来发现监控目标,这本身就是一个根本性的优势。这样你就可以确保你监控的是与服务生产流量相同的目标,并且你只需要维护一个服务发现机制。

意外地 DDoS 你的监控系统

无论是拉取还是推送,任何时间序列数据库在收到超过其处理能力的样本时都会崩溃。然而,根据我们的经验,基于推送的方法意外搞垮你的监控系统的可能性稍高一些。如果对从哪些实例摄取哪些指标的控制不是集中式的(在你的监控系统中),那么你就会面临实验性或流氓作业突然向你的生产监控系统推送大量垃圾数据并使其崩溃的危险。虽然基于拉取的方法(它只控制从哪里拉取指标,而不控制指标负载的大小和性质)仍有多种方式可能发生这种情况,但风险较低。更重要的是,这类事件可以在一个中心点得到缓解。

真实世界的证明

除了 Prometheus 已经在现实世界中被用于监控非常大的部署(例如,在 DigitalOcean 用它来监控数百万台机器),还有其他一些著名的例子,表明基于拉取的监控在最大规模的环境中被成功使用。Prometheus 的灵感来源于 Google 的 Borgmon,它曾经(并且部分现在仍然)在 Google 内部用于监控所有关键生产服务,采用的也是基于拉取的方法。我们在 Google 使用 Borgmon 时遇到的任何扩展性问题也都不是由其拉取方法引起的。如果一个基于拉取的方法可以扩展到拥有数十个数据中心和数百万台机器的全球环境,你很难说拉取模式无法扩展。

但是拉取模式还有其他问题!

确实存在一些难以用基于拉取的方法监控的场景。一个著名的例子是,当你有许多分散在全球各地的端点,由于防火墙或复杂的网络设置而无法直接访问,并且在每个网络分段中直接运行 Prometheus 服务器也不可行。这并非 Prometheus 被设计用来应对的环境,尽管通常可以找到变通方法(通过 Pushgateway 或重构你的部署)。无论如何,这些关于基于拉取监控的剩余担忧通常与扩展性无关,而是由于围绕打开 TCP 连接的网络操作困难。

那么一切都好?

本文探讨了围绕基于拉取的监控方法最常见的可扩展性担忧。鉴于 Prometheus 和其他基于拉取的系统在非常大的环境中被成功使用,并且拉取方面在现实中并不构成瓶颈,结果应该很明确:“拉取无法扩展”的论点并不是一个真正的问题。我们希望未来的辩论将集中在比这个转移视线的伪问题更重要的方面。