基于 Thanos 的 Prometheus 高可用与无限存储架构深度剖析

本文专为面临 Prometheus 存储瓶颈、高可用挑战及多集群监控难题的中高级工程师和架构师设计。我们将深入探讨 Prometheus 本身的存储局限性,并系统性地剖析 Thanos 如何通过将对象存储、Gossip 协议和分层查询机制相结合,构建一个理论上无限扩展、高可用的全局监控体系。本文将从 TSDB 的底层原理出发,穿透 Thanos 的各个核心组件,分析其在真实生产环境中的部署策略、性能权衡与架构演进路径,旨在提供一份可直接指导实践的深度技术指南。

现象与问题背景

Prometheus 已成为云原生时代监控领域的デファクトスタンダード(事实标准)。其强大的 PromQL、高效的 Pull 模型以及与服务发现的无缝集成,使其备受青睐。然而,当企业规模扩大,系统复杂度提升后,原生的 Prometheus 架构会暴露出三个核心痛点:

  • 有限的存储周期与单点瓶颈: Prometheus 的本地 TSDB (Time-Series Database) 被设计为一种高性能但有状态的存储。它直接与本地磁盘绑定,这意味着存储容量和保留周期受限于单机物理资源。在 Kubernetes 等动态环境中,高基数(High Cardinality)的指标会让磁盘空间迅速膨胀。多数团队被迫将数据保留周期设置为 15-30 天,这对于需要进行年度容量规划、季度趋势分析或对低频故障进行溯源的场景是远远不够的。同时,这个单体实例也构成了系统的单点故障(SPOF),一旦磁盘损坏或节点宕机,监控数据将面临丢失风险。
  • 全局视图的缺失: 在微服务架构或多数据中心部署中,通常会为每个集群或业务单元部署独立的 Prometheus 实例。这种架构隔离性好,但导致了监控数据的“孤岛化”。当需要进行全局性的业务指标聚合(例如,查询全球所有站点的 HTTP 请求总量)或进行跨集群的故障排查时,工程师不得不在多个 Prometheus UI 之间手动切换,效率低下且容易出错。Prometheus 自带的 Federation 机制虽能部分解决问题,但它通过“上级” Prometheus 从“下级”拉取数据,不仅配置复杂,还会在 Federation 层形成新的数据聚合瓶颈和单点。
  • 高可用方案的妥协: 社区标准的 Prometheus 高可用方案是运行两套完全相同的 Prometheus 实例,抓取同样的目标。这种“热备”模式虽然能在一个实例宕机时提供冗余,但成本高昂(双倍的计算、内存、磁盘资源),且并未解决长期存储问题。更重要的是,在查询时,用户需要自行处理来自两个数据源的重复数据,这给 Grafana 等可视化工具的配置带来了额外的复杂性。

Thanos 的出现,正是为了系统性地解决上述所有问题。它并非要替代 Prometheus,而是作为其“外挂”的增强层,无缝地将其从一个单机监控工具,提升为一个分布式的、具备长期存储能力和全局视图的监控平台。

关键原理拆解

要理解 Thanos 的精妙之处,我们必须回归到计算机科学的一些基础原理,看看它是如何巧妙地利用这些原理来构建其架构的。

(教授视角)从 LSM-Tree 到分层存储:

Prometheus 的 TSDB 在设计上深受 Log-Structured Merge-Tree (LSM-Tree) 的影响。数据首先被写入内存中的 Head Block,这是一个高度优化的读写区域。当数据达到一定时间窗口(默认为 2 小时)后,内存中的数据会被刷写到磁盘,形成一个不可变的块(Block)。后台的 Compaction 进程会不断地将小块合并成大块,以优化查询性能和空间利用率。这种设计的优势在于写入路径极快,因为它主要是顺序写和内存操作。但其固有的“本地性”也正是其扩展性的天花板。

