Spring Boot 2.x基础教程:使用Elastic Job实现定时任务

上一篇,咱们介绍了如何使用Spring Boot自带的@Scheduled注解实现定时任务。文末也说起了这种方式的局限性。当在集群环境下的时候,若是任务的执行或操做依赖一些共享资源的话,就会存在竞争关系。若是不引入分布式锁等机制来作调度的话,就可能出现预料以外的执行结果。因此,@Scheduled注解更偏向于使用在单实例自身维护相关的一些定时任务上会更为合理一些,好比:定时清理服务实例某个目录下的文件、定时上传本实例的一些统计数据等。html

那么,在实际实现业务逻辑的时候,没有更好的定时任务方案呢?今天咱们就来介绍一个老牌的分布式定时任务框架,在Spring Boot下的使用案例。java

Elasitc Job

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,分享外面看不到的干货与思考!

相关文章
相关标签/搜索