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

Spring多实例环境下指定服务器运行定时任务的配置方法

解决Spring Boot多实例下定时任务仅在指定服务器运行的问题

在多实例部署的Spring Boot应用中避免定时任务重复执行是很常见的需求,下面分享几个我实际项目中用过的有效方案:

方案一:配置文件开关控制(最简单直接)

这种方式通过给每个实例配置不同的开关值,让指定实例启用定时任务,其他实例直接禁用。

  1. 在配置文件(比如application.yml)中添加开关配置:
# 指定运行定时任务的实例将此值设为true,其他实例设为false
task:
  scheduled:
    enabled: true
  1. 在定时任务类上添加条件注解,只有当开关开启时才初始化这个Bean:
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
@ConditionalOnProperty(
    prefix = "task.scheduled",
    name = "enabled",
    havingValue = "true",
    matchIfMissing = false // 配置不存在时默认不启用
)
public class MyScheduledTask {

    @Scheduled(cron = "0 0 1 * * ?") // 每天凌晨1点执行
    public void doDailyTask() {
        // 你的定时任务逻辑
        System.out.println("定时任务执行中...当前实例:" + System.getenv("HOSTNAME"));
    }
}

优点:实现零成本,无需依赖额外组件;缺点:需要手动维护每个实例的配置,适合实例数量少、固定指定的场景。

方案二:基于服务器标识(主机名/IP)判断

通过获取当前服务器的主机名或IP,只允许预设的服务器执行定时任务,无需修改每个实例的核心开关。

方式1:在任务方法内直接判断

import java.net.InetAddress;
import java.net.UnknownHostException;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class MyScheduledTask {

    // 允许执行的服务器主机名列表
    private static final String[] ALLOWED_HOSTS = {"prod-server-01", "prod-server-02"};

    @Scheduled(cron = "0 0 1 * * ?")
    public void doDailyTask() {
        try {
            String currentHost = InetAddress.getLocalHost().getHostName();
            // 判断当前主机是否在允许列表中
            for (String allowedHost : ALLOWED_HOSTS) {
                if (allowedHost.equals(currentHost)) {
                    // 执行任务逻辑
                    System.out.println("定时任务执行中...");
                    return;
                }
            }
            System.out.println("当前服务器[" + currentHost + "]不允许执行定时任务");
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
    }
}

方式2:自定义Condition注解(更优雅)

先写一个自定义Condition类,判断当前主机是否在允许列表:

import java.net.InetAddress;
import java.net.UnknownHostException;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class OnAllowedHostCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        try {
            String currentHost = InetAddress.getLocalHost().getHostName();
            // 从配置文件读取允许的主机列表,更灵活
            String allowedHosts = context.getEnvironment().getProperty("task.scheduled.allowed-hosts");
            if (allowedHosts == null) {
                return false;
            }
            for (String host : allowedHosts.split(",")) {
                if (host.trim().equals(currentHost)) {
                    return true;
                }
            }
            return false;
        } catch (UnknownHostException e) {
            return false;
        }
    }
}

然后在定时任务类上使用@Conditional注解:

import org.springframework.context.annotation.Conditional;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
@Conditional(OnAllowedHostCondition.class)
public class MyScheduledTask {

    @Scheduled(cron = "0 0 1 * * ?")
    public void doDailyTask() {
        System.out.println("定时任务执行中...");
    }
}

同时在配置文件中添加允许的主机列表:

task:
  scheduled:
    allowed-hosts: "prod-server-01,prod-server-02"

优点:无需单独修改每个实例配置,只需统一维护允许列表;缺点:依赖服务器主机名/IP的稳定性,若主机名变更需同步更新配置。

方案三:分布式锁(动态选主,适合无固定指定场景)

如果不想固定指定服务器,而是让任意一个实例执行(避免重复),可以用分布式锁实现。以Redis+Redisson为例:

先引入依赖:

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.23.3</version>
</dependency>

然后在定时任务中获取锁,只有拿到锁的实例才执行:

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;

@Component
public class MyScheduledTask {

    private final RedissonClient redissonClient;

    public MyScheduledTask(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    @Scheduled(cron = "0 0 1 * * ?")
    public void doDailyTask() {
        RLock lock = redissonClient.getLock("daily-scheduled-task-lock");
        try {
            // 尝试获取锁:最多等待0秒(拿不到锁直接放弃),锁持有5分钟(防止实例挂了锁不释放)
            if (lock.tryLock(0, 5, TimeUnit.MINUTES)) {
                System.out.println("获取到锁,执行定时任务...");
                // 你的任务逻辑
            } else {
                System.out.println("其他实例正在执行任务,本次跳过");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

优点:无需指定固定服务器,自动选主,适合实例动态扩容缩容的场景;缺点:需要依赖Redis等中间件,增加了系统复杂度。

方案四:使用分布式任务调度框架(适合复杂场景)

如果你的定时任务逻辑复杂,或者需要更精细的调度控制(比如任务分片、动态路由),可以使用专门的分布式任务调度框架,比如XXL-Job、Elastic-Job等。这些框架原生支持任务路由到指定节点执行,能完美解决多实例下的任务调度问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 06:33:46