应使用 CallerRunsPolicy 而非 AbortPolicy 或 DiscardPolicy,避免队列满时静默丢弃审计任务;需检查线程池配置、日志对账、数据库时间戳及监控指标(queueSize、rejectedTasks),并配合熔断与同步失败日志保障可观测性。

直接看线程池拒绝策略是否用了 AbortPolicy 或 DiscardPolicy——这两类策略会在队列满时静默丢弃任务,导致审计日志不落库、关键访问轨迹消失,业务链路在可观测性层面直接“断层”。真正安全的兜底方式是 CallerRunsPolicy,它让主线程自己执行一次审计,慢但不丢。
查线程池配置是否埋了丢数据隐患
重点检查你声明的审计专用线程池 Bean(比如 @Bean("auditTaskExecutor"))中是否显式或隐式启用了危险策略:
- 没指定
setRejectedExecutionHandler?默认就是AbortPolicy,队列满直接抛RejectedExecutionException,而如果上层没捕获,异常就吞了,审计任务彻底消失 - 写了
new ThreadPoolExecutor.DiscardPolicy()?这是最隐蔽的“静默丢弃”,连日志都不打,任务被删得干干净净 - 用了
DiscardOldestPolicy?虽然保留了最新任务,但会把队列头的老任务踢掉——而老任务可能正是某次高风险操作(如权限变更、资金调拨)的审计事件
验证审计方法是否真正在执行
不能只看有没有加 @Async 注解,要确认调用是否实际进入线程池并完成落地:
- 在审计方法入口加一行日志,例如
log.info("Audit started: {}", event.getTraceId()),然后比对主流程日志和这条日志是否成对出现;若主流程有 100 次调用,审计日志只有 87 条,说明有 13 次被策略丢弃 - 检查数据库审计表的写入时间戳与主流程时间戳的偏移。如果某批请求在 14:02:01 发起,但审计表里最后一条记录停在 14:02:00,且之后再无新增,极可能是线程池已饱和、新任务被拒
- 禁用异步(临时改为同步调用),观察审计是否全部补全。若补全了,基本锁定是异步执行环节出了问题
看监控指标是否暴露队列积压
光靠日志不够,必须依赖可观测指标及时发现“即将断层”的前兆:
- 采集线程池的
queueSize和activeCount,当queueSize长期 > 80% 队列容量(如设了 500,持续 > 400),说明消费跟不上生产,再涨一点就会触发拒绝策略 - 监控
rejectedTasks累计值,只要这个数字非零增长,就代表已有审计事件丢失,必须立即干预 - 结合 TraceID 查链路:从网关日志提取一批失败/缺失审计的请求 TraceID,在全链路系统里搜索,若发现调用链在
@Async方法处戛然而止、无后续 span,基本可判定任务未执行
改用 CallerRunsPolicy 并加轻量熔断
这不是权宜之计,而是生产环境审计模块的底线配置:
- 把拒绝策略明确设为
new ThreadPoolExecutor.CallerRunsPolicy(),确保极端情况下由主线程兜底执行,宁可接口响应慢 20ms,也不能丢一条审计记录 - 给审计方法加简单耗时判断:若单次执行超过 500ms,主动打 warn 日志并上报指标
audit_slow_count,提示你该优化 SQL 或加索引,而不是等队列爆掉 - 数据库写入失败时,不要重试或忽略——审计失败本身就要记日志(用同步方式),否则你连“为什么丢”都查不到










