Spring Boot如何实现支持动态cron的无限制并发定时任务调度
Spring Boot 动态可配置高并发定时任务调度实现方案
你之前调研的ThreadPoolTaskScheduler无法满足需求的核心原因是它默认将任务调度逻辑和业务执行逻辑共用同一个线程池,一旦出现长耗时任务就会占用调度线程,导致后续任务触发延迟;同时它默认使用有界队列存储待执行任务,任务量大时会出现排队等待,无法满足触发即执行的要求。
本方案采用调度层+执行层双线程池分离的架构实现,调度层只负责任务的时间触发,执行层专门处理业务逻辑,完全解耦调度和执行的资源占用。
1. 线程池配置
1.1 执行层线程池(处理业务任务)
专门用于运行业务定时任务,配置满足无排队、高并发、空闲回收的要求:
@Configuration public class DynamicTaskExecutorConfig { @Bean public ThreadPoolTaskExecutor dynamicTaskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 核心线程数,设为0即可实现无任务时空闲线程最小化 executor.setCorePoolSize(0); // 最大线程数,根据服务器资源灵活调整,支持高并发 executor.setMaxPoolSize(2048); // 空闲线程存活时间,超时自动销毁 executor.setKeepAliveSeconds(60); // 使用SynchronousQueue,无存储容量,任务提交后无队列直接分配线程执行 executor.setQueueCapacity(0); executor.setThreadNamePrefix("biz-task-"); // 拒绝策略可自定义,默认达到最大线程数时抛出异常 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); executor.initialize(); return executor; } }
1.2 调度层线程池(负责触发任务)
仅处理任务的时间触发逻辑,逻辑轻量,不需要太多线程:
@Configuration public class DynamicTaskSchedulerConfig { @Bean public ThreadPoolTaskScheduler dynamicTaskScheduler() { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); // 调度线程仅负责触发,2~4个线程足够支撑上万级任务的调度 scheduler.setPoolSize(4); scheduler.setThreadNamePrefix("scheduler-"); scheduler.setDaemon(true); scheduler.initialize(); return scheduler; } }
2. 动态任务管理封装
封装任务管理器,支持运行时动态增删改任务,适配用户可配置的需求:
2.1 任务元数据类
存储任务的核心配置信息:
@Data public class DynamicTask { // 任务唯一标识 private String taskId; // 执行任务的Spring Bean名称 private String beanName; // 执行任务的方法名 private String methodName; // 方法入参 private Object[] params; // 任务cron表达式 private String cron; // 调度任务的Future对象,用于取消任务 private ScheduledFuture<?> scheduledFuture; }
2.2 任务管理器实现
提供任务的增删改查能力:
@Component public class DynamicTaskManager { @Autowired private ThreadPoolTaskScheduler taskScheduler; @Autowired private ThreadPoolTaskExecutor taskExecutor; @Autowired private ApplicationContext applicationContext; // 线程安全的任务存储容器 private final ConcurrentHashMap<String, DynamicTask> taskCache = new ConcurrentHashMap<>(); /** * 新增定时任务 */ public void addTask(DynamicTask task) { if (taskCache.containsKey(task.getTaskId())) { throw new RuntimeException("任务ID已存在: " + task.getTaskId()); } CronTrigger trigger = new CronTrigger(task.getCron()); // 注册调度逻辑,仅负责触发任务 ScheduledFuture<?> future = taskScheduler.schedule(() -> { // 触发后将业务任务提交到执行层线程池运行 taskExecutor.execute(() -> doRunTask(task)); }, trigger); task.setScheduledFuture(future); taskCache.put(task.getTaskId(), task); } /** * 修改定时任务配置 */ public void updateTask(DynamicTask newTask) { deleteTask(newTask.getTaskId()); addTask(newTask); } /** * 删除定时任务 */ public void deleteTask(String taskId) { DynamicTask task = taskCache.remove(taskId); if (task != null && task.getScheduledFuture() != null) { // false表示不中断正在运行的任务 task.getScheduledFuture().cancel(false); } } /** * 实际执行业务任务 */ private void doRunTask(DynamicTask task) { try { Object targetBean = applicationContext.getBean(task.getBeanName()); Method targetMethod = targetBean.getClass().getMethod( task.getMethodName(), Arrays.stream(task.getParams()).map(Object::getClass).toArray(Class[]::new) ); targetMethod.invoke(targetBean, task.getParams()); } catch (Exception e) { // 自定义异常处理:记录日志、告警等 e.printStackTrace(); } } }
3. 需求适配验证
- 动态可配置:通过
DynamicTaskManager暴露的接口,终端用户在前端配置任务的执行逻辑、参数、cron表达式后,可直接调用接口完成任务的增删改,无需重启服务,完全适配开发阶段无法提前确定任务的需求。 - 无等待高并发:调度层和执行层完全解耦,调度线程不会被长耗时业务阻塞,执行层使用
SynchronousQueue无队列设计,任务触发后立即分配线程执行,只要未达到最大线程数上限,即使多个任务同时触发、运行周期重叠也可完全并行执行,适配单任务运行数分钟的场景。 - 空闲线程可控:执行层核心线程数设为0,空闲线程60秒后自动销毁,无任务运行时无多余空闲线程占用资源,核心线程数、最大线程数、存活时间均可通过配置文件灵活调整。
4. 扩展说明
如果需要实现服务重启后任务不丢失,可以将DynamicTask的核心字段持久化到数据库,服务启动时批量查询所有有效任务调用addTask方法完成注册即可。
如果需要控制单个任务的并发执行(比如上一次执行未完成时,下一次触发跳过执行),可以在doRunTask方法中增加分布式锁或本地锁控制,按需配置即可。
内容的提问来源于stack exchange,提问作者zeodtr
相关产品推荐
相关产品推荐

