You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot多Nginx服务器下避免定时任务重复执行方案咨询

问题解答

@Transactional能不能解决重复执行问题?

不能。你当前的逻辑是先查询当日报表是否存在,再执行生成逻辑。就算给addDailyReports加上@Transactional,默认事务隔离级别下,两台服务器的并发请求会同时读到count=0的快照(快照读),进而都执行报表生成逻辑,最终还是会产生重复数据。事务只能保证单节点内的操作原子性,解决不了跨节点的并发竞争问题。

可行的解决方案

1. 数据库悲观锁优化查询逻辑

给查询语句加上排他锁,让第一个执行的事务锁住相关数据,后续事务必须等待前一个事务完成后才能查询,避免并发判断的问题。修改你的查询代码:

// 替换原有查询方法,新增带排他锁的查询
@Query(value = "SELECT COUNT(*) FROM your_table WHERE report_date = ?1 AND report_frequency = ?2 FOR UPDATE", nativeQuery = true)
Long getCountByReportdateAndReporeFrequencyForUpdate(LocalDate reportDate, String frequency);

@Override
@Transactional
public String addDailyReports(LocalDate date, Long branchCode) {
    LocalDate reportDate = (date != null) ? date : LocalDate.now();
    
    // 使用加锁的查询方法
    Long count = branchUserMakerRepos.getCountByReportdateAndReporeFrequencyForUpdate(reportDate, "Daily");
    if (count > 0) {
        return count + " reports exist for the report date " + reportDate;
    }
    
    // 报表生成逻辑...
}

注意要给addDailyReports加上@Transactional,确保锁的范围覆盖整个查询+生成逻辑。

2. 分布式锁

用Redis或ZooKeeper实现分布式锁,只有获取到锁的服务器才能执行定时任务,其他节点直接跳过。以Redis为例,执行任务前先尝试获取锁:

public void runDailyTask() {
    String lockKey = "daily-report-generate-lock";
    // 用SETNX命令加锁,设置过期时间防止锁泄漏
    Boolean lockAcquired = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "locked", Duration.ofMinutes(30));
    
    if (Boolean.TRUE.equals(lockAcquired)) {
        try {
            branchUserService.addDailyReports(null, null);
        } finally {
            // 任务完成后释放锁
            stringRedisTemplate.delete(lockKey);
        }
    } else {
        // 未获取到锁,跳过当前执行
        log.info("Another server is generating daily reports, skip current execution");
    }
}

这种方法适合跨节点的并发控制,要注意处理锁过期和异常场景,避免死锁。

3. 改用集中式定时任务框架

放弃@Scheduled的本地定时,改用Quartz、XXL-Job、Elastic-Job这类分布式定时任务框架。这些框架原生支持分布式调度,能保证同一时间只有一个节点执行任务,无需自己处理并发逻辑,比如XXL-Job可直接在后台配置任务执行策略。

4. 节点选主机制

让集群自动选举出一个主节点,仅主节点执行定时任务,备节点不执行。可以通过数据库表实现简单选主:

  • 创建节点心跳表,记录节点ID、心跳时间、是否为主节点
  • 每个节点定时更新自己的心跳时间
  • 节点启动或定时检查时,判断是否存在存活的主节点,若没有则竞选为主节点
  • 定时任务仅在主节点上触发执行

内容的提问来源于stack exchange,提问作者Ravi Dobariya

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 22:45:09