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
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
3.4 采集架构:数据怎么流?
指标埋好了,怎么送到监控系统?这里我画了一张图,把整个链路串起来。
这个架构里,Prometheus 是核心。它主动去拉取每个应用实例的 /metrics 端点,而不是等应用把数据推过来。为什么?因为拉取模式能更好地控制采集频率,也更容易做服务发现。
- 每个应用实例暴露一个
/metricsHTTP 端点 - Prometheus 通过服务发现找到所有实例,定时拉取
- 数据存储在 Prometheus 的 TSDB 中,供查询和告警使用
- Grafana 从 Prometheus 读取数据,绘制仪表盘
3.5 实战中的几个坑
最后,分享几个我踩过的坑,希望能帮你省点时间。
- 指标基数爆炸:有一次我把用户 ID 作为 label 打进了指标里。结果 Prometheus 直接 OOM 了。记住:label 的基数不能太高,一般控制在几百以内。
- 采集超时:应用实例的
/metrics端点如果响应太慢,Prometheus 会跳过这次采集。我遇到过因为数据库连接池耗尽,导致/metrics也卡住了。解决办法:把指标采集和业务逻辑隔离,用单独的线程或进程来暴露指标。 - 指标命名混乱:团队里不同人埋的指标,命名风格五花八门。后来我们定了规范:
命名空间_模块_指标名_单位。比如order_create_duration_seconds。这样一目了然。
curl 直接访问 /metrics 端点,看看返回的数据对不对。这比等 Grafana 出图快多了。你可以在本地跑一个 Prometheus,用 promtool check metrics 检查指标格式是否合规。