上一篇,我们介绍了如何使用Spring Boot自带的@Scheduled注解实现定时任务 。文末也提及了这种方式的局限性 。当在集群环境下的时候,如果任务的执行或操作依赖一些共享资源的话,就会存在竞争关系 。如果不引入分布式锁等机制来做调度的话,就可能出现预料之外的执行结果 。所以,@Scheduled注解更偏向于使用在单实例自身维护相关的一些定时任务上会更为合理一些,比如:定时清理服务实例某个目录下的文件、定时上传本实例的一些统计数据等 。
那么,在实际实现业务逻辑的时候,没有更好的定时任务方案呢?今天我们就来介绍一个老牌的分布式定时任务框架,在Spring Boot下的使用案例 。
Elasitc JobElastic Job的前生是当当开源的一款分布式任务调度框架,而目前已经加入到了Apache基金会 。
该项目下有两个分支:ElasticJob-Lite和ElasticJob-Cloud 。ElasticJob-Lite是一个轻量级的任务管理方案,本文接下来的案例就用这个来实现 。而
ElasticJob-Cloud则相对重一些,因为它使用容器来管理任务和隔离资源 。
更多关于ElasticJob的介绍,您也可以点击这里直达官方网站了解更多信息 。
动手试试说那么多,一起动手试试吧!
第一步:创建一个最基础的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>第三步:创建一个简单任务
@Slf4j@Servicepublic class MySimpleJob implements SimpleJob {@Overridepublic void execute(ShardingContext context) {log.info("MySimpleJob start : didispace.com {}", System.currentTimeMillis());}}第四步:编辑配置文件
elasticjob.reg-center.server-lists=localhost:2181elasticjob.reg-center.namespace=didispaceelasticjob.jobs.my-simple-job.elastic-job-class=com.didispace.chapter72.MySimpleJobelasticjob.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.541INFO 56365 --- [main] org.quartz.impl.StdSchedulerFactory: Quartz scheduler 'my-simple-job' initialized from an externally provided properties instance.2021-07-20 15:33:39.541INFO 56365 --- [main] org.quartz.impl.StdSchedulerFactory: Quartz scheduler version: 2.3.22021-07-20 15:33:39.551INFO 56365 --- [main] org.apache.curator.utils.Compatibility: Using org.apache.zookeeper.server.quorum.MultipleAddresses2021-07-20 15:33:40.067INFO 56365 --- [main] c.d.chapter72.Chapter72Application: Started Chapter72Application in 3.25 seconds (JVM running for 4.965)2021-07-20 15:33:40.069INFO 56365 --- [main] .s.b.j.ScheduleJobBootstrapStartupRunner : Starting ElasticJob Bootstrap.2021-07-20 15:33:40.078INFO 56365 --- [main] org.quartz.core.QuartzScheduler: Scheduler my-simple-job_$_NON_CLUSTERED started.2021-07-20 15:33:40.078INFO 56365 --- [main] .s.b.j.ScheduleJobBootstrapStartupRunner : ElasticJob Bootstrap started.2021-07-20 15:33:45.157INFO 56365 --- [le-job_Worker-1] com.didispace.chapter72.MySimpleJob: MySimpleJob start : didispace.com 16267664251572021-07-20 15:33:50.010INFO 56365 --- [le-job_Worker-1] com.didispace.chapter72.MySimpleJob: MySimpleJob start : didispace.com 16267664300102021-07-20 15:33:55.013INFO 56365 --- [le-job_Worker-1] com.didispace.chapter72.MySimpleJob: MySimpleJob start : didispace.com 1626766435013既然是分布式任务调度,那么我们再启动一个(注意,在同一台机器启动的时候,会端口冲突,可以在启动命令中加入-Dserver.port=8081来区分端口),在第二个启动的服务日志也打印了类似的内容
2021-07-20 15:34:06.430INFO 56371 --- [main] org.quartz.impl.StdSchedulerFactory: Quartz scheduler 'my-simple-job' initialized from an externally provided properties instance.2021-07-20 15:34:06.430INFO 56371 --- [main] org.quartz.impl.StdSchedulerFactory: Quartz scheduler version: 2.3.22021-07-20 15:34:06.436INFO 56371 --- [main] org.apache.curator.utils.Compatibility: Using org.apache.zookeeper.server.quorum.MultipleAddresses2021-07-20 15:34:06.786INFO 56371 --- [main] c.d.chapter72.Chapter72Application: Started Chapter72Application in 1.446 seconds (JVM running for 1.884)2021-07-20 15:34:06.787INFO 56371 --- [main] .s.b.j.ScheduleJobBootstrapStartupRunner : Starting ElasticJob Bootstrap.2021-07-20 15:34:06.792INFO 56371 --- [main] org.quartz.core.QuartzScheduler: Scheduler my-simple-job_$_NON_CLUSTERED started.2021-07-20 15:34:06.792INFO 56371 --- [main] .s.b.j.ScheduleJobBootstrapStartupRunner : ElasticJob Bootstrap started.2021-07-20 15:34:10.182INFO 56371 --- [le-job_Worker-1] com.didispace.chapter72.MySimpleJob: MySimpleJob start : didispace.com 16267664501822021-07-20 15:34:15.010INFO 56371 --- [le-job_Worker-1] com.didispace.chapter72.MySimpleJob: MySimpleJob start : didispace.com 16267664550102021-07-20 15:34:20.013INFO 56371 --- [le-job_Worker-1] com.didispace.chapter72.MySimpleJob: MySimpleJob start : didispace.com 1626766460013【Spring Boot 2.x基础教程:使用Elastic Job实现定时任务】此时,在回头看看之前第一个启动的应用,日志输出停止了 。由于我们设置了分片总数为1,所以这个任务启动之后,只会有一个实例接管执行 。这样就避免了多个进行同时重复的执行相同逻辑而产生问题的情况 。同时,这样也支持了任务执行的高可用 。比如:可以尝试把第二个启动的应用(正在打印日志的)终止掉 。可以发现,第一个启动的应用(之前已经停止输出日志)继续开始打印任务日志了 。
在整个实现过程中,我们并没有自己手工的去编写任何的分布式锁等代码去实现任务调度逻辑,只需要关注任务逻辑本身,然后通过配置分片的方式来控制任务的分割,就可以轻松的实现分布式集群环境下的定时任务管理了 。是不是在复杂场景下,这种方式实现起来要比@Scheduled更方便呢?
记得自己动手写一写,这样体会更深哦!如果碰到问题,可以拉取文末的代码示例对比一下是否有地方配置不一样 。下一篇,我们还将继续介绍关于定时任务的一些高级内容 。如果您对这个内容感兴趣,可以收藏本系列教程《Spring Boot 2.x基础教程》点击直达! 。学习过程中如遇困难,可以加入我们的Spring技术交流群,参与交流与讨论,更好的学习与进步!
代码示例本文的完整工程可以查看下面仓库中的chapter7-2目录:
- Github:https://github.com/dyc87112/SpringBoot-Learning/
- Gitee:https://gitee.com/didispace/SpringBoot-Learning/
Star支持,您的关注是我坚持的动力!欢迎关注我的公众号:程序猿DD,分享外面看不到的干货与思考!
- 春季老年人吃什么养肝?土豆、米饭换着吃
- 三八妇女节节日祝福分享 三八妇女节节日语录
- 老人谨慎!选好你的“第三只脚”
- 校方进行了深刻的反思 青岛一大学生坠亡校方整改校规
- 脸皮厚的人长寿!有这特征的老人最长寿
- 长寿秘诀:记住这10大妙招 100%增寿
- 春季老年人心血管病高发 3条保命要诀
- 眼睛花不花要看四十八 老年人怎样延缓老花眼
- 香槟然能防治老年痴呆症? 一天三杯它人到90不痴呆
- 老人手抖的原因 为什么老人手会抖
