← 中文资源

简体中文版本

RED 方法

RED 方法为每个服务度量速率、错误、时长——三个黄金指标回答“活着吗、失败吗、慢吗”——适用于请求驱动的端点。

阅读英文原文

概念定义

每个服务仪表盘都需要起点模板。RED 就是请求型负载的那个模板:三个指标、各自回答一个用户相关的问题,可组合成错误率与延迟分位数。

工作机制

插桩按路由与状态类发请求计数器、错误计数器、时长直方图;速率用计数器对时间相除,直方图给出 p50/p95/p99。告警由组合构成:高错误率、p99 抬升、吞吐异常;仪表盘按服务叠这三行。

使用场景

作为一切请求驱动服务(HTTP、RPC、以消息批计的消费者)的默认;面向资源的组件——数据库、缓存——在其旁加 USE(利用率、饱和、错误)。

常见误区

平均时长不可接受:均值藏尾巴,时长必须是直方图,否则“慢的那群人”不可见。错误率只数 5xx 会漏掉 200 里的业务错误——支付被拒、结果为空——那也是用户可见的失败;不除以速率的绝对错误计数在流量变化下误导。RED 度量的是孤立服务:上游劣化会表现为你的错误,因果要靠 trace 归因;路由标签需模板化,原始路径会炸基数。

代码示例

import promClient from 'prom-client';
import express from 'express';

const registry = new promClient.Registry();
promClient.collectDefaultMetrics({ register: registry, prefix: 'svc_' });

const ordersTotal = new promClient.Counter({
  name: 'orders_created_total',
  help: 'Total orders created',
  labelNames: ['tenant', 'channel'],
  registers: [registry],
});

const requestDuration = new promClient.Histogram({
  name: 'http_request_duration_seconds',
  help: 'Request latency distribution',
  buckets: [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5],
  labelNames: ['route', 'method'],
  registers: [registry],
});

async function handleCheckout(req, res) {
  const end = requestDuration.startTimer({ route: '/checkout', method: 'POST' });
  try {
    ordersTotal.inc({ tenant: 'acme', channel: 'web' });
    res.json({ ok: true });
  } finally {
    end();
  }
}

const app = express();
app.post('/checkout', handleCheckout);
app.get('/metrics', async (req, res) => {
  res.set('content-type', registry.contentType);
  res.end(await registry.metrics());
});

app.listen(3000);
from collections import defaultdict
from typing import Iterable

def escape_label_value(value: str) -> str:
    return value.replace('\\', '\\\\').replace('\n', '\\n').replace('"', '\\"')

def exposition(counters: dict[tuple[str, tuple[tuple[str, str], ...]], float]) -> str:
    lines: list[str] = []
    seen = set()
    for (name, labels), value in sorted(counters.items()):
        if name not in seen:
            lines.append(f'# HELP {name} Application counter.')
            lines.append(f'# TYPE {name} counter')
            seen.add(name)
        rendered = ','.join(f'{k}="{escape_label_value(v)}"' for k, v in labels)
        lines.append(f'{name}{{{rendered}}} {value}')
    return '\n'.join(lines) + '\n'

def record(counters, name: str, value: float, **labels: str) -> None:
    key = (name, tuple(sorted(labels.items())))
    counters[key] += value