线程池饱和时应将拒绝任务异步落库补偿,需确保不阻塞主线程、任务关键字段可序列化、状态可追踪;建表存储ID/内容/重试次数/状态/时间;自定义拒绝策略校验后JSON化关键信息入库,并配定时扫描+幂等重试机制。
线程池饱和时,直接丢弃任务或抛异常会导致关键业务数据丢失。把被拒绝的任务存进数据库,是最常用也最稳妥的补偿方式之一——它能保证任务不丢、可追溯、可人工干预。
关键设计原则
实现前必须明确三点:
不阻塞主线程
:落库操作不能在 rejectedExecution 方法里同步执行,否则会拖慢上游请求;建议用异步写入(如 Spring 的
)或带超时控制的连接池调用
任务要能序列化
:Runnable 对象本身不一定可序列化,推荐只提取关键字段(如订单ID、操作类型、参数JSON),避免直接存
这类不可靠字符串
状态必须可追踪
:数据库表至少包含 ID、任务内容、重试次数、当前状态(WAITING/SUCCESS/FAILED)、创建时间,方便后续定时扫描和幂等重试
数据库表结构与实体定义
建一张轻量但完整的任务表,例如 MySQL:
CREATE TABLE rejected_tasks (
id VARCHAR(36) PRIMARY KEY,
task_data TEXT NOT NULL,
retry_count INT DEFAULT 0,
status VARCHAR(20) NOT NULL DEFAULT 'WAITING',
create_time DATETIME NOT NULL
);
对应 Java 实体类需支持快速构建和 JSON 序列化,比如用 Lombok 简化:
public class RejectedTask {
private String id = UUID.randomUUID().toString();
private String taskData;
private int retryCount = 0;
private String status = "WAITING";
private LocalDateTime createTime = LocalDateTime.now();
}
自定义拒绝策略实现
核心是实现
接口,在
方法中完成安全落库:
第一行加空值校验:
用 Jackson 或 Gson 把任务关键信息转成 JSON 字符串(不要序列化整个 Runnable)
调用 DAO 层插入数据库,包裹 try-catch;失败时不抛异常,改打告警日志并记录到本地内存队列(作为二级兜底)
避免在该方法内调用
,防止死递归
配套补偿机制
光存进去还不够,得有人“干活”:
定时任务扫描
:每 30 秒查一次
且
的记录,重新提交到线程池
重试后更新状态
:成功则设为 SUCCESS;失败则
,若达上限改为 FAILED 并触发人工告警
防重复执行
:任务执行前先用乐观锁更新状态(
),只有一条能成功
@Asyncr.toString()RejectedExecutionHandlerrejectedExecutionif (r == null || executor.isShutdown()) return;executor.submit()status = 'WAITING'retry_count < 3retry_count++UPDATE ... SET status='PROCESSING' WHERE id=? AND status='WAITING'