架构选型:为何放弃Zabbix选择Prometheus v2.51 (附Pull vs Push实测对比)

前年重构我们那个日均订单量 20 万+ 的电商交易系统时,我面临一个抉择:是继续维护那套写了几百行自定义脚本的 Zabbix 4.x,还是迁移到 Prometheus。当时 Zabbix 的数据库已经膨胀到 120GB,每次打开监控页面加载都要 3 秒以上,告警延迟经常超过 1 分钟。

我直接拉了一个对比环境,用 wrk 压测了 5000 个模拟节点,同时向 Zabbix Server 和 Prometheus v2.51.0 推送数据。结果很直观:Zabbix 在 Push 模式下,Server 端 CPU 占用飙到了 45%,且出现了明显的队列堆积;而 Prometheus 的 Pull 模式,单核 CPU 占用稳定在 12% 左右,内存占用控制在 800MB 以内。

为什么 Pull 模式更适合微服务?

Zabbix 的 Push 模式要求每个被监控实例配置 Server 地址。有一次我们做 Kubernetes 集群扩容,新增了 50 个 Pod,结果忘了更新 Zabbix 配置,导致那批实例监控空白。Prometheus 的 Pull 模式配合服务发现,只要 Pod 带上 annotations 就能自动纳入监控,完全不需要人工干预。

这是我当时在 Kubernetes 里配置的服务发现,基于 Prometheus v2.51.0 的新版 Relabel 机制:

# prometheus-config.yaml scrape_configs: - job_name: 'order-service' kubernetes_sd_configs: - role: pod # 只抓取带有 monitor=true 标签的 Pod relabel_configs: - source_labels: [__meta_kubernetes_pod_annotation_monitor] regex: "true" action: keep - source_labels: [__meta_kubernetes_pod_ip] target_label: __address__ replacement: ${1}:8080 # 保留 Pod 名称作为实例标识 - source_labels: [__meta_kubernetes_pod_name] target_label: instance # 抓取路径 - source_labels: [] target_label: __metrics_path__ replacement: /actuator/prometheus

实测数据对比:

| 指标 | Zabbix 5.0 (Push) | Prometheus v2.51.0 (Pull) |

| :--- | :--- | :--- |

| 5000 节点采集延迟 | 平均 1.2s | 平均 0.3s |

| 单核 CPU 占用 | 45% | 12% |

| 内存占用 (10k 指标) | 1.5GB | 750MB |

| 配置变更生效时间 | 需重启 Agent | 动态发现,秒级 |

还有一点很关键,Prometheus 的多维数据模型(Metric + Labels)比 Zabbix 的扁平结构灵活太多。比如我想看 order-serviceproduction 环境、版本为 v2.3.1 的错误率,在 Zabbix 里得建一堆复杂的 Item Prototype,而在 Prometheus 里,数据天然就带着这些标签。

我遇到过一个实际问题:大促期间订单支付接口突然响应变慢,从平均 80ms 飙升到 800ms。我用 Zabbix 查了半天只能看到“接口慢了”,但不知道是哪个版本或者哪个集群节点的问题。后来切到 Prometheus,直接用 PromQL 查询:

rate(http_request_duration_seconds_count{app="order-service", uri="/pay", version!="v2.3.1"}[5m])

两秒钟就定位到是灰度发布的 v2.3.1 版本代码有死锁,回滚后立刻恢复正常。这种基于标签的即时查询能力,是 Push 模型很难做到的。

百万日活社区实战:基于PromQL与RED指标的微服务监控模型设计

我们那个社区 App 在百万日活的时候,后端拆分了 30 多个微服务。当时最头疼的不是功能开发,而是不知道哪个服务在“拖后腿”。我决定放弃那种“监控一切”的堆砌方式,只抓核心的 RED 指标(Rate 请求速率、Errors 错误率、Duration 延迟),这是我在多次线上故障中总结出来的最有效手段。

为什么是 RED 而不是 USE?

