推荐用concurrenthashmap实现内存+时间窗口+限频阈值的异常计数告警方案,按异常类名分组统计、窗口内达阈值仅告警一次且异步执行,多实例场景需升级redis或prometheus方案。

在 catch 块中统计报错频次并触发告警,核心是「隔离错误计数、控制告警节奏、避免干扰主逻辑」。不能每次异常都发告警,否则会淹没运维;也不能只靠内存计数,否则重启就归零。推荐用轻量级方案:内存+时间窗口 + 限频阈值。
用 ThreadLocal 或 ConcurrentHashMap 记录错误频次
单机场景下,推荐用 ConcurrentHashMap 存储「异常类型 + 时间窗口」的计数器,避免多线程冲突。比如按异常类名(e.getClass().getName())作为 key,value 是一个带时间戳的计数对象:
- 每次 catch 到异常,先获取当前时间(如秒级时间戳),检查是否在同一个时间窗口(例如最近 60 秒)内
- 若在窗口内,计数 +1;否则重置计数并更新时间戳
- 示例 key 可设为
"NullPointerException_1717027200"(含时间戳),或更简洁地用Map<string countwindow></string>,其中CountWindow包含 count 和 lastResetTime
设定动态阈值并触发一次告警
达到阈值后只告警一次,防止刷屏。常见做法:
- 定义「60 秒内出现 5 次同类型异常」即触发告警
- 触发后记录已告警标志(如用另一个 Map 存
"NullPointerException_alerted_1717027200"),该窗口内不再重复告警 - 告警动作建议异步执行(如提交到线程池),避免阻塞业务流程
补充:落地时考虑持久化与跨进程问题
单机计数有局限。如果服务是多实例部署,需升级方案:
- 短期可接入 Redis,用
INCR+EXPIRE实现带过期的原子计数(如 key ="err:NullPointerException:20240530",ttl=60s) - 告警判断放在统一入口(如网关或监控代理),或由各实例上报指标到 Prometheus + Alertmanager
- 开发阶段可用日志关键字(如
"ALERT_TRIGGERED")配合 ELK 快速验证逻辑
代码片段示意(内存版,生产请加锁/异步/日志)
(仅示意结构,勿直接复制)
private static final ConcurrentHashMap<string errorcounter> COUNTER_MAP = new ConcurrentHashMap();
// 在 catch 块中
String errKey = e.getClass().getSimpleName();
long now = System.currentTimeMillis() / 1000; // 秒级
ErrorCounter counter = COUNTER_MAP.computeIfAbsent(errKey, ErrorCounter::new);
if (counter.tryInc(now, 60, 5)) {
alertService.asyncAlert("高频异常:" + errKey + ",60秒内已达5次");
}
</string>
其中 ErrorCounter 封装了时间窗口判断和原子计数逻辑。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











