上一篇,咱们介绍了如何使用Spring Boot自带的@Scheduled
注解实现定时任务。文末也说起了这种方式的局限性。当在集群环境下的时候,若是任务的执行或操做依赖一些共享资源的话,就会存在竞争关系。若是不引入分布式锁等机制来作调度的话,就可能出现预料以外的执行结果。因此,@Scheduled
注解更偏向于使用在单实例自身维护相关的一些定时任务上会更为合理一些,好比:定时清理服务实例某个目录下的文件、定时上传本实例的一些统计数据等。html
那么,在实际实现业务逻辑的时候,没有更好的定时任务方案呢?今天咱们就来介绍一个老牌的分布式定时任务框架,在Spring Boot下的使用案例。java
Elastic Job的前生是当当开源的一款分布式任务调度框架,而目前已经加入到了Apache基金会。git
该项目下有两个分支:ElasticJob-Lite和ElasticJob-Cloud。ElasticJob-Lite是一个轻量级的任务管理方案,本文接下来的案例就用这个来实现。而
ElasticJob-Cloud则相对重一些,由于它使用容器来管理任务和隔离资源。github
更多关于ElasticJob的介绍,您也能够点击这里直达官方网站了解更多信息。spring
说那么多,一块儿动手试试吧!apache
第一步:建立一个最基础的Spring Boot项目,若是还不会?那么看看这篇快速入门。框架
第二步:pom.xml
中添加elasticjob-lite的starter分布式
<dependencies> <dependency> <groupId>org.apache.shardingsphere.elasticjob</groupId> <artifactId>elasticjob-lite-spring-boot-starter</artifactId> <version>3.0.0</version> </dependency> // ... </dependencies>
第三步:建立一个简单任务ide
@Slf4j @Service public class MySimpleJob implements SimpleJob { @Override public void execute(ShardingContext context) { log.info("MySimpleJob start : didispace.com {}", System.currentTimeMillis()); } }
第四步:编辑配置文件spring-boot
elasticjob.reg-center.server-lists=localhost:2181 elasticjob.reg-center.namespace=didispace elasticjob.jobs.my-simple-job.elastic-job-class=com.didispace.chapter72.MySimpleJob elasticjob.jobs.my-simple-job.cron=0/5 * * * * ? elasticjob.jobs.my-simple-job.sharding-total-count=1
这里主要有两个部分:
第一部分:elasticjob.reg-center
开头的,主要配置elastic job的注册中心和namespace
第二部分:任务配置,以elasticjob.jobs
开头,这里的my-simple-job
是任务的名称,根据你的喜爱命名便可,但不要重复。任务的下的配置elastic-job-class
是任务的实现类,cron
是执行规则表达式,sharding-total-count
是任务分片的总数。咱们能够经过这个参数来把任务切分,实现并行处理。这里先设置为1,后面咱们另外讲分片的使用。
完成了上面全部操做时候,咱们能够尝试运行一下上面应用,由于这里须要用到ZooKeeper来协调分布式环境下的任务调度。因此,你须要先在本地安装ZooKeeper,而后启动它。注意:上面elasticjob.reg-center.server-lists
配置,根据你实际使用的ZooKeeper地址和端口作相应修改。
在启动上述Spring Boot应用以后,咱们能够看到以下日志输出:
2021-07-20 15:33:39.541 INFO 56365 --- [ main] org.quartz.impl.StdSchedulerFactory : Quartz scheduler 'my-simple-job' initialized from an externally provided properties instance. 2021-07-20 15:33:39.541 INFO 56365 --- [ main] org.quartz.impl.StdSchedulerFactory : Quartz scheduler version: 2.3.2 2021-07-20 15:33:39.551 INFO 56365 --- [ main] org.apache.curator.utils.Compatibility : Using org.apache.zookeeper.server.quorum.MultipleAddresses 2021-07-20 15:33:40.067 INFO 56365 --- [ main] c.d.chapter72.Chapter72Application : Started Chapter72Application in 3.25 seconds (JVM running for 4.965) 2021-07-20 15:33:40.069 INFO 56365 --- [ main] .s.b.j.ScheduleJobBootstrapStartupRunner : Starting ElasticJob Bootstrap. 2021-07-20 15:33:40.078 INFO 56365 --- [ main] org.quartz.core.QuartzScheduler : Scheduler my-simple-job_$_NON_CLUSTERED started. 2021-07-20 15:33:40.078 INFO 56365 --- [ main] .s.b.j.ScheduleJobBootstrapStartupRunner : ElasticJob Bootstrap started. 2021-07-20 15:33:45.157 INFO 56365 --- [le-job_Worker-1] com.didispace.chapter72.MySimpleJob : MySimpleJob start : didispace.com 1626766425157 2021-07-20 15:33:50.010 INFO 56365 --- [le-job_Worker-1] com.didispace.chapter72.MySimpleJob : MySimpleJob start : didispace.com 1626766430010 2021-07-20 15:33:55.013 INFO 56365 --- [le-job_Worker-1] com.didispace.chapter72.MySimpleJob : MySimpleJob start : didispace.com 1626766435013
既然是分布式任务调度,那么咱们再启动一个(注意,在同一台机器启动的时候,会端口冲突,能够在启动命令中加入-Dserver.port=8081
来区分端口),在第二个启动的服务日志也打印了相似的内容
2021-07-20 15:34:06.430 INFO 56371 --- [ main] org.quartz.impl.StdSchedulerFactory : Quartz scheduler 'my-simple-job' initialized from an externally provided properties instance. 2021-07-20 15:34:06.430 INFO 56371 --- [ main] org.quartz.impl.StdSchedulerFactory : Quartz scheduler version: 2.3.2 2021-07-20 15:34:06.436 INFO 56371 --- [ main] org.apache.curator.utils.Compatibility : Using org.apache.zookeeper.server.quorum.MultipleAddresses 2021-07-20 15:34:06.786 INFO 56371 --- [ main] c.d.chapter72.Chapter72Application : Started Chapter72Application in 1.446 seconds (JVM running for 1.884) 2021-07-20 15:34:06.787 INFO 56371 --- [ main] .s.b.j.ScheduleJobBootstrapStartupRunner : Starting ElasticJob Bootstrap. 2021-07-20 15:34:06.792 INFO 56371 --- [ main] org.quartz.core.QuartzScheduler : Scheduler my-simple-job_$_NON_CLUSTERED started. 2021-07-20 15:34:06.792 INFO 56371 --- [ main] .s.b.j.ScheduleJobBootstrapStartupRunner : ElasticJob Bootstrap started. 2021-07-20 15:34:10.182 INFO 56371 --- [le-job_Worker-1] com.didispace.chapter72.MySimpleJob : MySimpleJob start : didispace.com 1626766450182 2021-07-20 15:34:15.010 INFO 56371 --- [le-job_Worker-1] com.didispace.chapter72.MySimpleJob : MySimpleJob start : didispace.com 1626766455010 2021-07-20 15:34:20.013 INFO 56371 --- [le-job_Worker-1] com.didispace.chapter72.MySimpleJob : MySimpleJob start : didispace.com 1626766460013
此时,在回头看看以前第一个启动的应用,日志输出中止了。因为咱们设置了分片总数为1,因此这个任务启动以后,只会有一个实例接管执行。这样就避免了多个进行同时重复的执行相同逻辑而产生问题的状况。同时,这样也支持了任务执行的高可用。好比:能够尝试把第二个启动的应用(正在打印日志的)终止掉。能够发现,第一个启动的应用(以前已经中止输出日志)继续开始打印任务日志了。
在整个实现过程当中,咱们并无本身手工的去编写任何的分布式锁等代码去实现任务调度逻辑,只须要关注任务逻辑自己,而后经过配置分片的方式来控制任务的分割,就能够轻松的实现分布式集群环境下的定时任务管理了。是否是在复杂场景下,这种方式实现起来要比@Scheduled
更方便呢?
记得本身动手写一写,这样体会更深哦!若是碰到问题,能够拉取文末的代码示例对比一下是否有地方配置不同。下一篇,咱们还将继续介绍关于定时任务的一些高级内容。若是您对这个内容感兴趣,能够收藏本系列教程《Spring Boot 2.x基础教程》点击直达!。学习过程当中如遇困难,能够加入咱们的Spring技术交流群,参与交流与讨论,更好的学习与进步!
本文的完整工程能够查看下面仓库中的chapter7-2
目录:
若是您以为本文不错,欢迎Star
支持,您的关注是我坚持的动力!
欢迎关注个人公众号:程序猿DD,分享外面看不到的干货与思考!