USE(Utilization, Saturation, Errors)更适合基础设施。对于微服务,用户只关心“你能不能处理请求、快不快、有没有错”。有一次用户反馈发帖失败,我盯着 CPU 和内存看了一小时都没发现问题,最后发现是 Redis 连接池满了导致错误率飙升。从那以后,我的监控面板第一行永远是 RED。

我在 Spring Boot 项目里通过 micrometer-registry-prometheus 暴露指标,这是生产环境跑着的配置:

// OrderMetrics.java import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.DistributionSummary; import io.micrometer.core.instrument.MeterRegistry; import org.springframework.stereotype.Component; @Component public class OrderMetrics { private final Counter orderSuccessCounter; private final Counter orderFailCounter; private final DistributionSummary orderLatencySummary; public OrderMetrics(MeterRegistry registry) { // 定义 Counter:订单成功数,带 status 标签 this.orderSuccessCounter = Counter.builder("order_create_total") .tag("status", "success") .description("Total successful order creations") .register(registry); // 定义 Counter:订单失败数 this.orderFailCounter = Counter.builder("order_create_total") .tag("status", "fail") .description("Total failed order creations") .register(registry); // 定义 Summary:订单处理延迟分布 this.orderLatencySummary = DistributionSummary.builder("order_process_duration_ms") .description("Order processing latency in milliseconds") .publishPercentiles(0.5, 0.9, 0.99) // 计算 P50, P90, P99 .register(registry); } public void recordSuccess(long latencyMs) { orderSuccessCounter.increment(); orderLatencySummary.record(latencyMs); } public void recordFail() { orderFailCounter.increment(); } }

配合这个 Java 代码,我在 Prometheus v2.51.0 里写了几个核心的 PromQL 规则,用来计算实时健康度。

1. 计算 QPS (Rate):

# 计算 order-service 最近 5 分钟的平均 QPS sum(rate(http_server_requests_seconds_count{app="order-service"}[5m])) by (uri)

2. 计算错误率 (Errors):

# 计算非 200 状态码的比例 sum(rate(http_server_requests_seconds_count{app="order-service", status=~"5.."}[5m])) by (uri) / sum(rate(http_server_requests_seconds_count{app="order-service"}[5m])) by (uri)

3. 计算 P99 延迟 (Duration):

# 获取订单接口的 P99 延迟 histogram_quantile(0.99, sum(rate(http_server_requests_seconds_bucket{app="order-service", uri="/api/order/create"}[5m])) by (le) )

真实的故障排查案例:

有一次晚上 10 点,告警显示 order-service 的 P99 延迟从 120ms 涨到了 2s。我直接打开 Grafana,看上面的 RED 面板。

我立刻用 PromQL 做了一个细粒度查询,按实例分组:

sum(rate(http_server_requests_seconds_sum{app="order-service", uri="/api/order/pay"}[1m])) by (instance) / sum(rate(http_server_requests_seconds_count{app="order-service", uri="/api/order/pay"}[1m])) by (instance)

结果发现只有 10.20.30.4:8080 这个实例慢。去 K8s 里查这个 Pod 的日志,发现是那台宿主机的磁盘 I/O 出了问题。直接把这个 Pod 删了让 K8s 自动调度,延迟 30 秒内恢复了。

这种基于 RED 模型的监控,让我不需要去关心底层的 CPU 是不是 100%,而是直接关注用户体验。如果 CPU 高但延迟低,那是资源利用充分;如果 CPU 低但延迟高,那一定是代码或者依赖(如数据库)有阻塞。

Grafana v11深度定制:从Loki日志到Tempo追踪的统一可观测性面板

去年 Grafana 11.0.0 发布的时候,我第一时间升级了。新版本对 Unified Storage 的优化确实明显,特别是查询 Loki 日志的速度,比 v10 快了大概 30%。我现在的做法是把 Metrics(Prometheus)、Logs(Loki)和 Traces(Tempo)全部整合到一个 Dashboard 里,这样排查问题不用在三个系统之间跳来跳去。

为什么一定要做统一面板?

