RED 方法论落地:Rate、Errors、Duration 的生产指标体系

发布时间:2026/7/13 12:57:12
RED 方法论落地:Rate、Errors、Duration 的生产指标体系 RED 方法论落地Rate、Errors、Duration 的生产指标体系一、服务挂了 20 分钟才被发现的监控盲区一个内部 Agent 服务开发自测通过后部署上线。两周后用户反馈功能用不了一查才发现服务三天前就开始间歇性返回 500 错误——但因为 QPS 很低每小时几十个请求告警阈值从来没触发。CPU 正常、内存正常、进程存活——传统的主机监控全部绿灯但业务已经红灯了。主机监控告诉你机器活着RED 方法论告诉你服务在工作吗。RED 关注的是外部可见的服务行为Rate请求速率、Errors错误率、Duration请求延迟。这三个指标覆盖了服务质量的 90% 维度。二、RED 三指标的完整定义Rate请求速率每秒接收的请求数。关注两个维度总请求数反映流量和成功请求数反映有效吞吐。QPS 突然下降可能意味着上游出问题或下游限流。Errors错误率失败请求占总请求的比例。需要明确定义什么是错误——HTTP 5xx业务返回码非 0超时未响应建议分层定义。Duration请求延迟请求从进入到返回的时间。必须用分位数P50/P95/P99而非平均值。平均延迟 50ms 但 P99 延迟 2 秒——说明有少量慢请求在影响用户体验。flowchart TD A[服务接收请求] -- B[Rate 指标] B -- B1[请求总数] B -- B2[按 API 端点分桶] A -- C[Duration 指标] C -- C1[Histogram 分桶] C1 -- C2[P50/P95/P99 计算] A -- D[Errors 指标] D -- D1[HTTP 5xx] D -- D2[业务错误码] D -- D3[超时/取消] B2 -- R[RED 面板] C2 -- R D3 -- R R -- AL{P95 1s 且持续 5 分钟} AL --|是| P1[P1 告警: 服务劣化] R -- ALE{错误率 5%} ALE --|是| P0[P0 告警: 服务异常] R -- ALR{QPS 骤降 50%} ALR --|是| P1[P1 告警: 流量异常]三、Go 实现RED 指标采集与暴露package metrics import ( net/http strconv time github.com/prometheus/client_golang/prometheus github.com/prometheus/client_golang/prometheus/promauto github.com/prometheus/client_golang/prometheus/promhttp ) // RED 指标定义 var ( // Rate: 请求总数按方法、路径、状态码分组 RequestTotal promauto.NewCounterVec( prometheus.CounterOpts{ Name: http_requests_total, Help: HTTP 请求总数 (Rate), }, []string{method, path, status_code}, ) // Errors: 错误请求总数按错误类型分组 RequestErrors promauto.NewCounterVec( prometheus.CounterOpts{ Name: http_request_errors_total, Help: HTTP 请求错误总数 (Errors), }, []string{method, path, error_type}, // timeout, 5xx, business_error ) // Duration: 请求延迟直方图关键用 Histogram 而非 Summary RequestDuration promauto.NewHistogramVec( prometheus.HistogramOpts{ Name: http_request_duration_seconds, Help: HTTP 请求延迟分布 (Duration), Buckets: prometheus.DefBuckets, // .005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10 }, []string{method, path}, ) // 额外正在处理的请求数用于诊断并发问题 InFlightRequests promauto.NewGaugeVec( prometheus.GaugeOpts{ Name: http_requests_in_flight, Help: 当前正在处理的请求数, }, []string{method, path}, ) ) // RED 中间件 // REDMiddleware RED 指标采集中间件 func REDMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start : time.Now() // 规范化路径避免高基数问题/user/123 → /user/:id path : normalizePath(r.URL.Path) // 记录并发数 InFlightRequests.WithLabelValues(r.Method, path).Inc() defer InFlightRequests.WithLabelValues(r.Method, path).Dec() // 包装 ResponseWriter 以捕获状态码 wrapped : responseWriter{ResponseWriter: w, statusCode: http.StatusOK} // 执行下一个处理器 next.ServeHTTP(wrapped, r) // 记录 Duration duration : time.Since(start).Seconds() RequestDuration.WithLabelValues(r.Method, path).Observe(duration) // 记录 Rate statusStr : strconv.Itoa(wrapped.statusCode) RequestTotal.WithLabelValues(r.Method, path, statusStr).Inc() // 记录 Errors if wrapped.statusCode 500 { RequestErrors.WithLabelValues(r.Method, path, server_error).Inc() } else if wrapped.statusCode 400 { RequestErrors.WithLabelValues(r.Method, path, client_error).Inc() } }) } // 业务错误追踪 // RecordBusinessError 在业务逻辑中手动记录业务错误 func RecordBusinessError(method, path, errorCode string) { RequestErrors.WithLabelValues(method, path, business_errorCode).Inc() } // 响应状态码捕获 type responseWriter struct { http.ResponseWriter statusCode int } func (rw *responseWriter) WriteHeader(code int) { rw.statusCode code rw.ResponseWriter.WriteHeader(code) } // 路径规范化防止高基数问题 func normalizePath(path string) string { // 实际项目中用正则或路由库提取模式 // 简化示例替换数字段为 :id parts : splitPath(path) for i, p : range parts { if isNumeric(p) { parts[i] :id } } return joinPath(parts) } func splitPath(path string) []string { // 简化实现 var parts []string current : for _, c : range path { if c / { if current ! { parts append(parts, current) } current } else { current string(c) } } if current ! { parts append(parts, current) } return parts } func joinPath(parts []string) string { result : for _, p : range parts { result / p } if result { result / } return result } func isNumeric(s string) bool { for _, c : range s { if c 0 || c 9 { return false } } return len(s) 0 } // Prometheus 端点暴露 func StartMetricsServer(addr string) { http.Handle(/metrics, promhttp.Handler()) go func() { if err : http.ListenAndServe(addr, nil); err ! nil { panic(err) } }() } // Grafana PromQL 查询参考 // Rate: QPS每秒请求数 // rate(http_requests_total{path/api/chat}[1m]) // Errors: 错误率百分比 // sum(rate(http_request_errors_total[5m])) / sum(rate(http_requests_total[5m])) * 100 // Duration: P95 延迟 // histogram_quantile(0.95, // sum(rate(http_request_duration_seconds_bucket[5m])) by (le, path)) // 错误预算消耗率结合 SLO // 1 - (sum(rate(http_requests_total{status_code!~5..}[28d])) / // sum(rate(http_requests_total[28d])))四、RED 指标的边界与注意事项高基数High Cardinality陷阱。如果在指标标签里用了用户 ID 或完整 URL含动态参数会导致时间序列爆炸。Prometheus 无法处理超过 10 万个活跃时间序列的场景。路径必须规范化/api/user/123→/api/user/:id。Histogram 桶的预设影响分位数精度。如果默认桶的最大值是 10 秒而你的服务 P99 是 15 秒P99 的值会被截断为 10 秒。桶的设计要覆盖你的 SLA 目标如果需要 P99 200ms桶应该在 200ms 附近加密。平均延迟是最危险的指标。100 个请求 10ms1 个请求 10 秒平均还是 110ms——看起来很健康。P99 才是用户的真实感知。永远用分位数判断延迟永远不看平均值。错误率的定义需要团队共识。HTTP 504 肯定是错误但业务返回码{code: 1001, msg: 余额不足}算不算错误建议定义两个维度的错误率技术错误率5xx 超时和业务错误率按错误码层级归类。五、总结RED 指标是服务可观测性的底线配置。Rate 告诉你负载如何Errors 告诉你健康与否Duration 告诉你快不快。实施要点路径规范化防止高基数、Histogram 桶针对 SLA 目标调优、分位数替代平均值判断延迟。RED 上线后的第一件事不是调 Dashboard而是根据一周的真实数据校准告警阈值——数据驱动的阈值永远不会错。