fix: 完善分布式调度恢复与批量触发

- 持久化 Quartz refire 状态并收口运行时启动关闭顺序

- 增加批量 Trigger 获取配置、校验与回归测试
This commit is contained in:
2026-08-31 14:56:41 +08:00
parent 93296eb810
commit 2d50f7de15
15 changed files with 735 additions and 18 deletions

View File

@@ -53,6 +53,11 @@
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>

View File

@@ -18,6 +18,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitialization;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -61,6 +62,7 @@ public class EasyAgentsSchedulerAutoConfiguration {
*/
@Bean(name = SCHEDULER_BEAN_NAME, initMethod = "start", destroyMethod = "close")
@ConditionalOnMissingBean(ScheduleService.class)
@DependsOnDatabaseInitialization
public QuartzScheduleService easyAgentsQuartzScheduleService(
EasyAgentsSchedulerProperties properties,
ListableBeanFactory beanFactory,
@@ -83,6 +85,8 @@ public class EasyAgentsSchedulerAutoConfiguration {
quartz.isClustered(),
quartz.getThreadCount(),
quartz.getThreadPriority(),
quartz.getBatchTriggerAcquisitionMaxCount(),
quartz.getBatchTriggerAcquisitionFireAheadTimeWindowMillis(),
quartz.getClusterCheckinIntervalMillis(),
quartz.getMisfireThresholdMillis(),
quartz.isWaitForJobsToCompleteOnShutdown(),

View File

@@ -104,6 +104,12 @@ public class EasyAgentsSchedulerProperties {
/** Quartz Worker 线程优先级。 */
private int threadPriority = Thread.NORM_PRIORITY;
/** 单次批量获取 Trigger 的最大数量。 */
private int batchTriggerAcquisitionMaxCount = 1;
/** 可提前纳入批量的时间窗口,单位毫秒。 */
private long batchTriggerAcquisitionFireAheadTimeWindowMillis;
/** 集群心跳间隔,单位毫秒。 */
private long clusterCheckinIntervalMillis = 15_000L;
@@ -251,6 +257,45 @@ public class EasyAgentsSchedulerProperties {
this.threadPriority = threadPriority;
}
/**
* 返回单次批量获取 Trigger 的最大数量。
*
* @return 批量上限
*/
public int getBatchTriggerAcquisitionMaxCount() {
return batchTriggerAcquisitionMaxCount;
}
/**
* 设置单次批量获取 Trigger 的最大数量。
*
* @param batchTriggerAcquisitionMaxCount 批量上限
*/
public void setBatchTriggerAcquisitionMaxCount(
int batchTriggerAcquisitionMaxCount) {
this.batchTriggerAcquisitionMaxCount = batchTriggerAcquisitionMaxCount;
}
/**
* 返回可提前纳入批量的时间窗口。
*
* @return 毫秒窗口
*/
public long getBatchTriggerAcquisitionFireAheadTimeWindowMillis() {
return batchTriggerAcquisitionFireAheadTimeWindowMillis;
}
/**
* 设置可提前纳入批量的时间窗口。
*
* @param batchTriggerAcquisitionFireAheadTimeWindowMillis 毫秒窗口
*/
public void setBatchTriggerAcquisitionFireAheadTimeWindowMillis(
long batchTriggerAcquisitionFireAheadTimeWindowMillis) {
this.batchTriggerAcquisitionFireAheadTimeWindowMillis =
batchTriggerAcquisitionFireAheadTimeWindowMillis;
}
/**
* 返回集群心跳间隔。
*

View File

@@ -114,6 +114,47 @@ public class EasyAgentsSchedulerAutoConfigurationTest {
}
}
/**
* 验证调度器等待 Spring Boot 数据库脚本初始化完成后再启动。
*/
@Test
public void shouldWaitForDatabaseInitializationBeforeStartingScheduler() throws Exception {
JdbcDataSource dataSource = dataSource();
Map<String, Object> properties = enabledProperties();
properties.put("easy-agents.scheduler.data-source-bean-name", "schedulerDataSource");
properties.put("spring.sql.init.mode", "always");
properties.put(
"spring.sql.init.schema-locations",
"classpath:quartz-schema/h2-2.5.2.sql"
);
SpringApplication application = new SpringApplication(AutoDiscoveryApplication.class);
application.setWebApplicationType(WebApplicationType.NONE);
application.setDefaultProperties(properties);
application.addInitializers(applicationContext -> {
GenericApplicationContext genericContext =
(GenericApplicationContext) applicationContext;
genericContext.registerBean(
"schedulerDataSource",
DataSource.class,
() -> dataSource
);
});
try (ConfigurableApplicationContext context = application.run()) {
assertNotNull(context.getBean(ScheduleService.class));
try (
Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(
"SELECT COUNT(*) FROM QRTZ_SCHEDULER_STATE"
)
) {
assertTrue(resultSet.next());
}
}
}
/**
* 验证多个 DataSource 未明确选择时启动失败并提供可操作信息。
*/
@@ -162,6 +203,13 @@ public class EasyAgentsSchedulerAutoConfigurationTest {
properties.put("easy-agents.scheduler.quartz.instance-id", "NON_CLUSTERED");
properties.put("easy-agents.scheduler.quartz.clustered", "false");
properties.put("easy-agents.scheduler.quartz.thread-count", "2");
properties.put(
"easy-agents.scheduler.quartz.batch-trigger-acquisition-max-count", "2"
);
properties.put(
"easy-agents.scheduler.quartz.batch-trigger-acquisition-fire-ahead-time-window-millis",
"500"
);
properties.put("easy-agents.scheduler.quartz.misfire-threshold-millis", "1000");
return properties;
}