隨著業務增長,單機部署無法滿足高併發需求,遷移至叢集後,原有定時任務在每台節點重複執行,導致資料重複處理、資源浪費。早期採用「人工分片」偽分散式方案,節點故障時任務中斷,嚴重影響業務連續性。
Quartz 透過資料庫鎖機制(QRTZ_LOCKS 表)確保同一時刻只有一個節點執行特定任務。核心配置:
org.quartz.jobStore.isClustered = trueorg.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTXorg.quartz.scheduler.instanceId = AUTO(自動生成節點 ID)<beans ...>
<context:component-scan base-package="com.example.job"/>
<!-- 資料源 (JNDI) -->
<bean name="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="jdbc/quartz"/>
</bean>
<!-- 事務管理 -->
<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 執行緒池 -->
<bean name="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="15"/>
<property name="maxPoolSize" value="25"/>
<property name="queueCapacity" value="100"/>
</bean>
<!-- SchedulerFactoryBean -->
<bean name="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="configLocation" value="classpath:quartz.properties"/>
<property name="dataSource" ref="dataSource"/>
<property name="transactionManager" ref="transactionManager"/>
<property name="schedulerName" value="baseScheduler"/>
<property name="overwriteExistingJobs" value="true"/>
<property name="applicationContextSchedulerContextKey" value="applicationContext"/>
<property name="jobFactory">
<bean class="com.example.autowired.AutowiringSpringBeanJobFactory"/>
</property>
<property name="triggers">
<list><ref bean="sampleTrigger"/></list>
</property>
<property name="jobDetails">
<list><ref bean="sampleJobDetail"/></list>
</property>
<property name="taskExecutor" ref="taskExecutor"/>
</bean>
<!-- JobDetail -->
<bean name="sampleJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass" value="com.example.job.SampleJob"/>
<property name="durability" value="true"/>
<property name="requestsRecovery" value="false"/>
</bean>
<!-- CronTrigger -->
<bean name="sampleTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="sampleJobDetail"/>
<property name="cronExpression" value="0/10 * * * * ?"/>
<property name="timeZone" value="GMT+8:00"/>
</bean>
</beans>
# JobStore 叢集配置
org.quartz.jobStore.useProperties=true
org.quartz.jobStore.tablePrefix=QRTZ_
org.quartz.jobStore.isClustered=true
org.quartz.jobStore.clusterCheckinInterval=5000
org.quartz.jobStore.misfireThreshold=60000
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
# Scheduler
org.quartz.scheduler.instanceId=AUTO
org.quartz.scheduler.instanceName=MY_CLUSTERED_SCHEDULER
# ThreadPool
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount=10
org.quartz.threadPool.threadPriority=5
public class AutowiringSpringBeanJobFactory
extends SpringBeanJobFactory implements ApplicationContextAware {
private AutowireCapableBeanFactory beanFactory;
@Override
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
beanFactory = ctx.getAutowireCapableBeanFactory();
}
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
Object job = super.createJobInstance(bundle);
beanFactory.autowireBean(job);
return job;
}
}
@Component
public class SampleJob extends QuartzJobBean {
private static final Log LOG = LogFactory.getLog(SampleJob.class);
@Autowired
private BusinessService businessService;
@Override
protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException {
LOG.info("任務開始: " + new Date());
businessService.doWork();
LOG.info("任務結束: " + new Date());
}
}
驗證結果證明:同一排程任務在叢集中任意時刻僅由單一節點執行,具備故障轉移能力。
QRTZ_FIRED_TRIGGERS 表檢測任務積壓requestsRecovery=true 確保重啟後補償