3、聚合函数详解:Count、Sum、Avg、Max、Min、Histogram、Quantile 的实现与选型
聚合函数,说白了就是实时指标系统的「算盘」。你采集上来的数据是散的,但老板想看的是总数、平均值、或者某个百分位。这章我就把几个最常用的聚合函数掰开揉碎了讲,顺便聊聊我踩过的坑。
3.1 基础聚合:Count、Sum、Avg、Max、Min
这几个函数是入门级,但用好了能解决 80% 的场景。我一个个说。
3.1.1 Count:计数
Count 最简单,就是数数。但实时系统里有个坑——去重计数和非去重计数。
- 非去重 Count:每条数据来一次就加 1。适合统计请求总量、日志条数。
- 去重 Count:需要维护一个集合,记录哪些 ID 已经算过了。内存开销大,但结果准确。
3.1.2 Sum:求和
Sum 就是把所有值加起来。听起来简单,但注意溢出问题。
// 错误示例:int 溢出
int sum = 0;
for (int value : values) {
sum += value; // 如果 values 很大,sum 会溢出
}
// 正确做法:使用 long 或 BigInteger
long sum = 0L;
for (int value : values) {
sum += value;
}
我曾经在统计广告点击金额时,用 int 存 sum,结果一天的数据就溢出了。嗯,从那以后我所有 Sum 都用 long。
3.1.3 Avg:平均值
Avg 的实现有两种方式:
- 实时计算:维护 count 和 sum,每次查询时 sum / count。
- 滑动窗口:只算最近 N 条数据的平均值。
我建议用第一种,因为简单可靠。滑动窗口的 Avg 容易受窗口大小影响,你想想看,窗口太小波动大,窗口太大反应慢。
3.1.4 Max 和 Min:最大值和最小值
这两个函数实现起来最直接——每次来新数据,跟当前值比一下就行。
// Max 的简单实现
long max = Long.MIN_VALUE;
for (long value : values) {
if (value > max) {
max = value;
}
}
但有个细节:窗口过期。如果数据有生命周期,Max 和 Min 需要支持「退场」。比如一个 5 分钟的窗口,第 1 分钟的最大值在第 6 分钟就该失效了。我见过有人没处理这个,结果 Max 永远只增不减,查了半天才发现。
3.2 高级聚合:Histogram 和 Quantile
基础聚合只能看整体,Histogram 和 Quantile 能看分布。这两个才是实时监控的利器。
3.2.1 Histogram:直方图
Histogram 把数据分成多个桶(bucket),统计每个桶里的数据量。比如统计 API 响应时间:
| 桶范围 (ms) | 计数 |
|---|---|
| 0-10 | 1500 |
| 10-50 | 3200 |
| 50-100 | 800 |
| 100-500 | 200 |
| 500+ | 50 |
实现时要注意桶的划分策略:
- 线性桶:每个桶宽度相同。适合数据分布均匀的场景。
- 指数桶:桶宽度指数增长。适合长尾分布,比如响应时间。
- 自定义桶:根据业务需求手动指定边界。
3.2.2 Quantile:分位数
Quantile 告诉你「百分之多少的数据小于某个值」。比如 P99 响应时间是 200ms,意思是 99% 的请求都在 200ms 内完成。
实现 Quantile 有两种主流方法:
- 基于 Histogram:先算直方图,再根据桶的累计计数估算分位数。
- T-Digest:一种近似算法,精度高且内存可控。
我个人习惯用 T-Digest。它在高百分位(P99、P999)的精度特别好,而且内存占用固定。我在监控系统里用过,效果不错。
// T-Digest 伪代码
TDigest digest = new TDigest(100); // compression 参数控制精度
for (double value : values) {
digest.add(value);
}
double p99 = digest.quantile(0.99);
3.3 选型指南:什么时候用什么?
选型其实不难,记住这个原则:先看需求,再看资源。
| 场景 | 推荐函数 | 原因 |
|---|---|---|
| 统计请求总量 | Count | 简单高效,无状态 |
| 统计收入总额 | Sum | 注意用 long 防溢出 |
| 监控平均响应时间 | Avg | 配合滑动窗口使用 |
| 发现异常峰值 | Max | 实时告警首选 |
| 分析数据分布 | Histogram | 桶数量控制在 50 以内 |
| 评估服务质量 | Quantile (P99) | T-Digest 精度高 |
核心建议:不要一上来就上 Quantile。先跑 Count 和 Avg,等业务成熟了再上 Histogram 和 Quantile。我见过太多人一开始就搞复杂聚合,结果系统扛不住,还得回退。
3.4 知识体系总览
下面这张图把本章的核心逻辑串起来了。你一看就明白。
这张图把基础聚合和高级聚合分成了两条线。左边是 Count、Sum、Avg、Max/Min,右边是 Histogram 和 Quantile。你从根节点往下看,就知道该走哪条路了。
3.5 实战中的坑与技巧
最后分享几个我踩过的坑,希望能帮你省点时间。
- Count 的并发问题:多线程同时加 Count,记得用原子类(AtomicLong)或者加锁。我见过有人用普通 long,结果计数少了 10%。
- Sum 的精度问题:浮点数求和会有精度损失。如果对精度要求高,用 BigDecimal 或者转成 long(乘以 100 存分)。
- Histogram 的桶边界:桶边界要覆盖数据的实际范围。我曾经设了 0-100ms 的桶,结果数据全是 200ms,全进了最后一个桶,直方图完全没意义。
- Quantile 的冷启动:刚启动时数据少,Quantile 算出来不准。我建议等至少 1000 条数据后再计算。