我在项目里埋了 6 个月的业务监控,终于不用等用户投诉才发现问题了

0 阅读9分钟

用户已经遇到问题但你不自知,是业务系统最隐蔽的风险。本文记录从前端错误、接口异常、核心流程到业务指标的监控埋点方案,结合 Sentry + Prometheus + Grafana,让系统问题在用户感知之前就被发现。

cf204a5c-7235-4998-b6b1-8fe410951289.jpg

比系统宕机更可怕的事

去年有一次,我们的核心功能——用户下单支付——在某个时段成功率降到了 70%。用户反复尝试支付失败,客服电话被打爆。而我们直到运营在群里发了张“今天订单怎么少了”的截图,才知道出了问题。翻日志一看,是第三方支付通道临时限流,而我们的代码没处理好降级,直接把错误抛给了前端。

那次之后我一直在想:如果能在成功率刚下降时就收到告警,几分钟内切到备用通道,损失会小得多。但传统监控只盯着 CPU、内存、磁盘——这些那天一切正常。

业务监控和系统监控是两回事。系统监控告诉你机器活没活着,业务监控告诉你用户能不能正常用。本文是我这半年把业务监控从零搭建起来的完整记录。

业务监控的四层金字塔

我把需要监控的内容分为四层,从底层到顶层依次是:

        / 业务指标 \
       / 核心流程  \
      / 接口监控    \
     / 前端错误      \
    /_________________\
  • 第一层:前端错误——用户看到什么报错、页面白屏、JS 异常。
  • 第二层:接口监控——哪些接口报错、响应慢、成功率低。
  • 第三层:核心流程——登录、下单、支付这些关键链路是否通畅。
  • 第四层:业务指标——下单量、支付转化率、客单价有没有异常波动。

下面从第一层开始,逐层往上搭。

第一层:前端错误监控

前端错误是离用户最近的一层。用户看到报错,他不会去区分是后端还是前端的锅,只会觉得“这系统不好用”。

方案选型

我对比了三个方案:

方案优点缺点适合场景
自己写 window.onerror简单无 SourceMap、无聚合、无告警只有 1-2 个页面的项目
Sentry SaaS功能全、零维护数据在第三方、有免费额度限制多数团队
Sentry 自部署数据可控、功能同 SaaS需要维护服务器合规要求高的团队

我们用 Sentry SaaS 免费版,每月 5000 个错误事件够用。关键是它的 SourceMap 自动还原和 Session Replay 功能,排查线上问题效率极高。

接入代码

// src/index.tsx
import * as Sentry from '@sentry/react';

Sentry.init({
  dsn: 'https://xxx@sentry.io/1',
  integrations: [
    Sentry.browserTracingIntegration(),
    Sentry.replayIntegration({
      maskAllText: true,
      blockAllMedia: true,
    }),
  ],
  tracesSampleRate: 0.1,
  replaysSessionSampleRate: 0.01,
  replaysOnErrorSampleRate: 1.0,  // 报错时 100% 录屏
});

核心指标

在 Sentry 里重点关注三个面板:

  • Issues:按错误类型聚合,看 Top 错误是哪些。
  • Performance:LCP、FCP、TBT 等 Web Vitals。
  • Replays:报错用户的录屏回放,排查复现步骤。

实际帮了大忙的一次:一个新版本只在 iOS 15.4 上触发 JS 异常。Sentry 告警带着设备型号和完整堆栈,十分钟定位到是 Array.prototype.at() 在低版本 iOS 上不支持。没 Sentry 的话,这个 Bug 可能躺到有足够多用户投诉才会被发现。

第二层:接口监控

接口监控需要回答三个问题:

  • 哪些接口在报错?
  • 哪些接口变慢了?
  • 整体成功率是多少?

我们在 Nginx 层和应用层都做了埋点,互相印证。

Nginx 层

Nginx 的访问日志本身就是一份完整的接口监控数据。用 nginx-prometheus-exporter 把日志转成 Prometheus 指标:

docker run -d \
  --name nginx-exporter \
  -v /var/log/nginx/access.log:/var/log/nginx/access.log \
  -p 9113:9113 \
  nginx/nginx-prometheus-exporter:0.11.0 \
  -nginx.scrape-uri=http://nginx:8080/stub_status

应用层

Spring Boot 项目用 Micrometer 自动暴露 HTTP 请求指标:

