3. 应用层指标采集:从埋点到实战

应用层指标,说白了就是你的服务在用户面前「表现如何」。HTTP 请求量、延迟、错误率,再加上你们业务自己定义的那些关键数字——这些东西不采集上来,监控就是瞎子。我做了这么多年运维,见过太多团队把精力全砸在基础设施监控上,结果用户都报怨了,自己还不知道。

这一章,咱们就聊聊怎么把这些指标实实在在地采上来。

3.1 四大核心指标:到底该看什么?

我个人习惯把应用层指标分成四类。你想想看,一个请求从进来,到处理完,再到用户感知到结果,这中间每一步都有对应的指标。

指标类别 典型指标 说明
流量 QPS、RPS、活跃连接数 系统当前承受的压力有多大
延迟 P50、P90、P99 响应时间 用户等多久才能拿到结果
错误 5xx 率、4xx 率、异常计数 请求失败的比例有多高
饱和度 队列深度、线程池使用率 系统还有多少余量

嗯,这里要注意:错误率不是越低越好。我在项目中遇到过,有个团队把错误率压到 0.01% 以下,结果发现是监控埋点漏掉了超时异常。说白了,指标要全面,更要准确。

3.2 埋点方式:代码里怎么插桩?

埋点这事,说难不难,说简单也不简单。我见过最粗暴的方式——直接在业务代码里写 print 然后去捞日志。嗯,这也能用,但到了生产环境你就知道有多痛苦了。

我个人推荐的做法是:用中间件模式统一拦截。以 Python 的 Flask 为例,一个装饰器就能搞定所有请求的指标采集。

import time
from functools import wraps
from prometheus_client import Counter, Histogram, Gauge

# 定义指标
REQUEST_COUNT = Counter(
    'http_requests_total',
    'Total HTTP requests',
    ['method', 'endpoint', 'status']
)

REQUEST_LATENCY = Histogram(
    'http_request_duration_seconds',
    'HTTP request latency in seconds',
    ['method', 'endpoint'],
    buckets=[0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]
)

IN_FLIGHT_REQUESTS = Gauge(
    'http_requests_in_flight',
    'Current number of in-flight requests',
    ['method']
)

def monitor_endpoint(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        method = request.method
        endpoint = request.path

        IN_FLIGHT_REQUESTS.labels(method=method).inc()

        start = time.time()
        try:
            response = func(*args, **kwargs)
            status = response.status_code
            return response
        except Exception as e:
            status = 500
            raise
        finally:
            duration = time.time() - start
            REQUEST_COUNT.labels(
                method=method, endpoint=endpoint, status=status
            ).inc()
            REQUEST_LATENCY.labels(
                method=method, endpoint=endpoint
            ).observe(duration)
            IN_FLIGHT_REQUESTS.labels(method=method).dec()

    return wrapper
我的经验:桶(buckets)的设置很关键。默认的桶分布往往不适合你的业务。我一般会先跑一周数据,看看 P99 大概在什么范围,再反推桶的边界。别小看这一步,桶设得不好,P99 算出来就是错的。

3.3 自定义业务指标:这才是核心

HTTP 指标是通用的,但真正能反映业务健康状况的,往往是你们自己定义的指标。比如电商的「下单成功率」、支付的「交易超时率」、视频平台的「播放卡顿率」。

我曾经帮一个电商团队排查问题。他们的 HTTP 错误率一直很低,但业务方天天投诉说用户下不了单。后来一查,原来是库存服务返回了「库存不足」,这在 HTTP 层面是 200,但业务上就是失败。所以,业务指标必须单独埋点。

from prometheus_client import Counter

# 业务指标:下单结果
ORDER_RESULT = Counter(
    'order_result_total',
    'Order result breakdown',
    ['status']  # success, insufficient_stock, payment_timeout, etc.
)

def create_order(user_id, product_id, quantity):
    try:
        # 业务逻辑...
        if stock < quantity:
            ORDER_RESULT.labels(status='insufficient_stock').inc()
            return {'code': 4001, 'msg': '库存不足'}
        # 继续处理...
        ORDER_RESULT.labels(status='success').inc()
        return {'code': 0, 'msg': '下单成功'}
    except Exception as e:
        ORDER_RESULT.labels(status='exception').inc()
        raise
避坑指南:我曾经见过一个团队,把业务指标埋在了异步任务里。结果任务队列积压,指标延迟了 10 分钟才上报。告警触发的时候,用户已经炸了。记住:关键业务指标必须同步上报,或者至少保证上报路径是高优的。

3.4 采集架构:数据怎么流?

指标埋好了,怎么送到监控系统?这里我画了一张图,把整个链路串起来。

应用层指标采集架构 应用实例 1 /metrics 端点 应用实例 2 /metrics 端点 应用实例 3 /metrics 端点 Prometheus 定时拉取 (scrape) 每 15s 一次 服务发现: K8s / Consul / 静态配置 数据持久化到 TSDB Alertmanager 告警通知 Grafana 仪表盘 & 可视化 关键点 拉取模式 vs 推送模式 服务发现自动管理目标

这个架构里,Prometheus 是核心。它主动去拉取每个应用实例的 /metrics 端点,而不是等应用把数据推过来。为什么?因为拉取模式能更好地控制采集频率,也更容易做服务发现。

核心要点:
  • 每个应用实例暴露一个 /metrics HTTP 端点
  • Prometheus 通过服务发现找到所有实例,定时拉取
  • 数据存储在 Prometheus 的 TSDB 中,供查询和告警使用
  • Grafana 从 Prometheus 读取数据,绘制仪表盘

3.5 实战中的几个坑

最后,分享几个我踩过的坑,希望能帮你省点时间。

  1. 指标基数爆炸:有一次我把用户 ID 作为 label 打进了指标里。结果 Prometheus 直接 OOM 了。记住:label 的基数不能太高,一般控制在几百以内。
  2. 采集超时:应用实例的 /metrics 端点如果响应太慢,Prometheus 会跳过这次采集。我遇到过因为数据库连接池耗尽,导致 /metrics 也卡住了。解决办法:把指标采集和业务逻辑隔离,用单独的线程或进程来暴露指标。
  3. 指标命名混乱:团队里不同人埋的指标,命名风格五花八门。后来我们定了规范:命名空间_模块_指标名_单位。比如 order_create_duration_seconds。这样一目了然。
一个小技巧:在开发环境里,我会用 curl 直接访问 /metrics 端点,看看返回的数据对不对。这比等 Grafana 出图快多了。你可以在本地跑一个 Prometheus,用 promtool check metrics 检查指标格式是否合规。