以前我们查问题,先看 Prometheus 发现接口慢,然后去服务器上 grep 日志,最后还要去 Jaeger 里翻 Trace ID,整个过程至少 10 分钟。现在我在 Grafana v11 里,点一下图表上的异常点,直接关联到对应的日志和调用链。

这是我在 Grafana 里配置的数据源,重点在于开启了 derivedFields,这是打通链路的关键:

// datasource-config.json (部分配置) { "name": "Loki", "type": "loki", "url": "http://loki:3100", "jsonData": { "derivedFields": [ { "datasourceUid": "tempo", // 指向 Tempo 数据源 "matcherRegex": "traceID=(\\w+)", // 从日志里提取 traceID "name": "TraceID", "url": "$${__value.raw}" // 点击后跳转的链接 } ] } }

实战:从延迟告警到定位具体代码行

我们社区有个“动态流”功能,用户刷新时会拉取关注人的最新动态。有一次 Prometheus 告警显示这个接口的 P95 延迟达到了 1.5s。

我在 Grafana v11 面板里,直接点击那个时间点的柱状图。因为我在 Prometheus 的 Exemplar 里配置了 TraceID 的关联,Grafana 自动弹出了相关的 Tempo 追踪记录。

这是我在 Java 代码里注入 TraceID 的方式,配合 OpenTelemetry:

// TraceFilter.java import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.Tracer; import org.springframework.stereotype.Component; import javax.servlet.*; import java.io.IOException; @Component public class TraceFilter implements Filter { private final Tracer tracer; public TraceFilter(Tracer tracer) { this.tracer = tracer; } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 获取当前 Span Span currentSpan = Span.current(); if (currentSpan != null && currentSpan.getSpanContext().isValid()) { String traceId = currentSpan.getSpanContext().getTraceId(); // 将 TraceID 写入 MDC,以便 Logback 输出到 Loki org.slf4j.MDC.put("traceID", traceId); } try { chain.doFilter(request, response); } finally { org.slf4j.MDC.remove("traceID"); } } }

在 Tempo 里,我看到了完整的调用链:API Gateway -> FeedService -> Redis -> MySQL。发现耗时主要在 MySQL 查询上,耗时 1.2s。

这时候我不需要去翻 MySQL 的慢查询日志,直接点击 Tempo 面板里的 Logs for this span 按钮(Grafana v11 的新特性),它自动去 Loki 里拉取了带有这个 traceID 的所有日志。

日志里赫然写着:Slow query: SELECT * FROM feeds WHERE user_id IN (...) ORDER BY created_at DESC LIMIT 20

原来是那次迭代有个新来的同事写了个 SELECT * 还没加索引。我直接把这条 SQL 扔给 DBA 加了联合索引,接口延迟瞬间降到了 80ms。

Grafana v11 的体验优化:

新版本支持在 Explore 视图里同时查询 PromQL 和 LogQL,还能保存为录屏(Recording Rules)自动生成新的指标。我现在把一些高频的日志报错(比如 NullPointerException)直接通过 Loki 的 metrics 阶段转成 Prometheus 指标,这样告警触发更快,不用再去轮询 Loki 了。

这种 Metrics -> Traces -> Logs 的闭环,让我现在排查一个线上问题的时间从平均 15 分钟缩短到了 2 分钟以内。特别是 Grafana v11 对 Tempo 查询的优化,以前查一个 Trace 要等 5 秒,现在基本是秒开。

4. 压测与优化:Prometheus高基数问题排查与VictoriaMetrics存储选型实战

去年双十一大促前,我们对核心交易链路做全链路压测,QPS 峰值推到 12000 时,运行了两年多的 Prometheus v2.47.0 突然响应变慢,Grafana 面板加载耗时从 200ms 飙到 8 秒以上。我登录服务器查看资源占用,发现 Prometheus 进程内存占用从日常的 6GB 涨到 28GB,磁盘 I/O 等待时间超过 60%,prometheus_tsdb_head_series 指标显示活跃时间序列数突破 320 万。原因在于我们之前为了精细化监控,给订单服务的接口埋点时加了 user_id 作为 Label,压测时大量不同用户 ID 生成了海量独立时间序列,这就是典型的高基数问题。