# application.yml
management:
  endpoints:
    web:
      exposure:
        include: prometheus,health,metrics
  metrics:
    export:
      prometheus:
        enabled: true

然后在 Grafana 里配告警规则:

# prometheus rules/api.yml
groups:
  - name: api_alerts
    rules:
      # 5xx 错误率超过 3%
      - alert: High5xxRate
        expr: |
          rate(http_server_requests_total{status=~"5.."}[5m])
          / rate(http_server_requests_total[5m]) > 0.03
        for: 3m
        labels:
          severity: critical
        annotations:
          summary: "{{ $labels.uri }} 5xx 错误率 {{ $value | humanizePercentage }}"

      # P95 响应时间超过 2 秒
      - alert: SlowApi
        expr: |
          histogram_quantile(0.95, rate(http_server_requests_seconds_bucket[5m])) > 2
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "{{ $labels.uri }} P95 延迟 {{ $value }}s"

告警分级

不是所有接口都重要。我们对接口做了分级:

级别接口告警阈值通知方式
P0(核心)登录、支付、下单错误率 > 1%钉钉 @所有人 + 电话
P1(重要)商品详情、购物车错误率 > 5%钉钉群
P2(一般)个人信息、收藏错误率 > 10%邮件汇总

告警分级之后,不再被不重要的问题打扰,也不会漏掉核心问题。

第三层:核心流程监控

前端和接口的监控是“单点”的,但用户的一个操作可能跨越多个服务和接口。核心流程监控关注的是“链路”——比如从点击“下单”到页面跳转“支付成功”,中间经历了多少个步骤,每一步的成功率和耗时是多少。

自定义埋点

// 前端:在关键步骤打点
const startCheckout = () => {
  const traceId = generateTraceId();
  
  // 步骤1: 创建订单
  reportEvent('checkout_start', { traceId, step: 'create_order' });
  
  api.createOrder(cartData).then(res => {
    reportEvent('checkout_step_done', { traceId, step: 'create_order', duration: Date.now() - startTime });
    
    // 步骤2: 发起支付
    reportEvent('checkout_start', { traceId, step: 'pay' });
    return api.pay(res.orderId);
  }).then(() => {
    reportEvent('checkout_complete', { traceId, totalDuration: Date.now() - startTime });
  }).catch(err => {
    reportEvent('checkout_failed', { traceId, step: err.step, reason: err.message });
  });
};

Grafana 漏斗图

在 Grafana 里用 PromQL 算出每一步的转化率,画成漏斗:

下单流程漏斗:
  进入下单页:1000 次
  点击提交:800 次     (80%)
  创建订单成功:750 次  (93.8%)
  支付成功:620 次      (82.7%)
  整体转化率:62%

如果支付成功率突然从 82% 掉到 50%,告警立刻触发。那次支付通道限流的问题,如果当时有这张漏斗图,不用等运营通知,Grafana 自己就发消息了。

第四层:业务指标监控

这一层关注的是业务数据本身的波动。技术指标(接口成功率、响应时间)正常,不代表业务正常。比如:今天订单量相比昨天同一时段少了 30%,虽然系统完全正常,但业务上可能出了问题——运营配错了活动价格、投放广告停了、或者竞品搞了促销。

指标定义

业务指标数据源对比维度
下单量订单表同比昨天同一时段、环比上周
支付转化率订单表 + 支付日志同上
客单价订单表同上
新增用户用户表同上
核心功能使用率埋点日志同上

实现方案

每天定时跑一个脚本,把昨天的业务数据聚合到一张 business_metrics 表,然后暴露给 Prometheus:

-- 每小时执行一次,写入指标表
INSERT INTO business_metrics (metric_name, metric_value, recorded_at)
SELECT 
  'orders_count' as metric_name,
  COUNT(*) as metric_value,
  DATE_FORMAT(NOW(), '%Y-%m-%d %H:00:00') as recorded_at
FROM orders
WHERE created_at >= DATE_FORMAT(NOW() - INTERVAL 1 HOUR, '%Y-%m-%d %H:00:00')
  AND created_at < DATE_FORMAT(NOW(), '%Y-%m-%d %H:00:00');

然后用一个简单的 Python 脚本把指标暴露为 Prometheus 格式:

# business_metrics_exporter.py
from flask import Flask
import mysql.connector
import os

