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

JBoss7集群部署无EJB的Spring EAR应用定时任务防重复执行方案咨询

针对你在JBoss 7集群环境下遇到的定时任务重复执行问题,我有几个非常贴合你技术栈的解决方案,帮你实现集群内仅单个实例执行每日调度的需求:

首先要明确:你原来给processor()加的synchronized关键字没用——这只是单个JVM内的线程同步,而你的应用跑在两个独立的JBoss实例上,属于不同的JVM,所以必须用集群级别的锁或者单例控制机制。

下面是具体方案,按推荐程度排序:

方案一:Spring集成Quartz集群模式(最推荐)

Quartz本身支持集群部署,通过共享数据库实现任务调度的分布式锁,确保同一个定时任务只会在集群中的一个节点执行。而且Spring和Quartz的集成非常成熟,完全契合你的技术栈。

步骤:

  1. 引入依赖:如果用Maven,添加Spring Quartz和Quartz的依赖(根据你的Spring版本调整):
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.3.2</version>
</dependency>
  1. 配置Quartz集群:在Spring配置文件中设置Quartz使用JDBC JobStore,指向集群共享的数据库:
<!-- 共享数据库数据源 -->
<bean id="sharedDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://your-shared-db-host:3306/quartz_db"/>
    <property name="username" value="db_user"/>
    <property name="password" value="db_pass"/>
</bean>

<!-- Quartz调度器配置 -->
<bean id="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="dataSource" ref="sharedDataSource"/>
    <property name="quartzProperties">
        <props>
            <!-- 集群调度器名称 -->
            <prop key="org.quartz.scheduler.instanceName">ClusterDailyProcessorScheduler</prop>
            <!-- 实例ID自动生成,确保集群内唯一 -->
            <prop key="org.quartz.scheduler.instanceId">AUTO</prop>
            <!-- 使用JDBC存储任务和触发器 -->
            <prop key="org.quartz.jobStore.class">org.quartz.impl.jdbcjobstore.JobStoreTX</prop>
            <!-- 数据库适配类,根据你的数据库选择 -->
            <prop key="org.quartz.jobStore.driverDelegateClass">org.quartz.impl.jdbcjobstore.StdJDBCDelegate</prop>
            <!-- Quartz表前缀,对应数据库中创建的表 -->
            <prop key="org.quartz.jobStore.tablePrefix">QRTZ_</prop>
            <!-- 开启集群模式 -->
            <prop key="org.quartz.jobStore.isClustered">true</prop>
            <!-- 集群节点心跳间隔,单位毫秒 -->
            <prop key="org.quartz.jobStore.clusterCheckinInterval">20000</prop>
        </props>
    </property>
    <!-- 关联触发器 -->
    <property name="triggers">
        <list>
            <ref bean="dailyProcessorTrigger"/>
        </list>
    </property>
</bean>

<!-- 任务详情:绑定你的processor方法 -->
<bean id="processorJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    <property name="targetObject" ref="yourBeanWithProcessor"/> <!-- 替换为你实际的Bean -->
    <property name="targetMethod" value="processor"/>
    <!-- 禁止同一任务在同一节点并发执行 -->
    <property name="concurrent" value="false"/>
</bean>

<!-- 每日执行触发器:比如每天凌晨1点执行 -->
<bean id="dailyProcessorTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
    <property name="jobDetail" ref="processorJobDetail"/>
    <property name="cronExpression" value="0 0 1 * * ?"/> <!-- 调整为你需要的执行时间 -->
</bean>
  1. 创建Quartz数据库表:从Quartz官方提供的SQL脚本(比如MySQL的tables_mysql.sql)中选择对应数据库的版本,在共享数据库中执行,创建Quartz需要的锁表和任务表。

这个方案的优势是:不依赖JBoss特定API,跨容器兼容,成熟稳定,即使后续更换应用服务器也无需大幅改动。

方案二:利用JBoss 7的HA Singleton服务(轻量无额外依赖)

