JBoss7集群部署无EJB的Spring EAR应用定时任务防重复执行方案咨询
针对你在JBoss 7集群环境下遇到的定时任务重复执行问题,我有几个非常贴合你技术栈的解决方案,帮你实现集群内仅单个实例执行每日调度的需求:
首先要明确:你原来给processor()加的synchronized关键字没用——这只是单个JVM内的线程同步,而你的应用跑在两个独立的JBoss实例上,属于不同的JVM,所以必须用集群级别的锁或者单例控制机制。
下面是具体方案,按推荐程度排序:
方案一:Spring集成Quartz集群模式(最推荐)
Quartz本身支持集群部署,通过共享数据库实现任务调度的分布式锁,确保同一个定时任务只会在集群中的一个节点执行。而且Spring和Quartz的集成非常成熟,完全契合你的技术栈。
步骤:
- 引入依赖:如果用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>
- 配置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>
- 创建Quartz数据库表:从Quartz官方提供的SQL脚本(比如MySQL的
tables_mysql.sql)中选择对应数据库的版本,在共享数据库中执行,创建Quartz需要的锁表和任务表。
这个方案的优势是:不依赖JBoss特定API,跨容器兼容,成熟稳定,即使后续更换应用服务器也无需大幅改动。
方案二:利用JBoss 7的HA Singleton服务(轻量无额外依赖)
如果你不想引入Quartz,可以直接用JBoss 7自带的HA Singleton特性——让集群中只有一个节点激活你的定时任务服务,即使该节点故障,另一个节点会自动接管。
步骤:
- 编写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"); }
- 注册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) { // 可选:清理资源 } }
- 配置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>
- 注册Listener:在
web.xml中添加这个Listener:
<listener> <listener-class>com.yourpackage.SingletonServiceRegistrar</listener-class> </listener>
这个方案的优势是:无需引入额外依赖,完全利用JBoss自带功能,轻量高效,但缺点是依赖JBoss特定API,后续更换容器需要调整。
总结
如果你希望方案通用、稳定,优先选择Quartz集群模式;如果想尽量减少依赖,利用JBoss原生能力,就选HA Singleton服务方案。两种方案都能彻底解决你当前的重复执行问题。
内容的提问来源于stack exchange,提问作者araraujo
相关产品推荐
相关产品推荐

