4.3 Elasticsearch-百分比、采样、移动平均、季节分解
4.3.1 百分比(Percentiles)
在监控与告警场景里,平均值往往掩盖长尾延迟。Elasticsearch 通过percentiles聚合把整条延迟分布切成 100 份,常用 P50、P90、P99、P99.9 四档即可看清“最慢 1 % 请求”到底慢到什么程度。
语法要点
GETlatency-*/_search?size=0{"aggs":{"latency_percentiles":{"percentiles":{"field":"duration","percents":[50,90,99,99.9],"keyed":true,"tdigest":{"compression":200}// 精度 vs 内存权衡,默认 100}}}}tdigest是 ES 7.x 默认算法,单分片误差 < 1 %,内存占用与compression成正比;- 若需精确值,可在索引映射里把
duration设为histogram或scaled_float+ignore_malformed,再用percentile_ranks反查“超过某阈值的请求占比”。
实战技巧
- 做 SLA 看板时,把 P99 与 P50 的差值命名为“长尾抖动”,差值突然放大即可触发告警;
- 对比灰度版本:用
filters子聚合把流量按version字段拆成 A/B 两组,再分别算 P99,一眼看出新版本是否把尾巴拖长。
4.3.2 采样(Sampler & Diversified Sampler)
全量聚合在十亿级文档上跑 P99 会直接把 data node 打爆。ES 提供两种采样聚合,用少量数据换 95 % 的精度。
1. Sampler
"aggs":{"sample":{"sampler":{"shard_size":10000},// 每个分片最多取 1 w 条"aggs":{"significant_errors":{"significant_terms":{"field":"error.message"}}}}}- 先随机采样,再跑子聚合,显著降低内存;
- 适合日志关键词突增、错误码聚类等 exploratory 场景。
2. Diversified Sampler
在采样时再加“去重”规则,保证同一用户、同一session不会被过度代表:
"diversified_sampler":{"shard_size":5000,"field":"user_id",// 每个 user_id 最多取 1 条"max_docs_per_value":1}- 做 A/B 实验时,用该聚合抽取“独立用户”延迟,可消除重度用户带来的偏差。
4.3.3 移动平均(Moving Average & Moving Function)
时序面板里最常见的降噪手段。ES 从 2.x 起内置moving_avg管道聚合,6.4 以后又加入更灵活的moving_fn,支持自定义脚本。
1. 简单线性平滑
"moving_avg":{"buckets_path":"avg_latency","window":10,"model":"linear","minimize":false}window=10表示用前后 10 个桶做滑动窗口;model可选linear、ewma、holt_winters,后者可叠加趋势与季节性。
2. 脚本化窗口(moving_fn)
"moving_fn":{"buckets_path":"avg_latency","window":7,"script":"MovingFunctions.unweightedAvg(values)"// 也可写标准差、中位数}- 支持
values.length动态判断边缘桶,避免头尾缺值造成的陡降; - 若想写“环比上周同期”,可在脚本里用
values[values.length-8]做跨周期对比。
可视化落地
Kibana Lens 里把指标拖到 Y 轴 → 添加“移动平均”层,窗口大小直接下拉选择;或者 TSVB 里用 “Series Agg → Moving Average” 并勾选 “Treat gaps as zeros”,即可在 5 秒内完成平滑曲线。
4.3.4 季节分解(Seasonal Decompose & ML Forecast)
移动平均只能降噪,无法把趋势、周期、残差拆开。ES 官方推荐两条路线:
- 用
transform+pivot把数据按“周×小时” 24×7 矩阵重聚合,再手写脚本算同期平均,做减法拿到残差; - 直接上 Machine Learning 模块,一条 API 完成趋势+周期+异常检测。
方案 A:纯聚合手写分解
步骤:
- 用
date_histogram把 metrics 聚合成 1 h 桶; - 用
bucket_script计算“同期历史平均值”:
"seasonal_baseline":{"bucket_script":{"buckets_path":{"hist":"history_avg",// 过去 4 周同一小时平均值"real":"current_avg"},"script":"params.real - params.hist"}}- 残差 > 3 σ 即认为异常。
优点:零依赖、轻量;缺点:只能处理单周期(周),且需自己维护历史基线索引。
方案 B:ML Forecast(开箱即用)
PUT_ml/anomaly-detector/service_latency{"analysis_config":{"bucket_span":"1h","detectors":[{"function":"mean","field_name":"duration","by_field_name":"service"}],"influencers":["service","az"]},"data_description":{"time_field":"@timestamp"},"model_plot_config":{"enabled":true}}- 自动识别每日、每周、每月多重季节;
- 实时输出
lower,upper,prediction三列,残差超出边界即告警; - 支持
forecast=7d一键生成未来 7 天容量预测,直接把曲线拖进 Kibana Dashboard 即可给老板汇报。
性能调优
- 把
bucket_span设为采集周期的整数倍,避免边界抖动; - 对超大数据量启用
summary_count_field,把 1 min 原始点预聚合成 1 h 计数,模型训练提速 10 倍; - 用
calendars排除大促、节假日,否则模型会把人为峰值当成新的季节分量。
4.3.5 小结
百分比让你看到“最慢的 1 %”;采样让你用 1 % 的算力看清趋势;移动平均帮你把锯齿变平滑;季节分解则告诉你锯齿里哪些是周期、哪些是异常。四板斧组合起来,足以把 Elasticsearch 从“搜索引擎”升级成“轻量级时序分析平台”,而无需额外引入 Spark、Flink 这类重型框架。
更多技术文章见公众号: 大城市小农民