如果你不想引入Quartz,可以直接用JBoss 7自带的HA Singleton特性——让集群中只有一个节点激活你的定时任务服务,即使该节点故障,另一个节点会自动接管。

步骤:

  1. 编写Singleton服务类:实现JBoss MSC的Service接口,在服务启动时初始化定时任务:
import org.jboss.msc.service.Service;
import org.jboss.msc.service.StartContext;
import org.jboss.msc.service.StartException;
import org.jboss.msc.service.StopContext;
import org.springframework.scheduling.concurrent.ScheduledExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;

public class ProcessorSingletonService implements Service<Void> {

    private final YourBeanWithProcessor targetBean;
    private ScheduledExecutorService scheduler;

    public ProcessorSingletonService(YourBeanWithProcessor targetBean) {
        this.targetBean = targetBean;
    }

    @Override
    public void start(StartContext startContext) throws StartException {
        // 计算从当前时间到下一次执行(比如每天凌晨1点)的延迟时间
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime nextRun = now.withHour(1).withMinute(0).withSecond(0);
        if (now.isAfter(nextRun)) {
            nextRun = nextRun.plusDays(1);
        }
        long initialDelay = ChronoUnit.MILLIS.between(now, nextRun);

        // 初始化调度器,每24小时执行一次
        scheduler = Executors.newSingleThreadScheduledExecutor();
        scheduler.scheduleAtFixedRate(targetBean::processor, initialDelay, 24 * 60 * 60 * 1000, TimeUnit.MILLISECONDS);
        startContext.getLogger().info("Daily processor scheduler started on this node");
    }

    @Override
    public void stop(StopContext stopContext) {
        if (scheduler != null) {
            scheduler.shutdownNow();
        }
        stopContext.getLogger().info("Daily processor scheduler stopped");
    }

    public static final org.jboss.msc.service.ServiceName SERVICE_NAME = 
        org.jboss.msc.service.ServiceName.of("your-app", "processor", "singleton-service");
}
  1. 注册Singleton服务:通过ServletContextListener在应用启动时向JBoss注册该服务,并标记为HA Singleton:
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.jboss.msc.service.ServiceController;
import org.jboss.msc.service.ServiceRegistry;
import org.jboss.msc.service.ServiceTarget;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

public class SingletonServiceRegistrar implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        // 获取Spring上下文和目标Bean
        WebApplicationContext springCtx = WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext());
        YourBeanWithProcessor targetBean = springCtx.getBean(YourBeanWithProcessor.class);

        // 获取JBoss的服务注册器和目标对象
        ServiceRegistry serviceRegistry = (ServiceRegistry) sce.getServletContext().getAttribute("org.jboss.as.server.ServiceRegistry");
        ServiceTarget serviceTarget = (ServiceTarget) sce.getServletContext().getAttribute("org.jboss.as.server.ServiceTarget");

        // 注册HA Singleton服务
        serviceTarget.addService(ProcessorSingletonService.SERVICE_NAME, new ProcessorSingletonService(targetBean))
                .setInitialMode(ServiceController.Mode.ACTIVE)
                .setSingleton(true) // 标记为集群单例
                .install();
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        // 可选:清理资源
    }
}
  1. 配置JBoss模块依赖:在EAR的META-INF/jboss-deployment-structure.xml中添加所需的JBoss模块依赖:
<jboss-deployment-structure>
    <deployment>
        <dependencies>
            <module name="org.jboss.msc"/>
            <module name="org.jboss.as.server"/>
        </dependencies>
    </deployment>
</jboss-deployment-structure>
  1. 注册Listener:在web.xml中添加这个Listener:
<listener>
    <listener-class>com.yourpackage.SingletonServiceRegistrar</listener-class>
</listener>

这个方案的优势是:无需引入额外依赖,完全利用JBoss自带功能,轻量高效,但缺点是依赖JBoss特定API,后续更换容器需要调整。

总结

如果你希望方案通用、稳定,优先选择Quartz集群模式;如果想尽量减少依赖,利用JBoss原生能力,就选HA Singleton服务方案。两种方案都能彻底解决你当前的重复执行问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 08:06:27