本文旨在为中高级工程师和技术负责人提供一份关于构建大规模、高可用 Prometheus 监控体系的深度指南。我们将剖析原生 Prometheus 在长期存储和全局视图方面的核心痛点,并深入探讨基于 Thanos 的分布式解决方案。文章将从分布式系统、数据存储等基础原理出发,结合关键模块的实现细节与配置示例,分析其架构在性能、成本和一致性上的权衡,并最终给出一套可落地的分阶段架构演进路线图。
现象与问题背景
Prometheus 已成为云原生时代监控领域的事实标准。其基于 Pull 模型的时序数据采集、强大的 PromQL 查询语言以及高效的单机 TSDB 存储引擎,使其在单体或小规模集群监控中表现出色。然而,当企业业务规模化、服务容器化程度加深后,原生的 Prometheus 架构会迅速暴露出其固有瓶颈:
- 有限的本地存储: Prometheus 的 TSDB 设计为本地磁盘存储,数据保留周期(retention)与磁盘容量直接挂钩。为了防止磁盘耗尽,运维团队不得不设置较短的保留策略(如 15-30 天),这导致无法进行长期的容量规划、故障回顾和趋势分析。垂直扩容磁盘成本高昂且存在物理上限。
- 缺失的全局视图: 为了实现高可用(HA)和分片(Sharding),通常会部署多套 Prometheus 实例。例如,为每个 Kubernetes 集群部署一套,或者为同一个集群部署两套互为备份。这些实例彼此独立,形成一个个数据孤岛。当需要查询横跨多个集群或应用的指标时,缺乏一个统一的查询入口,无法形成全局视图。
- HA 带来的数据冗余: 在高可用部署中(例如两台 Prometheus 抓取相同的 targets),会产生两份完全一样的时间序列数据。在 Grafana 等可视化工具中展示时,会导致指标计算错误(如 count, sum 会被加倍),需要用户手动在查询层面进行复杂的处理,体验极差。
- 集中式管理的挑战: 随着 Prometheus 实例数量的增加,告警规则(Alerting Rules)和记录规则(Recording Rules)的管理变得分散和复杂。无法在一个地方统一定义和管理针对全局数据的规则。
这些问题的本质在于,Prometheus 被设计为一个强大的、自包含的单体监控节点,而非一个原生支持水平扩展的分布式系统。Thanos 的出现,正是为了在不侵入 Prometheus 内核的前提下,通过一系列正交解耦的组件,将其无缝拓展为一个具备无限存储、全局查询和高可用能力的分布式监控联邦。
关键原理拆解
Thanos 的架构设计精妙地运用了计算机科学中的多个基础原理,理解这些原理是掌握其运行机制的关键。
- “无共享”架构(Shared-Nothing Architecture): 这是分布式系统设计中的一个核心思想。Thanos 的各个组件(Sidecar, Querier, Store Gateway 等)在很大程度上是无状态或状态隔离的。例如,每个 Prometheus 及其 Sidecar 构成一个独立的单元,它们之间不直接通信。Querier 本身是无状态的,可以水平扩展。这种设计避免了中心节点的瓶颈,极大地提高了系统的可扩展性和容错性。
- 对象存储作为“无限”的持久化层: Thanos 将廉价、高可用的对象存储(如 AWS S3, Google GCS, MinIO)作为其长期数据的最终归宿。从存储原理上看,对象存储的接口(PUT/GET/DELETE)非常简单,非常适合作为时序数据块(TSDB Block)这种不可变数据的存储介质。它将存储的复杂性(如数据冗余、容量扩展)完全下沉到云服务商的基础设施中,使得上层架构可以专注于计算和查询逻辑。
- 日志结构合并树(LSM-Tree)思想的延伸: Prometheus 本身的 TSDB 就借鉴了 LSM-Tree 的思想,将内存中的数据(Head Block)定期刷写为磁盘上不可变的块文件。Thanos 将这一思想延伸到了分布式和长期存储领域。Thanos Compactor 组件的角色,就类似于 LSM-Tree 中的后台合并进程。它定期扫描对象存储中的小数据块,将它们合并成更大的、索引更优化的数据块,并进行降采样(Downsampling),从而显著降低长期查询的 I/O 开销和存储成本。
- 散播/收集(Scatter-Gather)查询模式: 这是分布式数据查询的经典模式。当 Thanos Querier 收到一个 PromQL 查询时,它并不知道数据具体在哪。它会将查询请求“散播”给所有已知的数据源:包括各个 Prometheus Sidecar(用于查询最近 2-3 小时内的热数据)和所有 Store Gateway(用于查询对象存储中的历史冷数据)。然后,它会“收集”所有数据源返回的部分结果,在内存中进行合并、去重和最终计算,最后返回给客户端。这种模式是实现“全局视图”的技术核心。
系统架构总览
一个典型的 Thanos 部署由以下核心组件构成,它们像乐高积木一样组合在一起,共同构成一个完整的监控体系:
- Sidecar: 作为代理进程,与每个 Prometheus 实例一同部署。它的核心职责有两个:1)将 Prometheus 本地已经持久化的数据块(通常是 2 小时一个)上传到对象存储中。2) 实现一个 gRPC API,供 Querier 查询该 Prometheus 实例内存中最新的、尚未上传的热数据。
- Querier: 面向用户的统一查询入口。它本身不存储任何数据,是无状态的。它实现了 Prometheus HTTP API,可以无缝对接 Grafana。它会从服务发现机制(如 DNS、Kubernetes Services)中获取所有 Sidecar 和 Store Gateway 的地址,并将用户查询分发给它们。它也是实现数据去重的核心模块。
- Store Gateway: 对象存储的“守门人”。它监控对象存储桶(Bucket)中的数据块元信息,并向 Querier 提供一个 gRPC 接口,用于查询历史数据。Store Gateway 本身也是无状态的,可以根据查询负载进行水平扩展。它内部维护了对象存储中数据块的索引缓存,以加速查询。
- Compactor: 负责对象存储中数据块的生命周期管理。它是一个全局唯一的、长时间运行的单例进程(也可以做主备)。主要工作包括:将小的数据块合并为大块(降低索引开销),对旧数据进行降采样(如从 15s 精度降为 5m 精度),以及执行数据保留策略(删除过期数据)。
- Ruler: 提供全局告警和记录规则评估。它通过 Querier 查询全局数据,并根据配置的规则进行评估。这解决了原生 Prometheus 规则分散管理的问题。
- Receiver: 针对 Push 场景,实现了 Prometheus Remote Write API,允许不支持 Pull 模型的应用将指标推送给 Thanos。
这些组件协同工作,形成一个完整的数据流:Prometheus 抓取指标 -> Sidecar 上传历史数据 -> Compactor 整理对象存储 -> 用户通过 Querier 查询 -> Querier 同时查询 Sidecar(近期数据)和 Store Gateway(历史数据)。
核心模块设计与实现
Sidecar: 数据上传与热数据代理
Sidecar 是连接 Prometheus 和 Thanos 生态的桥梁。其核心是 `shipper` 模块,它会周期性地检查 Prometheus 的数据目录。一旦发现一个新的、完整的 TSDB 块(由 `meta.json` 文件的存在来标识),它就会将其上传到对象存储。为防止重复上传,上传成功后,它会在该块目录内创建一个 `thanos.shipper.json` 的标记文件。
以下是 Sidecar 上传逻辑的简化伪代码,展示了其核心工作流:
// Shipper's main loop (simplified)
func (s *Shipper) run() {
for range time.Tick(s.checkInterval) {
// 1. Find local Prometheus TSDB blocks
dirs, err := s.fs.ReadDir(s.dataDir)
if err != nil {
level.Error(s.logger).Log("msg", "failed to read data dir", "err", err)
continue
}
for _, dir := range dirs {
// 2. Check if block is complete and not already uploaded
meta, err := readMetaFile(filepath.Join(s.dataDir, dir.Name()))
if err != nil {
// Not a valid block, skip
continue
}
if s.isUploaded(dir.Name()) {
// Already has thanos.shipper.json, skip
continue
}
// 3. Upload the block to object storage
ctx, cancel := context.WithTimeout(s.ctx, 5*time.Minute)
err = s.bucket.Upload(ctx, filepath.Join(s.dataDir, dir.Name()), dir.Name())
cancel()
if err != nil {
level.Error(s.logger).Log("msg", "block upload failed", "block", dir.Name(), "err", err)
continue
}
// 4. Write marker file to prevent re-uploading
err = s.writeMarkerFile(dir.Name())
// ... handle error ...
}
}
}
除了上传,Sidecar 的 gRPC 服务直接查询 Prometheus 内存中的数据和本地磁盘上最近的块,这保证了 Querier 能够查询到秒级延迟的最新数据。
Querier: 全局查询与副本去重
去重是 Thanos 的一大亮点。它依赖于 Prometheus 配置中的 `external_labels`。在高可用部署中,你需要为每个 Prometheus 实例配置一组唯一的标签,其中一个专门用于标识副本。
例如,在 `prometheus-0` 的配置中:
global:
scrape_interval: 15s
external_labels:
cluster: 'k8s-prod-us-east-1'
replica: '0'
在 `prometheus-1` 的配置中:
global:
scrape_interval: 15s
external_labels:
cluster: 'k8s-prod-us-east-1'
replica: '1'
当 Querier 收到查询时,它会从多个源(例如两个 Sidecar)获取到标签完全相同、仅 `replica` 标签不同的时间序列。在启动 Querier 时,通过 `–query.replica-label=replica` 参数告知它哪个标签是副本标识。Querier 在合并结果时,对于每个时间戳,会选择 `replica` 标签值(经过哈希计算)最高的那个点作为最终结果,从而丢弃其他副本的数据。这个过程对用户完全透明。
Compactor: 降采样与生命周期管理
Compactor 是降低长期存储成本和提升查询性能的关键。它通过配置参数来定义降采样的策略。例如,保留原始数据 30 天,保留 5 分钟降采样数据 90 天,保留 1 小时降采样数据 1 年。
Compactor 的配置示例:
# Command-line arguments for thanos compact
--data-dir=/var/thanos/compact
--objstore.config-file=/etc/thanos/bucket.yml
--wait
# Retention and Downsampling
--retention.resolution-raw=30d
--retention.resolution-5m=90d
--retention.resolution-1h=365d
# Ensure deletion of old blocks
--delete-delay=24h
一个极客坑点: Compactor 的工作会消耗大量 CPU 和网络 I/O,并且它对对象存储的 API 调用(LIST, GET, DELETE)非常频繁。务必将其部署在能够访问对象存储内网端点(VPC Endpoint)的机器上,以降低公网流量费用和延迟。同时,要密切监控其 API 调用次数,避免超出云服务商的免费额度而产生意想不到的成本。
性能优化与高可用设计
构建生产级的 Thanos 系统,需要深入考虑其性能和可用性。
- 查询性能优化:
- Store Gateway 索引缓存: Store Gateway 启动时会扫描对象存储,并将所有数据块的元数据和索引(Index Header)缓存在本地磁盘和内存中。确保为其分配足够的内存和高速的本地磁盘(SSD),可以显著加速查询的初始阶段(series lookup)。
– Querier 结果缓存: Thanos Querier 支持多种缓存后端(如 Memcached, Redis),可以缓存查询结果。对于重复性高、计算量大的 Dashboard 查询,效果非常显著。但这是一种典型的空间换时间,需要权衡缓存成本与延迟收益。
- 并发与超时控制: Querier 对下游(Sidecars/Stores)的查询有精细的并发和超时控制。合理配置 `–store.limits.requests` 和 `–query.timeout` 等参数,可以防止慢查询拖垮整个系统。
- 无状态组件(Querier, Store Gateway, Ruler): 这些组件是无状态的,高可用非常简单。只需运行多个副本,并通过一个 L4 负载均衡器(如 Nginx TCP Proxy, Kubernetes Service)将流量分发给它们。
- Sidecar: Sidecar 的可用性与其绑定的 Prometheus 实例强相关。Prometheus 本身的高可用(例如在 Kubernetes 中使用 StatefulSet)是保证 Sidecar 可用性的前提。
- Compactor: Compactor 通常是单例。虽然其短暂宕机不影响数据写入和查询,但长时间停机会导致对象存储中的数据块碎片化。可以通过 Kubernetes 的 leader election 机制或者简单的 active-passive 模式实现高可用,但对于大多数场景,保证其能快速恢复即可。
架构演进与落地路径
直接部署一套完整的 Thanos 架构可能过于复杂。推荐采用分阶段、渐进式的演进路径。
第一阶段:解决单集群长期存储问题
- 对现有的 Prometheus 实例旁挂部署 Thanos Sidecar。
- 配置一个对象存储桶(Bucket)。
- 部署单个 Thanos Querier, 单个 Store Gateway, 和单个 Compactor。
- 将 Grafana 的数据源从 Prometheus 切换到 Thanos Querier。
目标: 在不改变现有监控体验的前提下,获得“无限”的历史数据存储能力。此时,团队可以开始体验查询一年前数据的能力。
第二阶段:实现单集群高可用与数据去重
- 在同一个集群内部署第二套 Prometheus + Sidecar 实例,抓取完全相同的目标。
- 为这两套 Prometheus 配置不同的 `replica` 外部标签。
- 在 Thanos Querier 的启动参数中,增加 `–query.replica-label=replica` 来开启去重。
目标: 实现监控数据采集层的高可用。即使一个 Prometheus 实例宕机,监控和查询依然可用,且 Dashboard 上的图表不会因为数据重复而失真。
第三阶段:构建多集群/多地域的全局视图
- 在其他 Kubernetes 集群或数据中心重复第一、二阶段的部署,但让所有 Sidecar 都指向同一个对象存储桶。
- 中心化的 Querier 通过服务发现(如 Kubernetes Endpoints, Consul, 或静态配置)来发现所有地域的 Sidecar 和 Store Gateway。
- 为不同集群的 Prometheus 配置不同的 `cluster` 外部标签,以便在查询时进行筛选和聚合。
目标: 实现真正的“Single Pane of Glass”,在一个 Grafana 界面中即可查询和关联公司所有业务线的监控指标,为 SRE 和平台工程团队提供强大的全局洞察力。
第四阶段:落地全局告警与成本优化
- 部署 Thanos Ruler,将原先分散在各个 Prometheus 实例上的告警规则(特别是业务级、SLA 相关的告警)集中到 Ruler 中进行统一管理和评估。
- 精细化调整 Compactor 的降采样和保留策略,根据业务对数据精度的不同要求,平衡查询性能和存储成本。
目标: 建立起成熟的、可扩展的、成本可控的全球一体化可观测性平台,支撑未来业务的持续发展。
延伸阅读与相关资源
-
想系统性规划股票、期货、外汇或数字币等多资产的交易系统建设,可以参考我们的
交易系统整体解决方案。 -
如果你正在评估撮合引擎、风控系统、清结算、账户体系等模块的落地方式,可以浏览
产品与服务
中关于交易系统搭建与定制开发的介绍。 -
需要针对现有架构做评估、重构或从零规划,可以通过
联系我们
和架构顾问沟通细节,获取定制化的技术方案建议。