Thanos 在此基础上引入了数据分层存储(Tiered Storage)思想。它将数据分为两层:

  • 实时层(Real-time Tier): 最近的、最热的数据(通常是过去几个小时)依然由各个 Prometheus 实例的本地 TSDB 负责。这部分数据需要极低的查询延迟,本地 SSD 是最佳选择。
  • 历史层(Historical Tier): 一旦 Prometheus 的一个数据块变得不可变(immutable),Thanos Sidecar 就会将其上传到一个廉价、高耐久、高吞吐的分布式对象存储(如 AWS S3, Google GCS, MinIO)中。

这种架构将“计算”(指标抓取、规则评估)与“存储”进行了解耦。Prometheus 实例回归其核心职责——数据采集与实时告警,而存储的持久性和扩展性则交给了专业的对象存储服务。这是一个典型的分布式系统设计模式,与 Snowflake、BigQuery 等现代数据仓库的存算分离架构异曲同工。

(教授视角)Gossip 协议与最终一致的服务发现:

在一个分布式的 Thanos 系统中,一个核心问题是:查询节点(Querier)如何知道有哪些数据源(Sidecars, Store Gateways)可以查询?传统的做法是使用一个中心化的服务注册中心(如 ZooKeeper, etcd)。但 Thanos 为了避免引入新的中心化依赖,采用了基于 Gossip 协议的去中心化服务发现机制。

Gossip 协议,又称“流行病协议”,模拟信息在社交网络中的传播方式。每个节点会周期性地、随机地与其他节点交换自己所知的全部节点信息。经过数轮交换,整个集群的状态信息(谁在线,谁提供了什么数据)就会收敛到一种最终一致的状态。这种方式容错性极强,任何节点的加入或离开都只会在短时间内造成视图不一致,系统会自动修复。它牺牲了强一致性,换来了极高的可用性和去中心化的简洁性,非常适合监控系统这种对秒级延迟不敏感的元数据同步场景。

系统架构总览

一个完整的 Thanos 部署由多个可独立部署、水平扩展的组件构成,它们协同工作,形成一个有机的整体。我们可以将这些组件按数据流向分为几层:

  • 数据采集与上传层 (Sidecar):

    Thanos Sidecar 是整个体系的起点。它作为一个独立的进程,与每一个 Prometheus Server 实例“背靠背”部署在同一个 Pod 或主机上。它的核心职责有两个:

    1. 数据上传 (Shipper): 定期检查 Prometheus 的数据目录,一旦发现新的、不可变的 TSDB 块文件,就将其上传到预先配置好的对象存储桶中。

    2. 实时查询代理 (Store API): 暴露一个 gRPC 接口,允许上游的查询器(Querier)直接查询这个 Prometheus 实例内存中尚未上传的实时数据(通常是最近 2-3 小时)。

  • 全局查询与聚合层 (Querier):

    Thanos Querier 是用户查询的统一入口,它本身是无状态的,可以水平扩展。当收到一个 PromQL 查询后,它会:

    1. 服务发现: 通过 Gossip 协议或静态配置,发现所有可用的数据源,包括集群中所有的 Sidecar(提供实时数据)和 Store Gateway(提供历史数据)。

    2. 查询扇出 (Fan-out): 根据查询的时间范围,智能地将查询请求分发给对应的数据源。例如,一个查询最近 1 小时的请求可能只会发给 Sidecar,而一个查询过去 30 天的请求会发给 Store Gateway。

    3. 结果合并与去重 (Merge & Deduplication): 从多个数据源收集查询结果,进行合并,并根据 Prometheus 实例上配置的 `external_labels`(如 `cluster` 和 `replica`)对来自 HA 对的数据进行去重,最终向用户呈现一个无缝的、全局统一的数据视图。

  • 历史数据服务层 (Store Gateway):

    Thanos Store Gateway 是对象存储的“代言人”。它也是无状态、可水平扩展的。它启动时会扫描对象存储桶,将所有数据块的元数据和索引(一个非常小的数据子集)加载到自己的内存中。当 Querier 请求历史数据时,Store Gateway 会利用这些索引快速定位到需要的数据块,然后从对象存储中只拉取必要的原始数据(chunks)进行计算,并将结果返回给 Querier。这种设计避免了将海量历史数据全部加载到本地磁盘,极大地降低了资源消耗。

  • 后台维护与优化层 (Compactor & Ruler):

    Thanos Compactor 是一个全局唯一的、长期运行的后台任务。它负责对对象存储中的数据进行维护,主要执行两个操作:

    1. 数据压缩 (Compaction): 将小的数据块合并成更大的块,减少块的数量,提升长期查询的效率。

    2. 数据降采样 (Downsampling): 这是性能优化的关键。Compactor 会创建数据的低分辨率版本。例如,将原始的 15 秒精度数据,聚合成 5 分钟和 1 小时精度的聚合数据。当用户进行长时间跨度的查询(如查询一年趋势)时,Querier 可以智能地使用这些降采样后的数据,将需要处理的数据量降低几个数量级,实现秒级响应。

    Thanos Ruler 则负责对全局数据进行告警规则和记录规则的评估,解决了原生 Prometheus 告警规则只能基于本地数据的局限。