app = Flask(__name__)

@app.route('/metrics')
def metrics():
    conn = mysql.connector.connect(
        host=os.environ['DB_HOST'],
        user=os.environ['DB_USER'],
        password=os.environ['DB_PASSWORD'],
        database=os.environ['DB_NAME']
    )
    cursor = conn.cursor()
    cursor.execute("""
        SELECT metric_name, metric_value 
        FROM business_metrics 
        WHERE recorded_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
        ORDER BY recorded_at DESC
        LIMIT 100
    """)
    
    output = []
    for metric_name, metric_value in cursor.fetchall():
        output.append(f'{metric_name} {metric_value}')
    
    cursor.close()
    conn.close()
    return '\n'.join(output), 200, {'Content-Type': 'text/plain'}

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=9200)

然后在 Prometheus 里增加采集任务:

scrape_configs:
  - job_name: 'business_metrics'
    static_configs:
      - targets: ['metrics-exporter:9200']
    scrape_interval: 30m

这个方案比较简单,适合快速落地。如果团队需要更完善的业务指标管理(比如自动同比、异常检测、多维下钻),也可以参考 gpt108.com 上分享的《业务监控进阶:用 ClickHouse + Grafana 搭建实时业务大盘》,里面包含了更复杂场景的设计思路。

同比告警

光看绝对值不够,要用同比来判断异常:

# prometheus rules/business.yml
groups:
  - name: business_alerts
    rules:
      # 每小时下单量相比昨天同一时段下降超过 30%
      - alert: OrderDropWarning
        expr: |
          (orders_count - orders_count offset 1d) / (orders_count offset 1d) < -0.3
        for: 1h
        labels:
          severity: warning
        annotations:
          summary: "下单量同比下跌超过 30%"
          description: "当前小时订单量 {{ $value | humanize }},较昨日同时段大幅下降"

      # 支付转化率异常
      - alert: ConversionRateDrop
        expr: |
          (payment_success_rate - payment_success_rate offset 1d) < -0.1
        for: 30m
        labels:
          severity: critical
        annotations:
          summary: "支付转化率下跌超过 10 个百分点"

业务指标告警发到运营群,配合技术告警发到技术群。各看各的,快速判断是技术问题还是业务问题。

搭建后的几个真实案例

这套监控体系上线半年,帮我们发现了几次系统监控完全看不出来的问题:

  1. 支付通道降级:支付成功率从 82% 降到 50%,Prometheus 告警触发后,运维切备用通道,5 分钟恢复。如果没有业务监控,要等运营发现订单变少才能定位到支付环节。

  2. 前端兼容性 Bug:一个新版本在 iOS 15.4 上白屏,Sentry 自动捕获、还原源码堆栈、录像回放。从用户首次报错到修复上线 15 分钟。

  3. 数据库慢查询:凌晨跑批任务突然变慢,P95 响应时间告警触发,定位到统计 SQL 走了全表扫描。当天加了索引,后续告警消失。

  4. 活动配置错误:运营配错了促销规则,客单价从 120 元跌到 60 元。业务指标告警发到运营群,5 分钟内修复。

分层监控的协作模式

不同角色看不同的监控面板:

角色关注层主要工具面板内容
前端开发第一层SentryJS 错误、白屏、Web Vitals
后端开发第二层Grafana接口成功率、延迟、QPS
技术负责人第三层Grafana核心流程漏斗、SLA
运营/产品第四层Grafana下单量、转化率、客单价

各看各的,出问题时不用在群里互相问“你那边正常吗”。

总结

业务监控和系统监控的关系,就像汽车仪表盘和发动机故障灯。发动机故障灯亮了,你知道车坏了;但仪表盘告诉你车速多少、还剩多少油、还能开多远。

搭建建议自上而下:

  1. 先上 Sentry:前端错误一键接入,5 分钟搞定。
  2. 再加 Prometheus + Grafana:接口监控和系统监控一步到位。
  3. 再补核心流程埋点:找出你的关键业务流程,埋上自定义指标。
  4. 最后建业务指标:把数据库里跑定时任务,暴露给 Prometheus。

四层全部搭完,你的系统就从一个“黑盒”变成了“透明盒”。用户还没发现问题,你已经知道了。


你的项目里做了几层监控?哪一层让你最有“救了一命”的感觉?欢迎评论区交流。