Spring scheduler + ShedLock
spring.main.allow-bean-definition-overriding: true
server:
port: 8080
spring.devtools:
add-properties: false
livereload:
enabled: false
#spring cron expression see https://riptutorial.com/spring/example/21209/cron-expression, use UTC time zone
cron:
credit: "0 0 4 * * 2-6"
commodity: "* * * * * 2-6"
gsm: "0 0 7 * * 2-6"
scheduledThreadPool:
threadNamePrefix: "lci-task-pool-"
threadCount: 10
# Could set lock time to '5S', '3M' or '0'(means no lock)
# Use 'lockAtLeastFor' to avoid concurrent running on cluster(cluster should use same clock)
# Use 'lockAtMostFor' to avoid indefinite lock time in case the machine which obtained the lock died before releasing it.
scheduler.lock:
credit:
lockAtMostFor: "3M"
lockAtLeastFor: "1M"
commodity:
lockAtMostFor: "3M"
lockAtLeastFor: "30s"
gsm:
lockAtMostFor: "3M"
lockAtLeastFor: "1M"
dependencies { implementation platform(project(':lci-core:lci-core-hive')) implementation platform(project(':lci-flow:lci-flow-credit')) implementation platform(project(':lci-flow:lci-flow-gsm')) implementation platform(project(':lci-modules:lci-modules-oauth2')) implementation "org.springframework.boot:spring-boot-starter-webflux" implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'org.springframework.boot:spring-boot-starter-data-mongodb-reactive' implementation "net.javacrumbs.shedlock:shedlock-spring:${shedlockVersion}" implementation "net.javacrumbs.shedlock:shedlock-provider-mongo:${shedlockVersion}" // Test dependencies testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'com.squareup.okhttp3:mockwebserver' testImplementation 'org.springframework.security:spring-security-test' testImplementation 'org.awaitility:awaitility' } jar { enabled = false } bootJar { enabled = true archiveFileName = "lci-app.jar" launchScript { properties "mode": "run" } } project.afterEvaluate { springBoot { buildInfo { properties { version = citi.buildVersion.get() } } } }
springBootVersion=2.3.2.RELEASE
shedlockVersion=4.15.1
package com.citi.xip.lci.app; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration; import org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration; import org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration; import org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration; import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; import org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration; /** Bootstrap entrypoint. */ @SpringBootApplication( exclude = { MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, MongoRepositoriesAutoConfiguration.class, MongoReactiveAutoConfiguration.class, MongoReactiveDataAutoConfiguration.class, MongoReactiveRepositoriesAutoConfiguration.class }) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
package com.citi.xip.lci.app.schedulers; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.scheduling.config.ScheduledTaskRegistrar; @Configuration @EnableScheduling public class SchedulerConfig implements SchedulingConfigurer { @Value("${scheduledThreadPool.threadCount}") private int threadPoolSize; @Value("${scheduledThreadPool.threadNamePrefix}") private String threadNamePrefix; @Override public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) { ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); threadPoolTaskScheduler.setPoolSize(threadPoolSize); threadPoolTaskScheduler.setThreadNamePrefix(threadNamePrefix); threadPoolTaskScheduler.initialize(); scheduledTaskRegistrar.setTaskScheduler(threadPoolTaskScheduler); } }
package com.citi.xip.lci.app.schedulers; public class SchedulerConstant { public static final String LOCK_UNTIL = "lockUntil"; public static final String ID = "_id"; public static final String SHEDLOCK_COLLECTION_NAME = "shedLock"; public static final String LOCK_NAME_SUFFIX = "SchedulerLock"; public static final String GSM_LOCK_NAME_PREFIX = "gsm"; public static final String CREDIT_LOCK_NAME_PREFIX = "credit"; public static final String COMMODITY_LOCK_NAME_PREFIX = "commodity"; }
package com.citi.xip.lci.app.schedulers; import static com.citi.xip.lci.app.schedulers.SchedulerConstant.SHEDLOCK_COLLECTION_NAME; import com.citi.xip.lci.core.base.spring.qualifiers.Quattro; import com.mongodb.client.MongoCollection; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.provider.mongo.MongoLockProvider; import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; import org.bson.Document; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.mongodb.core.MongoTemplate; @Configuration @EnableSchedulerLock(defaultLockAtMostFor = "0") public class ShedlockConfig { @Autowired @Quattro private MongoTemplate template; @Bean public LockProvider lockProvider() { MongoCollection<Document> mongo = template.getCollection(SHEDLOCK_COLLECTION_NAME); return new MongoLockProvider(mongo); } }
package com.citi.xip.lci.app.schedulers; import static com.citi.xip.lci.app.schedulers.SchedulerConstant.COMMODITY_LOCK_NAME_PREFIX; import static com.citi.xip.lci.app.schedulers.SchedulerConstant.LOCK_NAME_SUFFIX; import lombok.extern.slf4j.Slf4j; import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @Slf4j @Component public class CommodityScheduler { @Scheduled(cron = "${cron.commodity}", zone = "UTC") @SchedulerLock( name = COMMODITY_LOCK_NAME_PREFIX + LOCK_NAME_SUFFIX, lockAtMostFor = "${scheduler.lock.commodity.lockAtMostFor}", lockAtLeastFor = "${scheduler.lock.commodity.lockAtLeastFor}") public void work() { log.info("Commodity detecting job start!"); } }
package com.citi.xip.lci.app.controller; import static com.citi.xip.lci.app.schedulers.SchedulerConstant.ID; import static com.citi.xip.lci.app.schedulers.SchedulerConstant.LOCK_NAME_SUFFIX; import static com.citi.xip.lci.app.schedulers.SchedulerConstant.LOCK_UNTIL; import static com.citi.xip.lci.app.schedulers.SchedulerConstant.SHEDLOCK_COLLECTION_NAME; import static com.mongodb.client.model.Filters.eq; import static com.mongodb.client.model.Updates.combine; import static com.mongodb.client.model.Updates.set; import com.citi.xip.lci.core.base.spring.qualifiers.Quattro; import com.mongodb.client.MongoCollection; import java.time.Instant; import lombok.extern.slf4j.Slf4j; import org.bson.Document; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor; import org.springframework.scheduling.config.ScheduledTask; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @Slf4j @RestController public class SchedulerController { @Autowired org.springframework.context.ApplicationContext applicationContext; @Autowired @Quattro private MongoTemplate template; @GetMapping(path = "/scheduler/executeNow") public String execute(@RequestParam(value = "business") String business) { ScheduledAnnotationBeanPostProcessor bean = applicationContext.getBean(ScheduledAnnotationBeanPostProcessor.class); for (ScheduledTask task : bean.getScheduledTasks()) { if (task.toString().toLowerCase().contains(business.toLowerCase())) { removeLock(business); task.getTask().getRunnable().run(); return "executed"; } } return "No this business task"; } // If lockUtil > now will not execute and skip private void removeLock(String business) { MongoCollection<Document> mongo = template.getCollection(SHEDLOCK_COLLECTION_NAME); mongo.findOneAndUpdate( eq(ID, business.toLowerCase() + LOCK_NAME_SUFFIX), combine(set(LOCK_UNTIL, Instant.now()))); } }