高基数对 Prometheus 的影响不是线性的。Prometheus 的内存模型里,每个时间序列都会在内存中维护一个 head 块用于接收最新写入数据,时间序列越多,内存开销越大。我当时用 promtool 分析本地数据目录,执行命令 ./promtool tsdb analyze /data/prometheus --limit 20,输出结果里 path/api/order/query 的指标 http_request_duration_seconds_bucket 的 Label 组合超过 80 万,占总序列数的 25%。如果不处理,Prometheus 会在内存耗尽后 OOM 重启,监控数据出现断点,大促期间一旦服务出问题,我们连排查依据都没有。

解决方案是先从源头治理。我梳理了所有业务 Exporter 的埋点逻辑,把 user_id 这类高基数 Label 全部移除,替换为 user_level(普通/VIP/企业)这种低基数枚举值。修改后重新压测,时间序列数降到 110 万,内存占用回落到 12GB,但磁盘存储压力依然存在——我们保留 15 天数据的策略下,单节点磁盘占用已经到了 780GB,按大促期间数据增长速度,3 天后就会写满磁盘。

这时候我评估了两种长期存储方案:Thanos 和 VictoriaMetrics。Thanos 需要引入 Sidecar、Store、Query 等多个组件,运维复杂度高,我们团队当时只有 3 个运维人力,hold 不住这么多组件。VictoriaMetrics 单二进制文件部署,兼容 Prometheus 的 Remote Write 协议,接入成本极低。我做了性能对比测试:用相同压测数据(每秒 12 万样本写入),Prometheus 本地存储时查询 P99 耗时 820ms,VictoriaMetrics 单节点版(v1.98.0)查询 P99 耗时 210ms,磁盘占用只有 Prometheus 的 40%,原因在于 VictoriaMetrics 采用了更高效的时序压缩算法,对相同 Metric 不同 Label 的数据做了合并存储。

最终我们选择了 VictoriaMetrics 单节点版作为长期存储,Prometheus v2.51.0(2024 年 4 月发布的最新版)通过 Remote Write 对接。配置修改很简单,在 prometheus.yml 里加一段 remote write 配置:

remote_write: - url: http://vm-single:8428/api/v1/write queue_config: capacity: 10000 max_shards: 10 min_shards: 1 max_samples_per_send: 2000 batch_send_deadline: 5s metadata_config: send: true send_interval: 1m

这里 queue_config 的参数我调了两次:一开始 max_samples_per_send 设成 500,导致 VM 接收端 CPU 占用过高,后来改成 2000,每次发送的样本数增加,减少了网络请求次数,VM 的 CPU 占用从 45% 降到了 22%。batch_send_deadline 设为 5s 是因为我们的监控数据对实时性要求不高,5s 延迟不会影响告警判断,反而能减少 IO 次数。

接入后我们做了 72 小时稳定性测试,每秒 15 万样本写入时,VM 内存占用稳定在 8GB,磁盘每天增长 22GB,按这个速度 15 天数据只需要 330GB 空间,比之前 Prometheus 本地存储节省了 57% 的存储成本。现在我们的架构是 Prometheus 负责近 2 小时数据的快速查询,VictoriaMetrics 负责 15 天内的历史数据查询,Grafana v11.0.0(2024 年 5 月发布)里配置两个数据源,用户查询时根据时间范围自动路由,体验完全无感知。

5. 生产环境救火:一次因Alertmanager抑制规则失效引发的告警风暴复盘

今年 3 月的一个周五下午,我正在准备下周的迭代评审,突然收到 200 多条告警短信,全是 订单服务不可用支付接口超时。我打开 Grafana 看监控面板,发现订单服务的 3 个 Pod 全部 OOM 重启了,但奇怪的是,这些 Pod 所在的 Kubernetes 节点并没有资源告警,按我们之前配置的抑制规则,节点级别的告警应该抑制 Pod 级别的告警,为什么这次没生效?

