百万 QPS 不是洪水猛兽:高流量服务的采样、聚合与可视化,咱得这么干!
大家好,我是 Echo_Wish。
今天咱聊一个很多同学一想到就头皮发麻的话题——百万 QPS 服务的追踪与可观测性建设。
不少人一听“百万 QPS”就以为要上最贵的监控方案,卖房买服务器那种。
其实不用这么夸张。高流量不是问题,不会监控才是问题。
尤其是分布式追踪(tracing),如果你还想着给每个请求都打 span,那我劝你先深呼吸三秒。百万 QPS 给你打 span?那你的链路追踪系统会直接变成链路“阻塞”系统。
今天我就跟大家掰开揉碎讲清楚:百万 QPS 下,我们到底该如何做采样、如何做聚合、又如何把它可视化?
放心,全是接地气的干货,不讲那些论文式的废话。
一、为什么百万 QPS 不能全量追踪?
举个例子:
一个请求链路产生 10 个 span。
如果你有 1,000,000 QPS。
那么你要写入的 Span 数量是:
1,000,000 × 10 = 10,000,000 spans per second
10M span/s?
别闹了,那不是 tracing,这是 DDoS tracing backend。
你的 Jaeger、Tempo、Zipkin、SkyWalking 全得跪。
所以高流量服务的追踪,第一条原则:
【原则 1:永远不要全量存储请求链路】
要做的是:
✔ 合理的采样
✔ 高效的聚合
✔ 有意义的可视化
千万别犯那种“领导要全量链路”这种天真错误。
二、采样策略:不是乱抽样,而是“有目的地抽样”
采样是百万 QPS 下的灵魂操作,采样做得好,一切都顺;做不好,就是噩梦。
这里我给大家三个大厂常用的采样策略。
采样策略 1:固定比率采样(概率采样)
比如你只取万分之一:
import random
def should_sample(rate=0.0001):
return random.random() < rate
if should_sample():
print("trace this request")
适用于:
- 稳态流量大
- 请求类型单一
- 主要看整体健康情况
缺点:异常请求不一定采集到。
采样策略 2:基于规则的采样(Rule-based Sampling)
根据 URL、业务类型、用户等级等分类采样:
def should_sample(path):
if path.startswith("/pay"):
return True
if path.startswith("/user/query"):
return random.random() < 0.001
return random.random() < 0.0001
适用于:
- 某些接口更关键
- 有些业务要精准监控
- 请求种类繁多
采样策略 3:基于异常采样(智能采样)——大厂标配
比如:
- 请求耗时 > 200ms
- 返回 5xx
- 业务错误码出现
- 限流命中
- CPU/RT 背压上升
def should_sample_on_error(status, latency):
if status >= 500:
return True
if latency > 0.2:
return True
return random.random() < 0.001
这类采样策略能确保异常一定能采集。
这是百万 QPS 下最重要的一点:
【高流量服务不追踪正常请求,只追踪异常请求】
全量采样 = 杀死你的追踪系统。
三、聚合策略:从“请求级别”变成“指标级别”
有了采样,下一步是聚合数据。
百万 QPS 的服务如果你想看平均耗时、P99 延迟,你肯定不能拿采样后的请求直接算平均值——这会偏得一塌糊涂。
那么怎么办?
答案是:
【把请求“聚合成指标”,而不是直接查看每条请求】
聚合常见的三个策略:
策略 1:直方图聚合(Histogram)
prometheus 大量使用。
每个请求按延迟区间计数:
request_latency_seconds_bucket{
le="0.01"} 242
request_latency_seconds_bucket{
le="0.02"} 1024
request_latency_seconds_bucket{
le="0.05"} 18318
request_latency_seconds_bucket{
le="0.1"} 110293
request_latency_seconds_bucket{
le="+Inf"} 1000000
优点:
- 能计算 P90、P95、P99
- 百万 QPS 下易扩展
- 对可视化非常友好
策略 2:基于 Sketch 的近似聚合(DDSketch)
Datadog、Kafka Streams、DataSketches 都在用。
特点:
- 空间复杂度低
- 可以误差控制
- 百万 QPS 也不压力
Python 示例:
from datadog.dogsketch import DDSketch
sketch = DDSketch()
for latency in latencies:
sketch.add(latency)
print(sketch.get_quantile_value(0.99))
这类 sketch 适合追踪延迟、包大小、队列长度等高频指标。
策略 3:基于 Trace 聚合的“Span 分组统计”
比如你可以对某个接口统计:
- Span 数量
- 平均耗时
- 错误占比
- 下游依赖耗时分布
示例(伪代码):
stats = {
}
for span in spans:
key = span.operation
if key not in stats:
stats[key] = {
"count": 0, "error": 0, "latency": []}
stats[key]["count"] += 1
stats[key]["latency"].append(span.duration)
if span.status >= 500:
stats[key]["error"] += 1
这种的关键思路:
**你不是为了查“每个请求发生了什么”,
而是为了查“整个系统发生了什么”。**
四、百万 QPS 的可视化策略:你需要的是“大盘”,不是“显微镜”
这一点是很多团队最容易翻车的地方。
高流量服务的可视化要遵循一个原则:
【大盘看健康,采样看异常,回溯看细节】
换句话说:
- 健康情况:靠指标(Prometheus/Grafana)
- 异常请求:靠采样 Trace(Jaeger/Tempo)
- 深度排查:靠回溯某个异常请求
你不要把所有 trace 都画出来,那会像看“蚯蚓满天飞”。
下面我给你一个最实用的大盘布局逻辑:
大盘 1:请求流量 & 错误率
- QPS
- HTTP Status 分布
- 业务错误码分布
sum(rate(request_total[1m]))
sum(rate(request_5xx_total[1m]))
这图最好永远放左上角。
大盘 2:延迟直方图(P99、P999)
重点盯:
- RT 突升
- P99 波动
- 长尾延迟变长
这往往等于下游有问题。
大盘 3:慢请求样本(Trace 列表)
✓ 按耗时排序
✓ 按错误优先
✓ 按业务接口分组
这通常是“定位问题的入口”。
大盘 4:依赖拓扑图(Service Map)
高流量微服务必备:
- 谁调用了谁
- 哪个链路拖慢整体
- 下游哪个节点延迟暴涨
可视化示例(OpenTelemetry + Tempo)就是那类“气泡图”。
五、我踩坑总结(血泪教训版)
多年运维经验总结几点给你,绝对能少走弯路:
1. 千万不要把 trace 当作指标使用(会延迟偏差)
Trace 是抽样的。
指标才是全量的。
2. 千万不要采样过低,否则“异常看不到”
普遍问题:
- trace rate=0.0001
- 异常 QPS 大约 5 QPS
- 你一天能抓到一条异常 trace 就不错了
这根本不够查问题。
3. 千万不要只采样成功请求
这是“看什么是什么,一点都不监控”的典型错误。
4. 聚合一定要做直方图,而不是平均值
平均值 = 谎言制造机。
5. 千万不要把所有 trace 都展示在一个大图上
那不是 tracing,那是艺术创作。
六、最后想说一句:高流量不是问题,“可观测性不科学”才是问题
百万 QPS 听起来很猛,但本质上:
- 流量是数据
- 数据是规律
- 规律是可以被测量、聚合、可视化的
可观测性建设不是为了做“炫酷大屏”,
是为了:
✔ 让系统稳
✔ 让问题可查
✔ 让值班工程师不再被叫醒