核心模块设计与实现

(极客视角)Sidecar:本地数据与云端的桥梁

Sidecar 的实现并不复杂,但魔鬼在细节中。它的 `shipper` 组件会持续监视 Prometheus 的 `wal` 目录。当 Prometheus 完成一个 block 的写入并将其标记为不可变时,shipper 就会介入。


# 一个典型的 Prometheus + Thanos Sidecar 的 Kubernetes 容器配置
# prometheus-statefulset.yaml
...
containers:
- name: prometheus
  image: prom/prometheus:v2.37.0
  args:
    - "--config.file=/etc/prometheus/prometheus.yml"
    - "--storage.tsdb.path=/prometheus/"
    - "--storage.tsdb.retention.time=6h" # 本地只保留少量数据
    - "--web.enable-lifecycle" # 允许 sidecar reload prometheus
  volumeMounts:
  - name: prometheus-data
    mountPath: /prometheus/
- name: thanos-sidecar
  image: thanos/thanos:v0.28.0
  args:
    - "sidecar"
    - "--log.level=info"
    - "--tsdb.path=/prometheus/"
    - "--prometheus.url=http://localhost:9090"
    - "--objstore.config-file=/etc/thanos/object-storage.yaml"
    - "--cluster.address=0.0.0.0:10900" # Gossip 监听地址
    - "--cluster.peers=thanos-cluster.monitoring.svc.cluster.local:10900" # Gossip peers
  volumeMounts:
  - name: prometheus-data
    mountPath: /prometheus/
  - name: thanos-objstore-config
    mountPath: /etc/thanos/
...

工程坑点:

  • 数据一致性: Sidecar 通过在对象存储中写入一个 `meta.json` 文件来标记一个块的上传状态。这个过程是幂等的,即使中途失败,下次重试也能从未完成的地方继续。关键是要监控 `thanos_shipper_uploads_total{result=”error”}` 指标,及时发现上传失败。
  • 本地数据保留策略: 必须将 Prometheus 本地的 `storage.tsdb.retention.time` 调低(如 6h-2d)。否则,本地磁盘和对象存储中会存在大量重复数据,浪费空间。Sidecar 会阻止 Prometheus 删除它尚未上传的块,保证数据不丢失。

(极客视角)Querier:智能查询路由与去重

Querier 的核心是它的 Store API 客户端和去重逻辑。当你执行一个查询时,比如 `sum(rate(http_requests_total[5m]))`,它会并发地向所有已发现的 Store(Sidecars + Store Gateways)发送请求。每个 Store 返回一个时间序列集合。

去重的逻辑依赖于 `external_labels`。假设你有两个 Prometheus 实例组成 HA 对:

  • Prometheus-A 的配置: `global: { external_labels: { cluster: ‘prod-us-east-1’, replica: ‘A’ } }`
  • Prometheus-B 的配置: `global: { external_labels: { cluster: ‘prod-us-east-1’, replica: ‘B’ } }`

当 Querier 收到来自 A 和 B 的完全相同的时间序列(除了 `replica` 标签),它会优先选择 `replica` 标签值较小的那个(或按特定规则配置),丢弃另一个。这样就实现了无缝的故障切换和数据去重。


