Spring Boot触发式Task Scheduler批量邮件发送改造咨询
问题
开发Spring Boot应用时,每日需给员工发送超1000封邮件,因有每小时发送数量限制,希望每小时发送99封。现有一段通过线程休眠控制发送频率的代码,想要改用Spring的Task Scheduler改写,且邮件发送需通过前端点击按钮触发(已实现POST接口),不适用固定时间运行的常规cron调度器。
原代码如下:
Runnable task = () -> { int counter = 0; long startTime = System.currentTimeMillis(); for (String emailAddress : usersEmailAddresses) { counter++; bulkEmailService.send(BulkEmailMessage.builder() .from(bulkEmailMessage.getFrom()) .subject(bulkEmailMessage.getSubject()) .content(bulkEmailMessage.getContent()) .toAddresses(emailAddress) .build()); if (counter % 99 == 0) { long endTime = System.currentTimeMillis() - startTime; try { Thread.sleep(6000 * 60 * 60 - endTime); } catch (InterruptedException e) { throw new RuntimeException(e); } startTime = System.currentTimeMillis(); } } }; taskExecutor.execute(task);
解决方案
核心思路
不再在循环内用线程休眠阻塞执行,而是将邮件地址列表按99个为一批拆分,通过Spring Task Scheduler延迟调度每一批的发送任务,每批间隔1小时(可扣除当前批次发送耗时以保证精度),整个任务链由前端触发的POST接口启动。
具体实现步骤
启用Spring Task Scheduler
在Spring Boot启动类上添加@EnableScheduling注解,开启调度功能:@SpringBootApplication @EnableScheduling public class EmailApplication { public static void main(String[] args) { SpringApplication.run(EmailApplication.class, args); } }封装批量邮件调度组件
创建独立组件处理分批逻辑与任务调度,解耦业务与控制器:@Component public class BulkEmailScheduler { private final BulkEmailService bulkEmailService; private final TaskScheduler taskScheduler; // 构造注入依赖 public BulkEmailScheduler(BulkEmailService bulkEmailService, TaskScheduler taskScheduler) { this.bulkEmailService = bulkEmailService; this.taskScheduler = taskScheduler; } // 接口触发的入口方法 public void startBulkSend(List<String> emailAddresses, BulkEmailMessage template) { List<List<String>> batches = splitIntoBatches(emailAddresses, 99); if (batches.isEmpty()) return; long delayMs = 0; // 第一批立即执行 for (List<String> batch : batches) { // 调度当前批次发送任务 taskScheduler.schedule( () -> sendBatch(batch, template), new Date(System.currentTimeMillis() + delayMs) ); // 下一批延迟1小时(3600000毫秒) delayMs += 3600000; } } // 拆分列表为指定大小的批次 private List<List<String>> splitIntoBatches(List<String> list, int batchSize) { List<List<String>> batches = new ArrayList<>(); for (int i = 0; i < list.size(); i += batchSize) { int endIndex = Math.min(i + batchSize, list.size()); batches.add(list.subList(i, endIndex)); } return batches; } // 发送单批次邮件 private void sendBatch(List<String> batch, BulkEmailMessage template) { long batchStartTime = System.currentTimeMillis(); for (String email : batch) { bulkEmailService.send(BulkEmailMessage.builder() .from(template.getFrom()) .subject(template.getSubject()) .content(template.getContent()) .toAddresses(email) .build()); } // 可选:记录批次耗时,用于后续调整延迟精度 long costMs = System.currentTimeMillis() - batchStartTime; System.out.println("批次发送完成,耗时:" + costMs + "毫秒"); } }在Controller中触发调度
在已实现的POST接口中调用调度组件的启动方法:@RestController @RequestMapping("/emails") public class EmailController { private final BulkEmailScheduler emailScheduler; public EmailController(BulkEmailScheduler emailScheduler) { this.emailScheduler = emailScheduler; } @PostMapping("/bulk-trigger") public ResponseEntity<String> triggerBulkEmail(@RequestBody BulkEmailRequest request) { // 假设request包含邮件地址列表和模板信息 emailScheduler.startBulkSend(request.getEmailAddresses(), request.getTemplateMessage()); return ResponseEntity.ok("批量邮件任务已启动"); } }可选优化
- 精确控制间隔:若需要严格按小时窗口发送,可将下一批延迟调整为
3600000 - costMs,扣除当前批次的发送耗时。 - 自定义线程池:Spring默认TaskScheduler线程池较小,可自定义配置满足并发需求:
@Configuration public class TaskSchedulerConfig { @Bean public TaskScheduler taskScheduler() { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); scheduler.setPoolSize(5); scheduler.setThreadNamePrefix("email-batch-"); scheduler.initialize(); return scheduler; } } - 任务状态追踪:添加数据库记录,追踪各批次发送状态,方便失败重试。
- 精确控制间隔:若需要严格按小时窗口发送,可将下一批延迟调整为
优势说明
相比原代码的Thread.sleep()实现,该方案的好处:
- 避免线程长时间阻塞,更高效利用服务器线程资源。
- 任务由Spring统一管理,集成异常处理、监控更方便。
- 批次逻辑拆分清晰,代码可读性与可维护性更强。
内容的提问来源于stack exchange,提问作者Alex Grimaldo
相关产品推荐
相关产品推荐

