Spring 和多个实例上的计划任务
Spring and scheduled tasks on multiple instances
我们有一个 Spring 启动应用程序,并且有计划任务。
我们想在多台服务器上部署我们的应用程序,因此会有多个应用程序实例。
如何配置Spring到运行定时任务只在指定的服务器上?
这是一个非常广泛的话题。有很多选择可以实现这一目标。
您可以将您的应用程序配置为具有多个配置文件。例如使用另一个配置文件 'cron' 。并仅在具有此配置文件的一台服务器上启动您的应用程序。因此,例如,在生产环境中,您有三台服务器(S1、S2、S3),那么您可以在 S1 上使用配置文件 prod 和 cron(-Dspring.profiles.active=prod,cron
) 运行。在 S2 和 S3 上只需使用 prod profile(-Dspring.profiles.active=prod
).
并且在代码中,您可以在调度程序 类 上使用 @Profile("cron")
。这样它只会在 cron 配置文件处于活动状态时执行
使用分布式锁。如果你的环境中有Zookeeper,你可以使用它来实现分布式锁系统。
您可以使用一些数据库 (mysql) 并创建示例代码来锁定其中一个 table 并添加一个条目。无论哪个实例获得锁,都会在该数据库中创建一个条目并执行 cron 作业。你需要
检查你的代码,如果 getLock()
成功才继续执行。 Mysql 有像 LOCK TABLES
这样的实用程序,你可以用它来摆脱并发 read/writes.
就我个人而言,选项 2 是最好的。
使用 Spring 最简单的方法是使用环境变量和值注释:
1 - 在 class:
中获取带有 Value 注释的环境变量
@Value("${TASK_ENABLED}")
private boolean taskEnabled;
2 - 检查 taskEnabled 值以执行任务:
@Scheduled(fixedDelay = 50000)
public void myTask() {
if (this.taskEnabled) {
//do stuff here...
}
}
3 - 为每个服务器设置正确的环境变量:
错误:
java -DTASK_ENABLED=0 -jar software.jar
或
正确:
java -DTASK_ENABLED=1 -jar software.jar
具有全局配置的示例 class
要使用全局配置 class,您应该告诉 spring 它是一个带有 @Component 的组件,并注释一个 set 方法以将值传递给静态字段。
1 - 使用静态字段创建配置 class:
@Component
public class AppConfiguration {
public static boolean taskEnabled;
@Value("${TASK_ENABLED}")
public void setTaskEnabled(boolean taskEnabled) {
this.taskEnabled = taskEnabled;
}
}
2 - 检查 taskEnabled 值以执行任务:
@Scheduled(fixedDelay = 50000)
public void myTask() {
if (AppConfiguration.taskEnabled) {
//do stuff here...
}
}
3 - 为每个服务器设置正确的环境变量:
错误:
java -DTASK_ENABLED=0 -jar software.jar
或
正确:
java -DTASK_ENABLED=1 -jar software.jar
Spring - ShedLock 项目专门为此而创建。
依赖关系 -
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId>
配置-
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "PT30S")
实施-
@Scheduled(cron = "0 0/15 * * * ?")
@SchedulerLock(name = "AnyUniqueName",
lockAtLeastForString = "PT5M", lockAtMostForString = "PT10M")
public void scheduledTask() {
// ...
}
此设置将确保只有一个实例应该 运行 计划任务。
如果你只想要一个特定的实例应该运行调度程序任务,
您需要配置调度程序以使用属性文件并像这样控制调度程序开关 -
@ConditionalOnProperty(
value = "scheduling.enabled", havingValue = "true", matchIfMissing = true
)
@Configuration
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "PT30S")
public class SchedulingConfig {
现在,您需要在 application.properties
文件中提供一个 属性 scheduling.enabled = true
,因为您希望 Schedular 成为 运行.
按照此 link 完成实施。
我认为您需要的帮助在另一个 post 的答案之一中。
看到这个post:
最简单的解决方案是您可以为不同的实例使用不同的属性文件。以下是步骤
- 用
@ConditionalOnProperty(prefix = "enable-scheduler", havingValue = "true")
注释您的调度程序 class
- 在属性文件中添加一个布尔值
enable-scheduler=true
- 现在,对于任何实例,请在您的属性文件中使用
enable-scheduler=true
,对于任何其他实例,请使用 enable-scheduler=false
。
示例:
@Component
@ConditionalOnProperty(prefix = "enable-scheduler", havingValue = "true")
public class AnyScheduler {
private final Logger log = LoggerFactory.getLogger(getClass());
private final AnyService service;
@Autowired
public AnyScheduler(AnyService service) {
this.service = service;
}
@Scheduled(cron = "${scheduler-cron}")
public void syncModifiedCve() {
log.info("Scheduler started. . .");
service.doTask();
}
}
最佳选择之一 - 使用带有集群的 Quartz 调度程序。
很简单,就是:
implementation("org.springframework.boot:spring-boot-starter-quartz")
并使用 spring 为 quartz 配置作业(参见 tutorial)
application.yaml 中的集群配置:
spring:
datasource: ... # define jdbc datasource
quartz:
job-store-type: jdbc # Database Mode
jdbc:
initialize-schema: never # For clustering do not initialize table structure
properties:
org.quartz:
scheduler:
instanceId: AUTO #Default hostname and timestamp generate instance ID, which can be any string, but must be the only corresponding qrtz_scheduler_state INSTANCE_NAME field for all dispatchers
#instanceName: clusteredScheduler #quartzScheduler
jobStore:
class: org.quartz.impl.jdbcjobstore.JobStoreTX #Persistence Configuration
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate #We only make database-specific proxies for databases
useProperties: true #Indicates that JDBC JobStore stores all values in JobDataMaps as strings, so more complex objects can be stored as name-value pairs rather than serialized in BLOB columns.In the long run, this is safer because you avoid serializing non-String classes to BLOB class versions.
tablePrefix: QRTZ_ #Database Table Prefix
misfireThreshold: 60000 #The number of milliseconds the dispatcher will "tolerate" a Trigger to pass its next startup time before being considered a "fire".The default value (if you do not enter this property in the configuration) is 60000 (60 seconds).
clusterCheckinInterval: 5000 #Set the frequency (in milliseconds) of this instance'checkin'* with other instances of the cluster.Affects the speed of detecting failed instances.
isClustered: true #Turn on Clustering
threadPool: #Connection Pool
class: org.quartz.simpl.SimpleThreadPool
threadCount: 10
threadPriority: 5
threadsInheritContextClassLoaderOfInitializingThread: true
注意initialize-schema: never
- 集群模式需自行初始化
查看官方脚本:https://github.com/quartz-scheduler/quartz/tree/master/quartz-core/src/main/resources/org/quartz/impl/jdbcjobstore
您可以通过 liquibase/flyway/etc 使用它,但删除 DROP ...
查询!这就是为什么在集群中我们不自动初始化模式。
见quartz docs
参见 spring boot docs quartz
参见 article with example
我们有一个 Spring 启动应用程序,并且有计划任务。
我们想在多台服务器上部署我们的应用程序,因此会有多个应用程序实例。
如何配置Spring到运行定时任务只在指定的服务器上?
这是一个非常广泛的话题。有很多选择可以实现这一目标。
您可以将您的应用程序配置为具有多个配置文件。例如使用另一个配置文件 'cron' 。并仅在具有此配置文件的一台服务器上启动您的应用程序。因此,例如,在生产环境中,您有三台服务器(S1、S2、S3),那么您可以在 S1 上使用配置文件 prod 和 cron(
-Dspring.profiles.active=prod,cron
) 运行。在 S2 和 S3 上只需使用 prod profile(-Dspring.profiles.active=prod
).并且在代码中,您可以在调度程序 类 上使用
@Profile("cron")
。这样它只会在 cron 配置文件处于活动状态时执行使用分布式锁。如果你的环境中有Zookeeper,你可以使用它来实现分布式锁系统。
您可以使用一些数据库 (mysql) 并创建示例代码来锁定其中一个 table 并添加一个条目。无论哪个实例获得锁,都会在该数据库中创建一个条目并执行 cron 作业。你需要 检查你的代码,如果
getLock()
成功才继续执行。 Mysql 有像LOCK TABLES
这样的实用程序,你可以用它来摆脱并发 read/writes.
就我个人而言,选项 2 是最好的。
使用 Spring 最简单的方法是使用环境变量和值注释:
1 - 在 class:
中获取带有 Value 注释的环境变量@Value("${TASK_ENABLED}")
private boolean taskEnabled;
2 - 检查 taskEnabled 值以执行任务:
@Scheduled(fixedDelay = 50000)
public void myTask() {
if (this.taskEnabled) {
//do stuff here...
}
}
3 - 为每个服务器设置正确的环境变量:
错误:
java -DTASK_ENABLED=0 -jar software.jar
或
正确:
java -DTASK_ENABLED=1 -jar software.jar
具有全局配置的示例 class
要使用全局配置 class,您应该告诉 spring 它是一个带有 @Component 的组件,并注释一个 set 方法以将值传递给静态字段。
1 - 使用静态字段创建配置 class:
@Component
public class AppConfiguration {
public static boolean taskEnabled;
@Value("${TASK_ENABLED}")
public void setTaskEnabled(boolean taskEnabled) {
this.taskEnabled = taskEnabled;
}
}
2 - 检查 taskEnabled 值以执行任务:
@Scheduled(fixedDelay = 50000)
public void myTask() {
if (AppConfiguration.taskEnabled) {
//do stuff here...
}
}
3 - 为每个服务器设置正确的环境变量:
错误:
java -DTASK_ENABLED=0 -jar software.jar
或
正确:
java -DTASK_ENABLED=1 -jar software.jar
Spring - ShedLock 项目专门为此而创建。
依赖关系 -
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId>
配置-
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "PT30S")
实施-
@Scheduled(cron = "0 0/15 * * * ?")
@SchedulerLock(name = "AnyUniqueName",
lockAtLeastForString = "PT5M", lockAtMostForString = "PT10M")
public void scheduledTask() {
// ...
}
此设置将确保只有一个实例应该 运行 计划任务。
如果你只想要一个特定的实例应该运行调度程序任务,
您需要配置调度程序以使用属性文件并像这样控制调度程序开关 -
@ConditionalOnProperty(
value = "scheduling.enabled", havingValue = "true", matchIfMissing = true
)
@Configuration
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "PT30S")
public class SchedulingConfig {
现在,您需要在 application.properties
文件中提供一个 属性 scheduling.enabled = true
,因为您希望 Schedular 成为 运行.
按照此 link 完成实施。
我认为您需要的帮助在另一个 post 的答案之一中。
看到这个post:
最简单的解决方案是您可以为不同的实例使用不同的属性文件。以下是步骤
- 用
@ConditionalOnProperty(prefix = "enable-scheduler", havingValue = "true")
注释您的调度程序 class
- 在属性文件中添加一个布尔值
enable-scheduler=true
- 现在,对于任何实例,请在您的属性文件中使用
enable-scheduler=true
,对于任何其他实例,请使用enable-scheduler=false
。
示例:
@Component
@ConditionalOnProperty(prefix = "enable-scheduler", havingValue = "true")
public class AnyScheduler {
private final Logger log = LoggerFactory.getLogger(getClass());
private final AnyService service;
@Autowired
public AnyScheduler(AnyService service) {
this.service = service;
}
@Scheduled(cron = "${scheduler-cron}")
public void syncModifiedCve() {
log.info("Scheduler started. . .");
service.doTask();
}
}
最佳选择之一 - 使用带有集群的 Quartz 调度程序。 很简单,就是:
implementation("org.springframework.boot:spring-boot-starter-quartz")
并使用 spring 为 quartz 配置作业(参见 tutorial)
application.yaml 中的集群配置:
spring:
datasource: ... # define jdbc datasource
quartz:
job-store-type: jdbc # Database Mode
jdbc:
initialize-schema: never # For clustering do not initialize table structure
properties:
org.quartz:
scheduler:
instanceId: AUTO #Default hostname and timestamp generate instance ID, which can be any string, but must be the only corresponding qrtz_scheduler_state INSTANCE_NAME field for all dispatchers
#instanceName: clusteredScheduler #quartzScheduler
jobStore:
class: org.quartz.impl.jdbcjobstore.JobStoreTX #Persistence Configuration
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate #We only make database-specific proxies for databases
useProperties: true #Indicates that JDBC JobStore stores all values in JobDataMaps as strings, so more complex objects can be stored as name-value pairs rather than serialized in BLOB columns.In the long run, this is safer because you avoid serializing non-String classes to BLOB class versions.
tablePrefix: QRTZ_ #Database Table Prefix
misfireThreshold: 60000 #The number of milliseconds the dispatcher will "tolerate" a Trigger to pass its next startup time before being considered a "fire".The default value (if you do not enter this property in the configuration) is 60000 (60 seconds).
clusterCheckinInterval: 5000 #Set the frequency (in milliseconds) of this instance'checkin'* with other instances of the cluster.Affects the speed of detecting failed instances.
isClustered: true #Turn on Clustering
threadPool: #Connection Pool
class: org.quartz.simpl.SimpleThreadPool
threadCount: 10
threadPriority: 5
threadsInheritContextClassLoaderOfInitializingThread: true
注意initialize-schema: never
- 集群模式需自行初始化
查看官方脚本:https://github.com/quartz-scheduler/quartz/tree/master/quartz-core/src/main/resources/org/quartz/impl/jdbcjobstore
您可以通过 liquibase/flyway/etc 使用它,但删除 DROP ...
查询!这就是为什么在集群中我们不自动初始化模式。
见quartz docs
参见 spring boot docs quartz
参见 article with example