# Thanos Querier 的启动参数示例
args:
- "query"
- "--log.level=info"
- "--query.replica-label=replica" # 指定用于去重的标签
- "--store=dnssrv+_grpc._tcp.thanos-store-gateway.monitoring.svc.cluster.local" # 发现 Store Gateway
- "--store=dnssrv+_grpc._tcp.thanos-sidecar.monitoring.svc.cluster.local" # 发现 Sidecars

工程坑点:

  • 标签一致性: `external_labels` 的正确配置是 Thanos 全局视图和高可用的基石。任何一个 Prometheus 实例配置错误,都可能导致数据去重失败或查询结果混乱。必须通过自动化配置(如 Jsonnet/Cue)来保证其一致性。
  • 查询超时: Querier 需要协调多个下游组件。必须为查询设置合理的超时(`–query.timeout`),并监控 `thanos_query_range_duration_seconds` 等延迟指标,定位性能瓶颈是在 Sidecar、Store Gateway 还是网络。

(极客视角)Compactor:沉默的性能守护者

Compactor 是一个有状态的单例组件,它通过在对象存储中创建锁文件来保证全局只有一个实例在工作。它的降采样(Downsampling)是让长期查询变得可行的关键。

当一个查询,例如 `avg_over_time(cpu_usage[1y])` 到达 Querier 时,如果配置了降采样,Querier 会检查查询的步长(step)。如果步长很大(例如 Grafana 自动计算出步长为 1h),Querier 会自动选择查询 1 小时分辨率的聚合数据,而不是去扫描原始的 15s 数据。数据扫描量骤减,查询性能提升百倍以上。


# Thanos Compactor 启动参数示例
args:
- "compact"
- "--log.level=info"
- "--data-dir=/var/thanos/compact" # 本地临时工作目录
- "--objstore.config-file=/etc/thanos/object-storage.yaml"
- "--wait" # 启动时等待依赖
# 开启降采样,生成 5m 和 1h 两种分辨率的数据
- "--downsampling.resolutions=0s,5m,1h" 
- "--retention.resolution-raw=30d"  # 原始数据保留 30 天
- "--retention.resolution-5m=90d"   # 5 分钟数据保留 90 天
- "--retention.resolution-1h=2y"    # 1 小时数据保留 2 年

工程坑点:

  • 资源消耗: Compactor 在执行合并和降采样时,需要从对象存储下载数据块到本地临时目录,这会消耗大量的网络带宽和本地磁盘 I/O。必须为其分配足够的资源,并监控其本地磁盘使用情况。
  • 执行延迟: Compaction 和 Downsampling 不是实时的。通常会有几个小时到一天的延迟。这意味着你可能无法查询到“昨天”的降采样数据。监控 `thanos_compactor_downsample_last_successful_run_timestamp_seconds` 来了解数据处理的进度。

性能优化与高可用设计

构建一个生产级的 Thanos 系统,除了理解各组件的功能,还需要深入考虑性能和可用性。

性能优化策略

  • 查询加速:
    • 降采样 (Downsampling) 是最重要的性能优化,没有之一。对于长期查询,其效果是决定性的。
    • Store Gateway 索引缓存: Store Gateway 会将对象存储中所有块的索引缓存于内存。`–index-cache-size` 参数是关键的调优点。一个更大的缓存可以避免频繁地从对象存储重新加载索引,显著降低查询规划阶段的延迟。
    • 垂直扩展 Querier 和 Store Gateway: 这些组件的 CPU 和内存使用量与查询的并发度和复杂度直接相关。在查询负载高的场景下,需要为它们分配更多的资源。
    • 查询切分 (Query Splitting): 对于跨度极长或极其复杂的查询,Querier 可以自动将其切分为多个时间上更短的子查询,并行执行,最后合并结果。
  • 存储成本优化:
    • 合理配置保留策略 (Retention): 利用 Compactor 的多层保留策略,尽早删除不再需要的高精度原始数据,只保留降采样后的聚合数据,可以大幅降低对象存储的成本。
    • 选择合适的存储类别: 利用云厂商提供的不同存储类别,例如将数月前的冷数据自动转移到 S3 Infrequent Access 或 Glacier Deep Archive,进一步压缩成本。

