一、支付平台为什么必须有自己的监控告警

转卡码、自动发卡这类支付平台,最怕的不是写不出代码,而是出了问题没人知道:凌晨三点用户付款成功却没收到卡密、回调服务悄悄挂掉、支付通道被风控拦截——每一分钟都在真金白银地流失。等第二天早上打开后台才发现,损失已经无法挽回。

监控告警的意义,就是把"事后发现"变成"事中告警":掉单率、回调失败率、发货延迟、库存水位这些关键指标一旦越过阈值,钉钉/企业微信立刻把告警推到手机。本文以 Python + FastAPI 的转卡码后端为例,从零搭建一套完整的支付监控告警体系,全程可运行。

二、先定指标:支付平台必盯的 8 个核心指标

监控不是越多越好,指标太多反而淹没真正的问题。支付平台建议优先盯住这 8 个:

指标类型建议阈值
支付成功率(成功/发起)比率< 95% 告警
回调成功率(验签通过/总回调)比率< 99% 告警
回调延迟 P95耗时> 5s 告警
发货延迟 P95(支付成功→卡密发出)耗时> 3s 告警
卡密库存水位数值< 100 告警
支付通道健康状态0/1= 0 立即告警
MQ 队列积压数数值> 1000 告警
5xx 错误率比率> 1% 告警

这 8 个指标覆盖了"收得到钱、发得出货、通道活着"三条生命线。下面用 prometheus_client 挨个落地。

三、Python 埋点:prometheus_client 实战

Prometheus 的埋点模型分四种:Counter(只增不减,适合计数)、Gauge(可增可减,适合水位)、Histogram(分布,适合延迟)、Summary(分位数)。安装依赖并定义指标:

pip install prometheus-client fastapi uvicorn

# metrics.py 定义全局指标
from prometheus_client import Counter, Gauge, Histogram

pay_total = Counter('pay_total', '支付发起总次数', ['channel'])
pay_success = Counter('pay_success_total', '支付成功次数', ['channel'])
callback_total = Counter('callback_total', '回调接收次数')
callback_fail = Counter('callback_fail_total', '验签失败次数')
deliver_latency = Histogram('deliver_latency_seconds',
    '支付成功到发货完成耗时', buckets=(0.1, 0.5, 1, 2, 3, 5, 10))
stock = Gauge('card_stock', '卡密库存剩余', ['product_id'])
queue_depth = Gauge('mq_queue_depth', '消息队列积压数')

在支付回调接口里打点,注意把通道维度打上去,便于事后按通道拆分:

# notify.py 支付宝/微信回调入口
async def notify(request):
    ok = verify_sign(request)          # 验签
    if ok:
        callback_total.inc()
        # 幂等处理订单,发货成功再打点
        await deliver_card(order)
        deliver_latency.observe(time.time() - order.paid_at)
        pay_success.labels(channel=order.channel).inc()
    else:
        callback_fail.inc()            # 验签失败也要计数,防伪造攻击
    return {"code": 0}

最后暴露 /metrics 端点给 Prometheus 抓取:

# main.py 挂载 metrics 路由
from prometheus_client import make_asgi_app
from fastapi import FastAPI

app = FastAPI()
app.mount("/metrics", make_asgi_app())  # GET /metrics 返回指标文本

四、Prometheus 采集与告警规则

用 Docker 一键起 Prometheus,配置文件指向你的服务:

# prometheus.yml
scrape_configs:
  - job_name: 'pay-api'
    metrics_path: '/metrics'
    static_configs:
      - targets: ['127.0.0.1:8000']
    # 支付接口频率高,缩短抓取周期
    scrape_interval: 15s
docker run -d --name prometheus -p 9090:9090 \
  -v /etc/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml \
  prom/prometheus

告警规则用 PromQL 写。下面三条分别覆盖回调失败率突增发货延迟超标卡密库存告罄