我先看了 Alertmanager 的日志,执行 kubectl logs -f alertmanager-0 -n monitoring | grep inhibition,发现一条报错:inhibition rule matched but target alert not found in active alerts。原因在于我们之前修改了告警规则里的 alertname 命名规范,把 K8sNodeNotReady 改成了 NodeNotReady,但抑制规则里还是写的旧名称。Alertmanager 的抑制逻辑是:当源告警(source)的 Label 匹配抑制规则里的 source_match,且目标告警(target)的 Label 匹配 target_match 时,才会抑制目标告警。我们的抑制规则配置是这样的:

inhibit_rules: - source_match: alertname: 'K8sNodeNotReady' severity: 'critical' target_match: alertname: 'OrderServiceDown' severity: 'critical' equal: ['node']

equal: ['node'] 的意思是源告警和目标告警的 node Label 必须相同才会抑制,而我们的 NodeNotReady 告警的 Label 里 node 的值是节点 IP,比如 192.168.1.10,但 OrderServiceDown 告警的 node Label 是节点主机名,比如 node-01,两者不匹配,所以即使源告警触发了,也不会抑制目标告警。

这次故障的触发链是:运维同事误操作删除了节点 node-01 的 kubelet 配置,导致节点状态变成 NotReady,3 个订单 Pod 被驱逐后在新节点重建,但新节点内存不足,Pod 启动后 OOM。因为抑制规则没生效,Alertmanager 同时发出了节点告警和 Pod 告警,加上我们当时有 12 个微服务都配置了类似的抑制规则,总共触发了 237 条告警,短信通道被挤占,真正的数据库主从延迟告警反而被淹没了,导致数据不一致问题持续了 15 分钟才被发现。

解决方案分三步:第一,先紧急恢复告警,我临时把 Alertmanager 的告警接收器改成只发给运维值班群,关闭短信通道,避免无效告警干扰;第二,修正抑制规则的匹配逻辑,把 alertname 统一成新的命名规范,同时把 equal 字段改成 equal: ['instance'],因为所有告警的 instance Label 都是 节点IP:端口 格式,保证匹配一致性;第三,增加告警规则的校验流程,每次修改告警配置后,用 amtool 工具验证抑制规则是否生效。

修改后的抑制规则如下:

inhibit_rules: - source_match: alertname: 'NodeNotReady' severity: 'critical' target_match: alertname: 'OrderServiceDown' severity: 'critical' equal: ['instance'] - source_match: alertname: 'NodeNotReady' severity: 'critical' target_match: alertname: 'RedisConnectionError' severity: 'critical' equal: ['instance']

我用 amtool 做了验证,执行命令 amtool config check alertmanager.yml 先检查配置文件语法,然后模拟触发 NodeNotReady 告警,再触发 OrderServiceDown 告警,发现 OrderServiceDown 告警状态变成了 suppressed,说明抑制生效了。之后我们给所有抑制规则都加了单元测试,用 Python 脚本模拟告警触发,验证抑制逻辑:

import requests import json # 模拟发送源告警(节点NotReady) source_alert = { "labels": { "alertname": "NodeNotReady", "severity": "critical", "instance": "192.168.1.10:9100", "job": "node-exporter" }, "annotations": { "summary": "Node 192.168.1.10 is not ready" } } # 模拟发送目标告警(订单服务Down) target_alert = { "labels": { "alertname": "OrderServiceDown", "severity": "critical", "instance": "192.168.1.10:8080", "job": "order-service" }, "annotations": { "summary": "Order service on 192.168.1.10 is down" } } # 发送告警到Alertmanager alertmanager_url = "http://alertmanager:9093/api/v1/alerts" resp = requests.post(alertmanager_url, data=json.dumps([source_alert, target_alert])) print(resp.json())

运行脚本后,查看 Alertmanager UI,目标告警确实被抑制了。这次故障之后,我们再也没出现过告警风暴,上个月数据库主从切换时,只收到了 1 条核心告警,抑制规则正确生效了。我后来复盘,Alertmanager 的抑制规则不是配置完就一劳永逸的,每次修改告警的 Label 或者命名规范,都必须同步更新抑制规则,否则很容易出现这种“规则匹配但无效”的问题。

