ECharts 实现日志量异常检测:基线 ±Kσ 基带 + 灵敏度切换(Vue2 实战)
实现日志量异常检测基线 ±Kσ 基带 灵敏度切换Vue2 实战前言为什么固定阈值不好用做日志监控的同学大多配置过这样的告警日志量超过 5000 条/分钟就报警。上线第一天就会发现业务流量本身是有周期性的——白天高、夜里低、午间有个小高峰。固定阈值要么在白天业务高峰时疯狂误报要么在深夜把真正的异常漏掉。一、数据模型先约定数据结构后面所有逻辑都围绕它展开{labels:[10:00,10:05,...],// 时间轴5 分钟粒度raw:[2015,1892,2130,...],// 原始值每分钟日志量baseline:[1938,1951,1907,...],// 基线历史同期规律sigma:60// 波动标准差}生产环境中baseline和sigma一般由后端或 Flink 任务按历史同期数据计算好返回前端拿到后只做一件事constkthis.sensitivity// 1 / 2 / 3constupperbaseline.map(bbk*sigma)// 基带上限constlowerbaseline.map(bMath.max(0,b-k*sigma))// 下限防止为负K 越小基带越窄、报警越灵敏K 越大基带越宽、越保守。这就是灵敏度调节的全部原理。二、基带怎么画堆叠面积ECharts 没有原生的置信区间带标准做法是用两个堆叠的 line 系列第一个系列画lower线条和面积全部透明——它只是垫底第二个系列画upper - lower差值叠上去之后它的面积就正好落在 lower 和 upper 之间。series:[// 垫底的 lower不可见、不响应鼠标{name:基带下限,type:line,stack:band,data:lower,symbol:none,silent:true,lineStyle:{opacity:0},areaStyle:{opacity:0}},// 差值 (upper - lower)半透明蓝色就是基带{name:基带,type:line,stack:band,data:upper.map((u,i)u-lower[i]),symbol:none,silent:true,lineStyle:{opacity:0},areaStyle:{color:rgba(37, 99, 235, 0.10)}},// 基线浅蓝虚线{name:基线,type:line,data:baseline,smooth:true,symbol:none,lineStyle:{color:#93c5fd,type:dashed,width:2}},// 原始值蓝色实线{name:原始值,type:line,data:raw,smooth:true,symbol:none,lineStyle:{color:#2563eb,width:2},z:5}]三、异常点遍历 scatter 标红异常判定本身只有一行v upper[i] || v lower[i]。把所有越带点收集成[下标, 值]数组交给散点图constanomalyPoints[]raw.forEach((v,i){if(vupper[i]||vlower[i])anomalyPoints.push([i,v])})// 追加到 series{name:异常点,type:scatter,data:anomalyPoints,// [[54, 208], [55, 195], ...]symbolSize:8,itemStyle:{color:#dc2626},z:10}注意 scatter 的 data 用[index, value]二元组它会自动对齐 category 轴的下标不需要自己拼时间字符串。四、悬浮提示越基带时标红tooltip.formatter里可以拿到dataIndex反查四个数组就能同时展示原始值、基线、上下限并按是否越带切换颜色tooltip:{trigger:axis,formatter:(ps){constidxps[0].dataIndexconstvraw[idx]constoutvupper[idx]||vlower[idx]constflagout? span stylecolor:#dc2626;font-weight:700越基带/span:returndiv stylefont-size:12px;line-height:1.8 div stylefont-weight:600${labels[idx]}${flag}/div div stylecolor:${out?#dc2626:#1e293b}原始值${v.toLocaleString()}/div div基线${baseline[idx].toLocaleString()}/div div基带上限${upper[idx].toLocaleString()}/div div基带下限${lower[idx].toLocaleString()}/div /div}}五、灵敏度切换一个变量驱动全部重算把灵敏度 K 放在data里radio 切换时重新调用渲染函数即可。顶部统计卡越基带点数、最新状态用 computed 联动computed:{overviewStats(){constkthis.sensitivityconstnraw.lengthlettotal0,max0,outCount0raw.forEach((v,i){totalvif(vmax)maxvif(Math.abs(v-baseline[i])k*sigma)outCount})constcurrentraw[n-1]constlastOutMath.abs(current-baseline[n-1])k*sigmareturn{total,avg:Math.round(total/n),max,current,outCount,status:lastOut?越基带:正常}}}同一个 K既控制图表上基带的宽窄也控制统计口径两边天然一致。六、几个踩坑点1. 下限为负会拉长 y 轴。日志量不可能为负lower计算时务必Math.max(0, b - k * sigma)否则灵敏 ±1σ 模式下 y 轴会出现一大段无意义的负值区域。2. 切换指标时旧系列残留。从日志量切到QPS时 series 数量可能变化比如异常点从 10 个变 0 个要用chart.setOption(option, true)开启 notMerge否则上一次的散点会残留在图上。3. 垫底系列要silent: true。堆叠基带的 lower 系列如果响应鼠标tooltip 里会多出一条基带下限干扰信息设为 silent 一了百了。4. 记得 dispose。单页应用里切走路由前chart.dispose()并移除window.resize监听否则内存泄漏 重复初始化警告。5. 数据缓存。演示/调试时如果用随机数造数据每次重算 sensitivity 都重新 generate 会导致曲线抖动按指标缓存一份数据切换 K 值时只重算带和异常点不重新造数。七、演示数据怎么造没有后端时用余弦周期 三角分布噪声 尾部骤降可以造出非常接近真实流量的曲线for(leti0;i66;i){// 基线周期性波动12 个点一个周期 ≈ 1 小时constbbase-amp*Math.cos((2*Math.PI*i)/12)baseline.push(Math.round(b))// 三角分布噪声三个均匀随机数之和中间值概率高更像真实噪声constnoise(Math.random()Math.random()Math.random()-1.5)/1.5letvbnoise*sigmaif(i54)vanomalyValuenoise*sigma*0.3// 14:30 起骤降raw.push(Math.max(0,Math.round(v)))}三个Math.random()相加再归一化得到的是近似正态的三角分布比单一Math.random()的均匀噪声自然得多。结语整套实现的核心其实就三件事堆叠面积画基带、scatter 标异常点、一个 K 值驱动全部重算。剩下的都是 ECharts 的常规配置。建议把它做成独立的图表组件基线计算交给后端按历史同期上周同时段、昨的同时段滚动计算前端只负责渲染和灵敏度交互职责会很清晰。你们的基线是用历史同期算的还是滑动平均遇到周期性业务比如整点抢购时有没有更稳的基线算法欢迎评论区聊聊。