Shell脚本告警收敛规则实战指南:从零搭建高效监控体系
📖 目录导读
告警风暴的痛点与收敛必要性
告警风暴指单位时间内系统产生的告警数量远超处理能力,导致关键事件被淹没,例如某电商平台在促销期间,单台服务器磁盘告警每5秒触发一次,同一个问题产生4320条重复告警。
收敛的核心价值:

- ✅ 降低通知噪音:聚合重复事件,减少90%以上无效告警
- ✅ 保留证据链:记录首次和最后一次发生时间,便于根因定位
- ✅ 保护下游系统:避免监控API或IM机器人被高频请求打爆
典型场景:
- 服务端口不可达(3次重试后确认)
- 同一IP的HTTP 500错误(10分钟内超过20次)
- 多节点同时故障(按机房或集群聚合)
Shell脚本实现告警收敛的核心机制
1 依赖文件作为状态存储器
Shell无法像Python/Prometheus那样维护内存状态,需借助文件系统:
- 状态文件:记录告警ID、首次时间、最新时间、聚合次数
- 锁文件:防止并发写入导致的脏数据(关键!)
2 收敛三要素
| 要素 | 作用 | Shell实现方式 |
|---|---|---|
| 聚合键 | 定义“相同事件”的规则 | 用md5sum或sha256sum对告警关键字取哈希 |
| 窗口期 | 多少秒内告警算同一事件 | date +%s计算时间差 |
| 阈值 | 触发告警的条件 | 重复次数/首次间隔时间 |
3 安全操作原则
- 所有文件操作需加
flock独占锁 - 使用临时文件(如
${STATE_DIR}/tmp.$$)避免直接修改 - 定期清理超过24小时的过期状态文件
四大经典收敛规则实现(含代码)
1 去重规则:相同告警不重复发送
适用:相同IP的SSH登录失败、同一服务状态码
#!/bin/bash
# 收敛规则:同时间戳唯一ID,10分钟内不重复发送
ALERT_ID=$(echo "$1" | md5sum | cut -d' ' -f1)
STATE_FILE="/tmp/alert_state/${ALERT_ID}"
LOCK_FILE="/tmp/alert_state/lock.${ALERT_ID}"
exec 200>"$LOCK_FILE"
flock -x 200
if [ -f "$STATE_FILE" ]; then
echo "丢弃重复告警: $1"
exit 0
fi
echo "$(date +%s)" > "$STATE_FILE"
# 发送告警逻辑...
echo "触发送告警: $1"
2 防抖规则:N次确认后再告警
适用:HTTP 5xx错误、数据库连接失败(避免网络抖动误报)
# 变量声明
ALERT_KEY="http_error_$IP"
COUNTER_FILE="/tmp/alert_counter/${ALERT_KEY}"
THRESHOLD=3
WINDOW=60 # 60秒窗口期
# 原子更新计数器
current_count=$(cat "$COUNTER_FILE" 2>/dev/null || echo 0)
((current_count++))
if (( current_count >= THRESHOLD )); then
# 重置计数器并发送告警
rm -f "$COUNTER_FILE"
send_alert "【防抖触发】${ALERT_KEY} 连续${THRESHOLD}次异常"
else
echo "$current_count" > "$COUNTER_FILE"
# 设置60秒后自动清除
(sleep $WINDOW; rm -f "$COUNTER_FILE") &
fi
3 时间窗口规则:高频告警合并
适用:CPU使用率持续超阈值、磁盘空间告警
# 聚合窗口:300秒内,同一类型的告警仅发送一次摘要
WINDOW_SEC=300
ALERT_TYPE="disk_use_over_90"
SUMMARY_FILE="/tmp/alert_summary/${ALERT_TYPE}"
# 如果是窗口内第一次告警,创建状态文件并启动计时器
if [ ! -f "$SUMMARY_FILE" ]; then
echo "1" > "$SUMMARY_FILE"
echo "首次告警" >> "$SUMMARY_FILE"
send_alert "初始告警: 磁盘使用率92%"
(sleep $WINDOW_SEC;
# 窗口结束时,读取累计次数并发送摘要
final_count=$(head -1 "$SUMMARY_FILE")
send_alert "【聚合报告】磁盘告警: 共${final_count}次"
rm -f "$SUMMARY_FILE") &
else
# 更新计数器
count=$(head -1 "$SUMMARY_FILE")
((count++))
sed -i "1s/.*/$count/" "$SUMMARY_FILE"
echo "追加告警: $(date)" >> "$SUMMARY_FILE"
fi
4 智能抑制规则:依赖服务不误报
适用:基础网络故障时,不发送上层应用告警
# 假设依赖链路:DNS -> Web Server -> DB
# 监控DNS服务,如果不可用,则抑制所有Web相关告警
if check_service_unavailable "DNS"; then
echo "DNS故障,抑制全部WEB告警"
# 创建抑制标记文件
touch /tmp/suppress/web_alert_flag
# 设置清理任务:每30秒检查一次DNS恢复
while check_service_unavailable "DNS"; do
sleep 30
done
rm -f /tmp/suppress/web_alert_flag
fi
常见问题解答(FAQ)
Q1:Shell脚本收敛规则对性能影响大吗?
A:单次磁盘读写约2-5ms,锁操作约0.1ms,如果每秒告警超过1000条,建议用Redis替代文件系统。最佳实践:在Shell脚本中加入快速路径——当ls /tmp/alert_state/ | wc -l超过1000时,直接跳过状态检查。
Q2:如何调试收敛规则?
A:启用日志模式
# 在脚本开头加入
export DEBUG_ALERT=true
# 在关键位置输出调试信息
if [ "$DEBUG_ALERT" = true ]; then
echo "[DEBUG] 聚合键: $ALERT_KEY, 窗口: $WINDOW" >> /tmp/alert_debug.log
fi
Q3:收敛规则误将不同事件聚合怎么办?
A:调整聚合键的粒度,比如只对“错误代码+IP”聚合(如500_10.0.0.1),而不是单纯对URL聚合。黄金法则:聚合键颗粒度越细,误报率越低,但收敛效果也越差。
Q4:Shell脚本和Prometheus告警规则的优劣?
A:
- Shell优势:简单直接,无需额外组件,适合轻量级监控
- Prometheus优势:内置
repeat_interval、group_interval,支持复杂数学运算(如sum by (instance))
建议:已有Prometheus环境用PromQL,新搭建统一监控可用Shell+Pushgateway。
性能优化与生产部署建议
1 文件系统优化
- 使用
tmpfs:mount -t tmpfs -o size=50M tmpfs /tmp/alert_state - 设置
noatime挂载选项,减少inode访问次数
2 并发控制方案
# 使用flock防止脚本并发执行 ( flock -x 200 # 收敛逻辑代码块 ) 200>/var/lock/alert_converge.lock
3 与外部系统集成
- 对接Prometheus:通过
textfile模式写入metricsecho "alert_converge_total{type=\"重复\"} $(wc -l < /tmp/duplicated.log)" > /var/lib/node_exporter/textfile/alert.prom - 对接Slack:在收敛脚本中调用
curl -d 'payload={"text":"聚合通知"}' <your-slack-webhook> - 对接企业微信:使用webhook机器人发送Markdown格式摘要
4 容灾与自愈
- 状态文件损坏时自动重建:
if ! head -1 "$STATE_FILE" 2>/dev/null | grep -qE '^[0-9]+$'; then rm -f "$STATE_FILE" mv "${STATE_FILE}.bak" "$STATE_FILE" 2>/dev/null || true fi - 脚本异常退出时清理临时文件:
trap "rm -f /tmp/alert_state/*.tmp" EXIT
核心总结:Shell脚本告警收敛关键在于文件锁安全+聚合键设计+窗口期阈值,从“去重-防抖-聚合-抑制”四步走,配合
tmpfs和flock即可应对90%的告警收敛场景,当每秒处理告警超百级时,建议升级到Golang或Rust实现高性能版,但Shell的灵活性和低运维成本在小规模监控中仍不可替代。