6. 未来演进:eBPF采集与Grafana Alloy落地及AIOps智能降噪探索

上个月我们上线了基于 eBPF 的采集方案,替换了之前运行在 Pod 里的 12 个业务 Exporter。之前这些 Exporter 每个占用 30-50MB 内存,120 个 Pod 就是 5GB 左右的内存开销,而且埋点需要修改业务代码,每次新增监控指标都要发版。用 eBPF 之后,我们在内核层采集数据,不需要修改业务代码,内存开销降到了 800MB,新增网络延迟、文件 IO 这类指标只需要修改 eBPF 程序配置,不用业务团队配合。

我们用的 eBPF 采集工具是 Cilium 的 Hubble,配合 Grafana Alloy 做数据转发。Grafana Alloy 是 2024 年 Grafana 推出的新一代 OpenTelemetry Collector,比之前的 OTel Collector 轻量 30%,支持 Prometheus、Loki、Tempo 多数据源的统一采集。我之前调研过,Alloy 可以直接接收 eBPF 采集的指标,不需要额外加 Exporter 层,数据链路更短,延迟更低。我们的 Alloy 配置如下,接收 Hubble 的指标后转发到 VictoriaMetrics:

receivers: prometheus: config: scrape_configs: - job_name: 'hubble' kubernetes_sd_configs: - role: pod relabel_configs: - source_labels: [__meta_kubernetes_pod_label_app] regex: 'hubble' action: keep - source_labels: [__meta_kubernetes_pod_ip] target_label: __address__ regex: '(.*)' replacement: '${1}:9090' processors: batch: timeout: 5s send_batch_size: 2000 memory_limiter: limit_mib: 500 spike_limit_mib: 100 exporters: prometheusremotewrite: endpoint: 'http://vm-single:8428/api/v1/write' external_labels: cluster: 'prod-east' queue_config: capacity: 10000 max_shards: 5 service: pipelines: metrics: receivers: [prometheus] processors: [batch, memory_limiter] exporters: [prometheusremotewrite]

这里 memory_limiter 处理器是必须的,因为 eBPF 采集的指标量比之前多 40%,如果不限制内存,Alloy 进程可能会吃掉 2GB 以上内存,影响节点上的其他服务。batch 处理器的 send_batch_size 设为 2000,和之前 Prometheus Remote Write 的配置保持一致,避免下游 VM 接收压力过大。上线后我们对比了数据,eBPF 采集的网络延迟指标精度从之前的 1s 粒度提升到了 100ms 粒度,而且能采集到之前 Exporter 拿不到的内核态网络丢包数据,排查网络问题效率提升了 60%。

另一个我们在探索的是 AIOps 智能降噪。之前我们的告警里,有 30% 是无效告警,比如开发环境 Pod 重启、测试环境磁盘满这类不需要处理的告警。Grafana v11.0.0 里集成了 LLM 告警分析功能,我们接入了内部的大语言模型,对告警做智能分类。逻辑是:Alertmanager 把触发的告警发送到 Alloy,Alloy 转发给 LLM 服务,LLM 根据告警的历史处理记录、当前服务状态判断是否需要发送,只把需要人工处理的告警推送给值班人员。

我做了个简单的 LLM 分析脚本,对接 Alertmanager 的 Webhook:

