“拉取”模式无法扩展吗?——其实不然
2016年7月23日作者 Julius Volz
让我们来聊聊一个特别顽固的误区。每当人们讨论监控系统,且谈到 Prometheus 基于“拉取”(pull)的指标收集方式时,总会有人跳出来说,基于拉取的方法“根本无法扩展”。他们给出的理由通常含糊不清,或者只适用于与 Prometheus 本质上完全不同的系统。事实上,在处理过大规模的拉取式监控后,我们发现这种说法与我们的实际运维经验背道而驰。
我们已经在常见问题解答(FAQ)中说明了 Prometheus 为什么选择拉取模式而不是推送模式,但该文档并未特别聚焦于扩展性方面。让我们仔细看看围绕这一论点的常见误解,并分析它们是否以及如何适用于 Prometheus。
Prometheus 不是 Nagios
当人们想到主动拉取的监控系统时,往往会想到 Nagios。Nagios 之所以被认为扩展性差,部分原因是它通过派生子进程来进行主动检查,这些子进程可以在 Nagios 主机上运行任意操作以判断主机或服务的健康状况。这种检查架构确实无法很好地扩展,因为中央 Nagios 主机很快就会超负荷。结果就是,人们通常将检查频率设置为几分钟一次,或者面临更严重的性能问题。
然而,Prometheus 采取了完全不同的方法。它不执行检查脚本,而是仅仅通过网络从一组预埋了指标的目标(target)中收集时间序列数据。对于每个目标,Prometheus 服务器只需通过 HTTP 获取该目标所有指标的当前状态(通过 goroutine 以高度并行的方式完成),除此之外没有任何与拉取相关的执行开销。这就引出了下一点。
谁发起连接并不重要
从扩展性的角度来看,由谁发起传输指标数据的 TCP 连接并不重要。无论哪种方式,建立连接的工作开销与指标载荷及其他必需工作相比,微乎其微。
“但是推送模式可以使用 UDP,从而彻底避免连接建立!”你可能会这样说。确实如此,但 Prometheus 中的 TCP/HTTP 开销与 Prometheus 服务器摄入数据(尤其是将时间序列数据持久化到磁盘)所需的其他工作相比,仍然可以忽略不计。用数据说话:单台大型 Prometheus 服务器可以轻松存储数百万个时间序列,最高记录为每秒摄入 80 万个样本(根据 SoundCloud 的实际生产指标数据测算)。假设抓取间隔为 10 秒,每台主机产生 700 个时间序列,那么单台 Prometheus 服务器就可以监控超过 1 万台机器。这里的扩展性瓶颈从未与拉取指标有关,通常瓶颈在于 Prometheus 服务器将数据摄入内存,以及随后将数据持久化至磁盘/SSD 并进行过期的速度。
此外,尽管如今网络相当可靠,但使用基于 TCP 的拉取模式可以确保指标数据可靠到达,或者至少能在网络中断导致指标传输失败时,让监控系统立即知晓。
Prometheus 不是基于事件的系统
有些监控系统是基于事件的。也就是说,它们在每个事件(如 HTTP 请求、异常等)发生时,立即将其上报给中央监控系统。然后,这个中心系统要么将这些事件聚合为指标(StatsD 是典型例子),要么将事件分别存储以便后续处理(ELK 堆栈就是例子)。在这样的系统中,拉取模式确实会有问题:被监控服务必须在拉取间隔之间缓存事件,而且拉取频率必须极高,才能模拟推送模式的“实时性”,并避免事件缓冲区溢出。
然而,再次重申,Prometheus 不是基于事件的监控系统。你既不会向 Prometheus 发送原始事件,它也无法存储这些事件。Prometheus 的工作是收集聚合后的时间序列数据。这意味着它只对定期收集给定指标集的当前状态感兴趣,而不是产生这些指标背后的底层事件。例如,被监控服务不会在处理每个 HTTP 请求时就给 Prometheus 发送消息,而是在内存中简单地累加这些请求数。这可以在每秒发生数十万次而不会产生任何监控流量。Prometheus 只是每隔 15 或 30 秒(或你配置的任何时间)询问服务实例当前的计数器值,并将该值与抓取时间戳一起存储为样本。其他指标类型(如仪表盘、直方图和摘要)的处理方式类似。产生的监控流量很低,在这种情况下,拉取模式也不会造成任何问题。
但现在我的监控系统需要了解我的服务实例!
在拉取模式下,你的监控系统需要知道存在哪些服务实例以及如何连接到它们。有些人担心这需要监控系统侧进行额外的配置,并将其视为一个运维扩展性问题。
我们要说的是,对于任何严肃的监控设置,这种配置工作都是避无可避的:如果你的监控系统不知道世界应该是什么样子,也不知道应该存在哪些受监控的服务实例,那么当某个实例从未上报、因故障宕机或确实不再存在时,它又如何能判断出来呢?除非你根本不关心单个实例的健康状况(例如,只运行短暂的 Worker 任务,只要有足够数量的任务报告结果即可),否则这种担忧毫无意义。大多数环境并非全是这种情况。
如果监控系统无论如何都需要知道理想的世界状态,那么推送模式实际上总共需要更多的配置。不仅你的监控系统需要知道哪些服务实例应该存在,你的服务实例现在还需要知道如何连接到你的监控系统。拉取模式不仅需要的配置更少,还使监控设置更加灵活。使用拉取模式,你可以直接在笔记本电脑上运行一个生产环境监控的副本进行实验。它还允许你使用其他工具轻松获取指标,或手动检查指标端点。为了实现高可用性,拉取模式允许你简单地并行运行两个配置相同的 Prometheus 服务器。最后,如果你必须移动监控系统的可访问端点,拉取模式不需要你重新配置所有指标来源。
从实践上看,Prometheus 通过其内置的对云厂商和服务发现机制的支持,使得配置理想状态变得十分容易:Consul、Marathon、Kubernetes、EC2、DNS SD、Azure、Zookeeper Serversets 等等。如果有需要,Prometheus 还允许你插入自定义的发现机制。在微服务环境或任何多层架构中,如果监控系统使用与服务实例发现后端时相同的方法来发现监控目标,这在本质上是一种优势。这样,你可以确保你正在监控的是那些正在处理生产流量的实例,并且只需维护一种发现机制。
意外地 DDoS 攻击你的监控系统
无论是拉取还是推送,任何时间序列数据库如果收到的样本量超过了其处理能力,都会崩溃。然而,根据我们的经验,推送模式更容易意外地让你的监控系统瘫痪。如果对于从哪些实例摄入哪些指标的控制不是集中式的(在监控系统中),那么你就会面临实验性任务或失控任务突然向生产监控推送大量垃圾数据并将其搞垮的风险。虽然拉取模式下仍然有多种方式会导致这种情况(因为拉取模式只控制从哪里拉取,但不控制指标载荷的大小和性质),但风险要低得多。更重要的是,此类事件可以在中心节点处进行缓解。
现实世界的证明
除了 Prometheus 已经被用于监控现实世界中非常大规模的系统(例如 在 DigitalOcean 监控数百万台机器)之外,还有其他显着的例子表明拉取式监控在最大规模的环境中也得到了成功应用。Prometheus 的灵感来源于 Google 的 Borgmon,后者在 Google 内部被用来通过拉取方式监控其所有关键生产服务。我们在 Google 使用 Borgmon 时遇到的任何扩展性问题,也都不是由其拉取方法导致的。如果一种基于拉取的方法可以扩展到拥有数十个数据中心和数百万台机器的全球环境,你很难说“拉取模式无法扩展”。
但拉取模式还有其他问题!
确实存在一些难以用拉取模式监控的场景。一个显着的例子是,你拥有遍布世界各地的许多端点,由于防火墙或复杂的网络设置而无法直接访问,且在每个网络段中直接运行 Prometheus 服务器也不可行。这并不完全是 Prometheus 的设计初衷,尽管通常可以通过变通方法实现(通过 Pushgateway 或重构你的环境)。无论如何,这些关于拉取式监控的遗留顾虑通常不是与扩展性有关,而是由于在打开 TCP 连接方面的网络运维困难。
所以一切都好吗?
本文探讨了围绕拉取式监控方法最常见的扩展性顾虑。鉴于 Prometheus 和其他拉取式系统已在极大规模的环境中取得成功,且“拉取”本身在现实中并未构成瓶颈,结论应当是显而易见的:“拉取模式无法扩展”这一论点并非真实存在的问题。我们希望未来的辩论能够集中在比这个“转移注意力的论点”更重要的问题上。