属于我们的Performance & Scalability系列
阅读完整指南生产事故平均每分钟停机成本为 5,600 美元。 拥有成熟监控的公司可在 5 分钟内检测到问题,而那些没有监控的公司平均需要 197 分钟才能检测到——这就是小问题和客户流失灾难之间的区别。
本指南涵盖端到端生产监控设置:测量什么、如何收集、在何处可视化以及何时发出警报。
要点
- 可观察性的三大支柱(指标、日志、跟踪)服务于不同的目的,而这三者都是必要的
- 对症状(错误率、延迟)而非原因(CPU 使用率)发出警报,将噪音降低 80%
- 每个警报附带的操作手册可确保一致的事件响应,无论谁在值班
- 从 5 个基本警报开始,仅在您了解基线后才进行扩展
可观察性的三大支柱
指标
随着时间的推移采样的数值测量值。指标回答“现在发生了什么?”
应用指标:
- 请求率(每秒请求数)
- 错误率(每秒 5xx 响应)
- 延迟分布(P50、P95、P99)
- 活动会话/并发用户
基础设施指标:
- 每个服务的CPU利用率
- 内存使用和垃圾收集
- 磁盘 I/O 和可用空间
- 网络吞吐量
业务指标:
- 每分钟订单数
- 购物车放弃率
- 每小时收入
- 通过端点进行API调用
日志
离散事件的带时间戳的结构化记录。日志回答“为什么会发生?”
{
"timestamp": "2026-03-16T14:32:01.234Z",
"level": "error",
"service": "api",
"requestId": "req_abc123",
"userId": "usr_456",
"message": "Payment processing failed",
"error": "Stripe API timeout after 30000ms",
"endpoint": "POST /billing/checkout",
"duration": 30142
}
记录最佳实践:
- 使用结构化 JSON 日志记录,而不是纯文本
- 包括跨服务的关联 ID (
requestId) - 以适当的级别记录(错误表示失败,警告表示降级,信息表示关键事件)
- 切勿记录敏感数据(密码、令牌、完整信用卡号)
痕迹
通过分布式系统的端到端请求路径。 Traces回答“瓶颈在哪里?”
单个用户对电子商务结帐的请求可能会涉及:
- Nginx (2ms) 到 Next.js 前端 (50ms) 到 NestJS API (120ms) 到 PostgreSQL (45ms) 到 Stripe API (800ms) 到电子邮件服务 (200ms)
如果没有跟踪,您会看到“结账需要 1.2 秒”。通过跟踪,您会看到“Stripe API 占结帐延迟的 67%”。
监控堆栈设置
Prometheus + Grafana(自托管)
# docker-compose.monitoring.yml
services:
prometheus:
image: prom/prometheus:v2.50.0
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus
ports:
- "9090:9090"
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.retention.time=30d'
- '--web.enable-lifecycle'
grafana:
image: grafana/grafana:10.3.0
volumes:
- grafana-data:/var/lib/grafana
ports:
- "3030:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
- GF_USERS_ALLOW_SIGN_UP=false
loki:
image: grafana/loki:2.9.0
volumes:
- loki-data:/loki
ports:
- "3100:3100"
alertmanager:
image: prom/alertmanager:v0.27.0
volumes:
- ./alertmanager.yml:/etc/alertmanager/alertmanager.yml
ports:
- "9093:9093"
volumes:
prometheus-data:
grafana-data:
loki-data:
普罗米修斯配置
# prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- "alerts/*.yml"
alerting:
alertmanagers:
- static_configs:
- targets: ["alertmanager:9093"]
scrape_configs:
- job_name: "api"
metrics_path: /metrics
static_configs:
- targets: ["api:3001"]
- job_name: "node-exporter"
static_configs:
- targets: ["node-exporter:9100"]
- job_name: "postgres"
static_configs:
- targets: ["postgres-exporter:9187"]
- job_name: "redis"
static_configs:
- targets: ["redis-exporter:9121"]
NestJS 应用指标
暴露 Prometheus 指标
// metrics.module.ts
import { Module } from '@nestjs/common';
import { PrometheusModule } from '@willsoto/nestjs-prometheus';
import {
makeCounterProvider,
makeHistogramProvider,
makeGaugeProvider,
} from '@willsoto/nestjs-prometheus';
@Module({
imports: [
PrometheusModule.register({
path: '/metrics',
defaultMetrics: { enabled: true },
}),
],
providers: [
makeCounterProvider({
name: 'http_requests_total',
help: 'Total HTTP requests',
labelNames: ['method', 'path', 'status'],
}),
makeHistogramProvider({
name: 'http_request_duration_seconds',
help: 'HTTP request duration in seconds',
labelNames: ['method', 'path'],
buckets: [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
}),
makeGaugeProvider({
name: 'active_connections',
help: 'Number of active connections',
}),
],
exports: [PrometheusModule],
})
export class MetricsModule {}
警报配置
五个基本警报
每个生产系统从第一天起就需要这些警报:
# alerts/essential.yml
groups:
- name: essential
rules:
- alert: ServiceDown
expr: up == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Service {{ $labels.job }} is down"
runbook: "https://wiki.example.com/runbooks/service-down"
- alert: HighErrorRate
expr: |
rate(http_requests_total{status=~"5.."}[5m])
/ rate(http_requests_total[5m]) > 0.01
for: 5m
labels:
severity: critical
annotations:
summary: "Error rate above 1% for 5 minutes"
runbook: "https://wiki.example.com/runbooks/high-error-rate"
- alert: HighLatency
expr: |
histogram_quantile(0.95,
rate(http_request_duration_seconds_bucket[5m])
) > 2
for: 5m
labels:
severity: warning
annotations:
summary: "P95 latency above 2 seconds"
- alert: DiskSpaceLow
expr: |
node_filesystem_avail_bytes{mountpoint="/"}
/ node_filesystem_size_bytes{mountpoint="/"} < 0.2
for: 10m
labels:
severity: warning
annotations:
summary: "Disk space below 20% on {{ $labels.instance }}"
- alert: SSLCertExpiringSoon
expr: |
probe_ssl_earliest_cert_expiry - time() < 14 * 24 * 3600
labels:
severity: warning
annotations:
summary: "SSL certificate expires within 14 days"
警报路由
# alertmanager.yml
global:
slack_api_url: "${SLACK_WEBHOOK_URL}"
route:
group_by: ['alertname', 'severity']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
receiver: 'default'
routes:
- match:
severity: critical
receiver: 'pagerduty'
repeat_interval: 15m
- match:
severity: warning
receiver: 'slack'
receivers:
- name: 'default'
slack_configs:
- channel: '#alerts'
title: '{{ .GroupLabels.alertname }}'
text: '{{ .CommonAnnotations.summary }}'
- name: 'pagerduty'
pagerduty_configs:
- routing_key: "${PAGERDUTY_KEY}"
severity: '{{ .GroupLabels.severity }}'
- name: 'slack'
slack_configs:
- channel: '#alerts-warnings'
title: '{{ .GroupLabels.alertname }}'
警报质量规则
| 实践 | 为什么 |
|---|---|
| 警惕症状,而非原因 | “错误率高”是可操作的; “CPU 达到 80%”可能不是 |
| 每个警报都有一个操作手册 | 值班工程师不需要在凌晨 3 点思考 |
| 警报必须具有可操作性 | 如果没有人能够采取行动,那就是噪音,而不是警报 |
| 两周后调整阈值 | 初始阈值是猜测;根据基线进行调整 |
| 每月检查警报疲劳 | 如果每天触发警报但没有采取任何行动,请提高阈值或将其删除 |
Grafana 仪表板
仪表板层次结构
- 概览仪表板:所有服务的高级运行状况。这是发生事件时任何人看到的第一个屏幕。
- 服务仪表板:每个服务(API、Web、工作人员)的详细指标。
- 基础设施仪表板:节点级指标(CPU、内存、磁盘、网络)。
- 业务仪表板:收入、订单、用户活动。
服务仪表板的 RED 方法
对于每项服务,显示:
- Rate:每秒请求数
- Eerrors:错误率百分比
- Duration:延迟分布(P50、P95、P99)
这提供了对服务运行状况的即时可见性,而无需认知过载。
使用 Sentry 进行错误跟踪
// sentry.config.ts
import * as Sentry from '@sentry/nestjs';
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: 0.1,
profilesSampleRate: 0.1,
integrations: [
Sentry.postgresIntegration(),
],
beforeSend(event) {
// Strip sensitive data
if (event.request?.headers) {
delete event.request.headers['authorization'];
delete event.request.headers['cookie'];
}
return event;
},
});
哨兵提供:
- 自动错误分组和重复数据删除
- 带有源映射的堆栈跟踪
- 发布跟踪(哪个部署引入了错误)
- 性能监控(交易痕迹)
常见问题
监控堆栈的成本是多少?
自托管(Prometheus + Grafana + Loki):托管资源约为每月 50-100 美元。托管替代方案:Datadog 基础设施起价为 15 美元/主机/月,日志费用为 0.10 美元/GB。 Sentry Cloud 团队计划的价格为 26 美元/月。小型企业合理的起始预算总计为 100-200 美元/月。
What is the difference between monitoring and observability?
监控会在出现问题时告诉您。可观察性告诉你原因。监控是针对已知故障模式的预定义仪表板和警报。可观察性是指使用指标、日志和跟踪来询问有关系统行为的任意问题的能力。两者都需要,但监控是基础。
我们如何避免警觉疲劳?
三个规则:(1) 每个警报都必须需要人工操作,(2) 根据实际基线而不是理论理想设置阈值,(3) 每月检查和调整警报。如果警报每周触发一次以上且无需采取措施,请解决根本问题或提高阈值。遭受警报疲劳的团队会忽略所有警报,包括关键警报。
我们应该以不同的方式监控我们的 ERP 系统吗?
ERP 系统具有独特的监控要求。除了标准 Web 指标之外,还可以监控:数据库连接池使用情况、后台作业队列深度、集成同步状态(Shopify、支付网关)、计划的报告执行时间以及按模块的用户会话计数。 ECOSIRE 提供托管 Odoo 监控 作为我们支持包的一部分。
接下来会发生什么
监控是生产基础设施的眼睛和耳朵。将其与 CI/CD 自动化 配对以提高部署信心,并与 灾难恢复规划 配对以提高弹性。有关全面的 DevOps 路线图,请参阅我们的小型企业 DevOps 指南。
联系 ECOSIRE 以监控设置和托管基础设施服务。
由 ECOSIRE 发布——帮助企业了解生产中的重要因素。
作者
ECOSIRE TeamTechnical Writing
The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.
相关文章
2026 年云托管成本是多少?实际价格细分(AWS、Hetzner、DigitalOcean、Odoo.sh)
真正的 2026 年云托管成本来自支付账单的团队:业余爱好每月 5-25 美元,中小企业每月 50-400 美元,隐藏出口和备份费用,预留实例数学。
2026 年 Odoo 托管要求:按用户数量调整服务器规模(使用实际配置)
按用户数量划分的 Odoo 托管要求:5 至 250 个以上用户的 vCPU、RAM、存储和工作线程设置,以及实际部署中的 PostgreSQL 调整值。
Odoo CI/CD 与 GitHub Actions:测试和部署
使用 GitHub Actions 构建生产 Odoo CI/CD 管道:linting、runbot 式测试、多版本矩阵、分阶段部署、零停机生产。
更多来自Performance & Scalability
Shopify 速度优化:真正改变核心网络生命力的技术清单 (2026)
经过现场测试的 2026 年 Shopify 速度清单 — 哪些因素实际上改进了真实商店中的 LCP、INP 和 CLS,哪些因素浪费了时间,以及如何审核应用程序和主题。
2026 年技术 SEO 审核清单:我们在每个客户网站上运行的 47 项检查
2026 年,我们在每个客户网站上运行了 47 点技术 SEO 审核清单——可爬行性、索引、规范、hreflang、核心网络生命和日志。
Odoo 19 HR:技能矩阵、职业规划、绩效周期
Odoo 19 HR 升级:本地技能矩阵、职业道路规划、绩效评估周期、9 框网格、继任计划、HRIS 集成。
Odoo 19 性能基准:PostgreSQL 17 调整数字
真实的 Odoo 19 性能基准:Web 客户端速度、ORM 吞吐量、PG17 调整设置、连接池、工作线程数、扩展阈值。
OpenClaw 大规模成本优化和代币效率
OpenClaw 令牌成本优化:提示缓存、模型路由、响应缓存、批处理 API 和生产代理的每租户成本护栏。
Power BI 增量刷新超过 1000 万行的表
适用于 10M 以上行表的 Power BI 增量刷新手册:分区设计、RangeStart/RangeEnd、刷新策略、查询折叠和 DirectQuery 混合。