# pay_alerts.yml
groups:
  - name: pay-alerts
    rules:
      - alert: CallbackFailRateHigh
        expr: rate(callback_fail_total[5m]) / rate(callback_total[5m]) > 0.01
        for: 2m
        labels: { severity: critical }
        annotations:
          summary: "回调失败率超过 1%,可能存在验签问题或伪造攻击"
      - alert: DeliverLatencyHigh
        expr: histogram_quantile(0.95, rate(deliver_latency_seconds_bucket[5m])) > 3
        for: 5m
        labels: { severity: warning }
        annotations:
          summary: "发货 P95 延迟超过 3 秒"
      - alert: StockLow
        expr: card_stock < 100
        for: 5m
        labels: { severity: warning }
        annotations:
          summary: "卡密库存不足 100,请及时补充"

五、Alertmanager 对接钉钉机器人

告警规则触发后,由 Alertmanager 统一收敛并推送到钉钉群。先建一个钉钉群机器人(安全设置选"加签"),再写一个 webhook 转发服务:

# dingtalk.py 钉钉机器人加签 + 发送
import time, hmac, hashlib, base64, urllib.parse, requests

def sign(secret: str) -> str:
    ts = str(round(time.time() * 1000))
    string_to_sign = f"{ts}\n{secret}"
    h = hmac.new(secret.encode(), string_to_sign.encode(),
                 hashlib.sha256).digest()
    sign = urllib.parse.quote_plus(base64.b64encode(h))
    return ts, sign

def send(title: str, text: str):
    ts, s = sign(SECRET)
    url = (f"https://oapi.dingtalk.com/robot/send?access_token={TOKEN}"
           f"&timestamp={ts}&sign={s}")
    requests.post(url, json={
        "msgtype": "markdown",
        "markdown": {"title": title, "text": text}})

Alertmanager 配置里把 webhook 指到这个服务:

# alertmanager.yml
route:
  group_by: ['alertname']
  group_wait: 10s
  repeat_interval: 1h
  receiver: 'dingtalk'
receivers:
  - name: 'dingtalk'
    webhook_configs:
      - url: 'http://127.0.0.1:5000/alert'
        send_resolved: true

这样告警一旦触发,1 分钟内就能出现在手机钉钉上,值班人员不用一直盯着 Grafana 面板。

六、trace_id 日志链路:出问题时 30 秒定位

指标只能告诉你"哪里坏了",日志才能告诉你"为什么坏"。给每个请求分配一个 trace_id,从下单到支付回调、发货全程透传,出问题时一条 grep 拉出完整链路:

# 中间件生成 trace_id 并注入日志
import uuid, logging

@app.middleware("http")
async def trace_middleware(request, call_next):
    trace_id = request.headers.get("X-Trace-Id") or uuid.uuid4().hex[:12]
    request.state.trace_id = trace_id
    logger = logging.LoggerAdapter(logging.getLogger("pay"),
        {"trace_id": trace_id})
    response = await call_next(request)
    response.headers["X-Trace-Id"] = trace_id
    return response

# 日志格式带上 trace_id 和订单号
# 2026-08-20 03:12:44 [a1b2c3d4e5f6] 回调验签成功 order=WX20260820031244
grep "a1b2c3d4e5f6" /var/log/pay/app.log

配合 curl -H "X-Trace-Id: test001" 手动复现问题,整条调用链一目了然。日志量大了之后再上 Loki/ELK,但先把 trace_id 打对,比什么组件都管用

七、故障演练:告警体系要定期"点火"

监控搭完不演练等于没搭。建议每月做一次故障演练:

  • 杀进程:kill 掉回调服务,确认 5 分钟内收到告警;
  • 改错密钥:把验签密钥改错,确认回调失败率告警触发;
  • 清零库存:把卡密库存改到阈值以下,确认低库存告警;
  • 断网模拟:拔掉外网,确认通道健康检查告警。
记住:告警的本质不是"通知",而是可行动的决策信息——收到告警后 30 秒内能定位问题,这套监控才算合格。

八、总结

监控告警是支付平台的"仪表盘"和"守夜人"。本文从指标设计、Prometheus 埋点、告警规则到钉钉通知、trace_id 链路,串起了完整闭环,全部代码可以直接抄进你的转卡码系统。如果你不想从零写这些基础设施,源码商城的 转卡码系统 v3 已内置 Prometheus 指标埋点、库存水位告警和钉钉通知模块,部署即用;配合 Codex Desktop 本地 AI 编程助手,改告警规则、调 PromQL 的效率还能再翻一倍。