高可用架构

  • 数据采集层 (Prometheus + Sidecar): 运行两套完全相同的 Prometheus 实例(抓取相同的 targets),并为它们配置不同的 `replica` 标签。这是实现实时数据高可用的基础。
  • 查询层 (Querier): Querier 是无状态的,可以直接部署多个实例,并通过 Load Balancer 对外提供服务。
  • 存储层 (Store Gateway): Store Gateway 也是无状态的,同样可以部署多个实例以实现高可用和负载均衡。
  • 维护层 (Compactor / Ruler): 这两个组件是有状态的单例。高可用通常通过 Kubernetes 的自动重启机制实现。由于它们通过锁机制来协调,即使 Pod 发生漂移,新的实例也能安全地接管工作。在极端要求下,可以考虑 Active-Passive 模式。
  • 依赖项 (对象存储): Thanos 系统的整体可用性上限取决于其底层的对象存储。选择具备跨区域复制和高 SLA 的对象存储服务(如 AWS S3 Multi-Region Access Points)是构建异地容灾监控体系的关键。

架构演进与落地路径

对于大多数团队而言,一步到位构建一个全功能的 Thanos 系统既不现实也无必要。一个务实的、分阶段的演进路径如下:

  1. 阶段一:解决长期存储问题 (Backup & Archive)

    目标: 解决 Prometheus 本地存储周期短的痛点,实现监控数据的长期归档。

    实施:

    • 为现有的核心 Prometheus 实例部署 Thanos Sidecar。
    • 配置对象存储,并将 Sidecar 指向它。
    • 部署一个 Thanos Querier 和一个 Thanos Store Gateway。

    收益: 立竿见影。数据开始被备份到对象存储,可以通过 Thanos Querier 查询超过本地保留周期的历史数据。此时,Thanos 主要扮演一个“数据保险库”的角色。

  2. 阶段二:实现全局统一视图 (Global View)

    目标: 打破多集群、多地域的监控数据孤岛。

    实施:

    • 在所有 Prometheus 实例旁都部署 Thanos Sidecar。
    • 将中心化的 Thanos Querier 配置为可以发现所有 Sidecar 实例(通过 Gossip 或静态配置列表)。

    收益: 运维和开发团队获得了一个单一入口,可以执行跨所有环境的 PromQL 查询,极大提升了故障排查和容量分析的效率。

  3. 阶段三:构建高可用监控体系 (High Availability)

    目标: 消除监控系统自身的单点故障。

    实施:

    • 对关键的 Prometheus 实例进行 HA 部署(即部署一对)。
    • 严格规范 `external_labels`,特别是 `replica` 标签。
    • 在 Thanos Querier 上开启 `replica` 标签的去重功能。
    • 将 Querier 和 Store Gateway 扩展到多个副本。

    收益: 任意单个 Prometheus 或 Thanos 组件的失效都不会影响监控服务的可用性,系统达到生产级健壮性。

  4. 阶段四:优化性能与成本 (Scale & Optimize)

    目标: 在数据量持续增长的情况下,保证查询性能并控制成本。

    实施:

    • 部署 Thanos Compactor。
    • 配置 Compaction 和 Downsampling 策略,并根据业务需求设置多级数据保留策略。
    • 引入 Thanos Ruler,将告警和记录规则从各个 Prometheus 实例中剥离,进行集中化、全局化管理。

    收益: 长期查询性能得到数量级提升,存储成本得到有效控制,告警管理更加清晰统一。至此,一个成熟、可扩展、高可用的云原生监控平台才算真正建成。

延伸阅读与相关资源

  • 想系统性规划股票、期货、外汇或数字币等多资产的交易系统建设,可以参考我们的
    交易系统整体解决方案
  • 如果你正在评估撮合引擎、风控系统、清结算、账户体系等模块的落地方式,可以浏览
    产品与服务
    中关于交易系统搭建与定制开发的介绍。
  • 需要针对现有架构做评估、重构或从零规划,可以通过
    联系我们
    和架构顾问沟通细节,获取定制化的技术方案建议。
滚动至顶部