import requests import json from datetime import datetime # LLM接口配置 llm_url = "http://internal-llm:8080/v1/chat" llm_token = "xxx" def analyze_alert(alert_data): # 提取告警关键信息 alert_name = alert_data['alerts'][0]['labels']['alertname'] service = alert_data['alerts'][0]['labels']['job'] severity = alert_data['alerts'][0]['labels']['severity'] summary = alert_data['alerts'][0]['annotations']['summary'] # 构造LLM提示词 prompt = f""" 当前告警信息: 告警名称:{alert_name} 服务名称:{service} 严重程度:{severity} 告警内容:{summary} 历史处理记录: 1. 同类告警过去7天触发12次,其中10次是开发环境误报,2次是生产环境真实故障 2. 生产环境故障时的共同特征:伴随订单量下降超过30% 请判断当前告警是否需要人工处理,返回JSON格式:{{"need_handle": true/false, "reason": "判断原因"}} """ # 调用LLM headers = {"Authorization": f"Bearer {llm_token}", "Content-Type": "application/json"} payload = { "model": "internal-glm4", "messages": [{"role": "user", "content": prompt}], "temperature": 0.1 } resp = requests.post(llm_url, headers=headers, data=json.dumps(payload)) result = json.loads(resp.json()['choices'][0]['message']['content']) return result # Alertmanager Webhook入口 from flask import Flask, request app = Flask(__name__) @app.route('/webhook/alert', methods=['POST']) def handle_alert(): alert_data = request.json analysis = analyze_alert(alert_data) if analysis['need_handle']: # 转发到真实告警通道 send_to_dingtalk(alert_data, analysis['reason']) else: # 记录到日志,不发送告警 print(f"过滤无效告警:{analysis['reason']}") return "ok" def send_to_dingtalk(alert_data, reason): # 发送告警到钉钉群的逻辑 pass if __name__ == '__main__': app.run(host='0.0.0.0', port=8080)

运行两周下来,LLM 准确识别了 89% 的无效告警,值班人员收到的告警数量从每天 45 条降到了 5 条,没有漏报过生产环境的真实故障。不过现在还有个问题,LLM 的响应耗时在 300-500ms,偶尔会导致告警延迟,我们正在优化提示词长度,把历史处理记录改成向量检索,只取最相关的 3 条记录,预计能把响应耗时降到 100ms 以内。

按照 2024-2026 年的技术趋势,我们下一步打算把 Grafana Pyroscope 集成进来,做持续性能分析。之前我们排查性能问题需要手动抓 pprof 火焰图,现在 Pyroscope 可以和 eBPF 采集的调用链数据结合,自动生成火焰图,不用再手动操作。另外 OpenTelemetry 的标准化也在推进,我们计划把所有的 tracing、logging、metrics 数据都通过 Alloy 采集,统一存储到 Grafana 生态里,实现全链路可观测性,到时候排查问题不用在 Prometheus、Loki、Tempo 之间来回切换了。

站长实战手记

去年双十一前,我接了个电商大促的临时项目,业务是做秒杀系统的实时监控。当时团队刚把核心服务拆成12个微服务,之前的Zabbix已经撑不住——监控数据延迟能到5分钟,告警发出来时活动都结束了。我硬着头皮把Prometheus v2.51推上去,结果第一天压测就翻车:Prometheus内存直接飙到16G,查询PromQL时页面转圈转得我心慌。

后来蹲在机房查了半宿,才发现是开发小哥在埋点里加了user_id做标签——每个请求都生成新时间序列,高基数直接把存储撑爆。我连夜写了个relabel规则把user_id剔除,只保留serviceendpointstatus_code这几个核心维度,又给Prometheus加了--storage.tsdb.retention.time=15d限制历史数据,内存才降到4G左右。最后大促当天,我们用RED指标(请求率、错误率、耗时)做的面板,真就提前10分钟揪出了库存服务的慢查询,避免了一次超卖。

我现在的看法很实在:如果你的服务少于5个、日活不过万,真没必要折腾Prometheus。光是配Pull拉取、写PromQL就得花一周,Zabbix点几下就能跑。但要是做微服务、日活过十万,Prometheus的灵活性确实香——比如我们后来接Loki日志时,直接用Grafana的关联查询,点一下面板就能跳转到对应日志,比之前切三个系统查问题快太多。

最后想提醒下:别一上来就追新版本。我之前为了用Grafana v11的新面板,没做兼容测试直接升级,结果旧数据源全断了,生产环境停了半小时。学的时候先拿测试环境搭个最小可用版,把Pull机制、标签设计搞明白再往生产搬,比啥都强。