Spring Boot 定时任务从入门到精通

小七学习网,助您升职加薪,遇问题可联系:客服微信【1601371900】 备注:来自网站

定时任务是一个比较常用的模块,在本文中,你将会看到如下内容: 什么是定时任务 如何使用单线程定时任务 如何使用多线程定时任务 如何使用分布式定时任务 定时任务常用框架 小结 什么是定时任务 定时任务是…

定时任务是一个比较常用的模块,在本文中,你将会看到如下内容:

  1. 什么是定时任务
  2. 如何使用单线程定时任务
  3. 如何使用多线程定时任务
  4. 如何使用分布式定时任务
  5. 定时任务常用框架
  6. 小结


什么是定时任务

定时任务是按照指定时候周期运行的短任务。使用场景为某个固定的时间点,为所有运行节点的时间做同步。

定时任务是基于时间控制的短任务。类似于 Linux 系统中的 crontab 文件中的一行。在指定时间周期运行的短时任务。

  • 在给定的时间运行一次
  • 在给定的时间周期运行

Spring Boot 定时任务

package com.accord.task;import java.text.SimpleDateFormat;import java.util.Date;import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Component;/** * 从配置文件加载任务信息 */@Componentpublic class ScheduledTask {  private static final SimpleDateFormat dateFormat = new SimpleDateFormat(\"HH:mm:ss\");  //@Scheduled(fixedDelayString = \"${jobs.fixedDelay}\")  @Scheduled(fixedDelayString = \"2000\")  public void getTask1() {    System.out.println(\"任务 1,从配置文件加载任务信息,当前时间:\" + dateFormat.format(new Date()));  }  @Scheduled(cron = \"${jobs.cron}\")  public void getTask2() {    System.out.println(\"任务 2,从配置文件加载任务信息,当前时间:\" + dateFormat.format(new Date()));  }}

application.properties 文件如下所示:

jobs.fixedDelay=5000jobs.cron=0/5 * *  * * ?

启动文件如下所示:

package com.accord;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.scheduling.annotation.EnableScheduling;@SpringBootApplication@EnableSchedulingpublic class SpringBootCron2Application {    public static void main(String[] args) {        SpringApplication.run(SpringBootCron2Application.class, args);    }}

单线程定时任务

创建定时任务

package com.accord.task;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Component;/** * 构建执行定时任务 * TODO */@Componentpublic class ScheduledTask2 {    private Logger logger = LoggerFactory.getLogger(ScheduledTask2.class);    private int fixedDelayCount = 1;    private int fixedRateCount = 1;    private int initialDelayCount = 1;    private int cronCount = 1;    @Scheduled(fixedDelay = 5000)        //fixedDelay = 5000 表示当前方法执行完毕 5000ms 后,Spring scheduling 会再次调用该方法    public void testFixDelay() {        logger.info(\"===fixedDelay: 第{}次执行方法\", fixedDelayCount++);    }    @Scheduled(fixedRate = 5000)        //fixedRate = 5000 表示当前方法开始执行 5000ms 后,Spring scheduling 会再次调用该方法    public void testFixedRate() {        logger.info(\"===fixedRate: 第{}次执行方法\", fixedRateCount++);    }    @Scheduled(initialDelay = 1000, fixedRate = 5000)   //initialDelay = 1000 表示延迟 1000ms 执行第一次任务    public void testInitialDelay() {        logger.info(\"===initialDelay: 第{}次执行方法\", initialDelayCount++);    }    @Scheduled(cron = \"0 0/1 * * * ?\")  //cron 接受 cron 表达式,根据 cron 表达式确定定时规则    public void testCron() {        logger.info(\"===initialDelay: 第{}次执行方法\", cronCount++);    }}

使用 @Scheduled 来创建定时任务,支持多个参数:

  • cron:cron 表达式
  • fixedDelay:表示上一次任务执行完成以后,等多久再执行
  • frixedDelayString:和 fixedDelay 一样
  • fixedRate:表示按一定频率执行任务
  • fixedRateString:和 fixedRate 一样
  • initialDelay:表示延迟多久再执行一次任务
  • initialDelayString:和 initialDelay 一样
  • zone:时区

开启定时任务

package com.accord;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.scheduling.annotation.EnableScheduling;@SpringBootApplication@EnableSchedulingpublic class SpringBootCron2Application {    public static void main(String[] args) {        SpringApplication.run(SpringBootCron2Application.class, args);    }}

这里的 @EnableScheduling 注解,作用是任务交给后台执行。

执行结果:

在这里插入图片描述

多线程定时任务

实现 SchedulingConfigurer 接口,重写 configureTasks 方法:

package com.accord.task;import org.springframework.context.annotation.Configuration;import org.springframework.scheduling.annotation.SchedulingConfigurer;import org.springframework.scheduling.config.ScheduledTaskRegistrar;import java.util.concurrent.Executors;/** * 多线程执行定时任务 */@Configuration//所有的定时任务都放在一个线程池中,定时任务启动时使用不同都线程。public class ScheduleConfig implements SchedulingConfigurer {    @Override    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {        //设定一个长度 10 的定时任务线程池        taskRegistrar.setScheduler(Executors.newScheduledThreadPool(10));    }}

执行结果:

在这里插入图片描述

分布式定时任务

什么是分布式定时任务

分布式定时任务,是把分散的可靠性差的任务,纳入平台进行管理,实现集群管理,分布式部署的一种方式。

为什么采用分布式定时任务

  • 功能简单,交互性差,任务部署效率低,开发维护成本比较高。不能很好的满足系统的要求。
  • 许多任务都是单机部署。
  • 任务跟踪和告警难以实现。

优势如下:

  • 降低开发和维护成本。
  • 保证了系统的高可用,伸缩性,负载均衡,提高了容错。
  • 方便灵活高效。
  • 可以持久化到数据库。

如何设计一个定时任务

分时方案:

在这里插入图片描述

HA 高可用方案:

在这里插入图片描述

多路心跳方案:

在这里插入图片描述

任务抢占方案:

在这里插入图片描述

任务轮询或任务轮询 + 抢占排队方案:

在这里插入图片描述

定时任务常用框架

这里使用的版本为 Spring 4.x + Quartz 2.2.x。

如何在 Spring 中集成 Quartz 集群

基于 Maven 项目,需要在 pom.xml 引入的 j 依赖为:

<dependency>            <groupId>org.quartz-scheduler</groupId>            <artifactId>quartz</artifactId></dependency>

Quartz 集群的基本配置信息:

#调度标识名 集群中每一个实例都必须使用相同的名称org.quartz.scheduler.instanceName: DefaultQuartzScheduler#远程管理相关的配置,全部关闭org.quartz.scheduler.rmi.export: falseorg.quartz.scheduler.rmi.proxy: falseorg.quartz.scheduler.wrapJobExecutionInUserTransaction: falseThreadPool 实现的类名org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool#线程数量org.quartz.threadPool.threadCount: 10#线程优先级 org.quartz.threadPool.threadPriority: 5#自创建父线程org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true#容许的最大作业延org.quartz.jobStore.misfireThreshold: 60000#ID 设置为自动获取 每一个必须不同 org.quartz.scheduler.instanceId: AUTO#数据保存方式为持久化org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX#加入集群org.quartz.jobStore.isClustered: true#调度实例失效的检查时间间隔org.quartz.jobStore.clusterCheckinInterval: 10000

在项目中加入 Quartz 的初始化信息:

<?xml version=\"1.0\" encoding=\"UTF-8\"?>  <beans       xmlns=\"http://www.springframework.org/schema/beans\"    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"     xmlns:mvc=\"http://www.springframework.org/schema/mvc\"    xmlns:aop=\"http://www.springframework.org/schema/aop\"xmlns:context=\"http://www.springframework.org/schema/context\"    xsi:schemaLocation=\"http://www.springframework.org/schema/beans        http://www.springframework.org/schema/beans/spring-beans.xsd       http://www.springframework.org/schema/mvc       http://www.springframework.org/schema/mvc/spring-mvc.xsd        http://www.springframework.org/schema/aop        http://www.springframework.org/schema/aop/spring-aop.xsd       http://www.springframework.org/schema/context        http://www.springframework.org/schema/context/spring-context.xsd\">       <bean id=\"quartzScheduler\"        class=\"org.springframework.scheduling.quartz.SchedulerFactoryBean\">            <!-- 自定义的 bean 注入类,解决 job 里面无法注入 spring 的 service 的问题 -->            <property name=\"jobFactory\">                <bean class=\"com.fc.sales.control.statistics.job.SpringBeanJobFactory\" />            </property>            <!-- quartz 的数据源 -->            <property name=\"dataSource\" ref=\"quartz\" />            <!-- quartz 的基本配置信息引入 -->            <property name=\"configLocation\" value=\"classpath:quartz.properties\"/><!-- 调度标识名 -->            <property name=\"schedulerName\" value=\"DefaultQuartzScheduler\" />            <!--必须的,QuartzScheduler 延时启动,应用启动完后 QuartzScheduler 再启动 -->            <property name=\"startupDelay\" value=\"30\" />            <!-- 通过 applicationContextSchedulerContextKey 属性配置 spring 上下文 -->            <property name=\"applicationContextSchedulerContextKey\" value=\"applicationContextKey\" />            <!--可选,QuartzScheduler 启动时更新己存在的 Job,这样就不用每次修改 targetObject 后删除 qrtz_job_details 表对应记录了 -->            <property name=\"overwriteExistingJobs\" value=\"true\" />            <!-- 设置自动启动 -->            <property name=\"autoStartup\" value=\"true\" />            <!-- 注册触发器 -->            <property  name=\"triggers\">                <list>                    <ref bean=\"orderSyncScannerTrigger\" />                </list>            </property>            <!-- 注册 jobDetail -->            <property name=\"jobDetails\">                <list>                    <ref bean=\"orderSyncDetail\" />                </list>            </property>        </bean>          <!--配置调度具体执行的方法-->          <bean id=\"orderSyncDetail\"              class=\"org.springframework.scheduling.quartz.JobDetailFactoryBean\">             <property name=\"jobClass\" value=\"com.fc.sales.control.statistics.job.OrderSyncJob\"/>            <property name=\"durability\" value=\"true\" />                <property name=\"requestsRecovery\" value=\"true\" />         </bean>         <!--配置调度执行的触发的时间-->    <bean id=\"orderSyncScannerTrigger\" class=\"org.springframework.scheduling.quartz.CronTriggerFactoryBean\">              <property name=\"jobDetail\" ref=\"orderSyncDetail\" />              <property name=\"cronExpression\">                  <!-- 每天上午 00:30 点执行任务调度 -->                  <value>0 30 00 * * ?</value>              </property>          </bean>

在 web.xml 启动项中加入 spring-quartz.xml 文件:

  <servlet>        <servlet-name>dispatcherServlet</servlet-name>        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>        <init-param>            <param-name>contextConfigLocation</param-name>            <param-value>classpath:/spring/spring-quartz.xml</param-value>        </init-param>        <load-on-startup>1</load-on-startup>  </servlet>

Quartz 分布式定时任务的暂停和恢复

创建一个我们自己 job 的实体类 ScheduleJob:

public class ScheduleJob {           private String jobNo; //任务编号            private String jobName; //任务名称            private String jobGroup; //任务所属组            private String desc; //任务描述                  private String jobStatus; //任务状态            private String cronExpression; //任务对应的时间表达式        private String triggerName; //触发器名称         //此处省略 get 和 set 方法  }

创建 QuartzImplService 服务层:

import org.quartz.*;import org.quartz.impl.matchers.GroupMatcher;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Service;import javax.annotation.Resource;import java.util.*;/** * quartz_job 的工具类 */@Servicepublic class QuartzUtils {    private final Logger logger = LoggerFactory.getLogger(QuartzUtils.class);    @Resource    private Scheduler scheduler;    /**     *     * 获取计划任务列表     * @return  List<ScheduleJob>     */    public List<ScheduleJob> getPlanJobList() throws SchedulerException{        List<ScheduleJob> jobList = new ArrayList<>();        GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();        Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);;        jobKeys = scheduler.getJobKeys(matcher);        for (JobKey jobKey : jobKeys) {            List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);            for (Trigger trigger : triggers) {                ScheduleJob job = new ScheduleJob();                job.setJobName(jobKey.getName());                job.setJobGroup(jobKey.getGroup());                // 此处是我自己业务需要,给每个定时任务配置类对应的编号和描述                String value = PropertiesUtils.getStringCN(jobKey.getName());                if(null != value && !\"\".equals(value)){                    job.setJobNo(value.split(\"/\")[0]);                    job.setDesc(value.split(\"/\")[1]);                }else{                    job.setJobNo(\"0000\");                    job.setDesc(\"未监控任务\");                }                job.setTriggerName(\"触发器:\" + trigger.getKey());                Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());                job.setJobStatus(triggerState.name());                if (trigger instanceof CronTrigger) {                    CronTrigger cronTrigger = (CronTrigger) trigger;                    String cronExpression = cronTrigger.getCronExpression();                    job.setCronExpression(cronExpression);                }                jobList.add(job);            }        }      // 对返回的定时任务安装编号做排序        Collections.sort(jobList,new Comparator<ScheduleJob>(){            public int compare(ScheduleJob arg0, ScheduleJob arg1) {                return arg0.getJobNo().compareTo(arg1.getJobNo());            }        });        return jobList;    }    /**     * 获取正在运行的任务列表     * @return List<ScheduleJob>     */    public List<ScheduleJob> getCurrentJobList() throws SchedulerException{        List<JobExecutionContext> executingJobs = scheduler.getCurrentlyExecutingJobs();;        List<ScheduleJob> jobList = new ArrayList<ScheduleJob>(executingJobs.size());;        for (JobExecutionContext executingJob : executingJobs) {            ScheduleJob job = new ScheduleJob();            JobDetail jobDetail = executingJob.getJobDetail();            JobKey jobKey = jobDetail.getKey();            Trigger trigger = executingJob.getTrigger();            job.setJobName(jobKey.getName());            job.setJobGroup(jobKey.getGroup());            String value = PropertiesUtils.getStringCN(jobKey.getName());            if(null != value && !\"\".equals(value)){                job.setJobNo(value.split(\"/\")[0]);                job.setDesc(value.split(\"/\")[1]);            }else{                job.setJobNo(\"0000\");                job.setDesc(\"未监控任务\");            }            job.setTriggerName(\"触发器:\" + trigger.getKey());            Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());            job.setJobStatus(triggerState.name());            if (trigger instanceof CronTrigger) {                CronTrigger cronTrigger = (CronTrigger) trigger;                String cronExpression = cronTrigger.getCronExpression();                job.setCronExpression(cronExpression);            }            jobList.add(job);        }        Collections.sort(jobList,new Comparator<ScheduleJob>(){            public int compare(ScheduleJob arg0, ScheduleJob arg1) {                return arg0.getJobNo().compareTo(arg1.getJobNo());            }        });        return  jobList;    }    /**     * 暂停当前任务     * @param scheduleJob     */    public void pauseJob(ScheduleJob scheduleJob) throws SchedulerException{        JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());        if(scheduler.checkExists(jobKey)){            scheduler.pauseJob(jobKey);        }    }    /**     * 恢复当前任务     * @param scheduleJob     */    public void resumeJob(ScheduleJob scheduleJob) throws SchedulerException{        JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());        if(scheduler.checkExists(jobKey)){            //并恢复            scheduler.resumeJob(jobKey);            //重置当前时间            this.rescheduleJob(scheduleJob);        }    }    /**     * 删除任务     * @param scheduleJob     * @return boolean     */    public boolean deleteJob(ScheduleJob scheduleJob) throws SchedulerException{        JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());        if(scheduler.checkExists(jobKey)){            return scheduler.deleteJob(jobKey);        }        return false;    }    /**     * 立即触发当前任务     * @param scheduleJob     */    public void triggerJob(ScheduleJob scheduleJob) throws SchedulerException{        JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());        if(scheduler.checkExists(jobKey)){            scheduler.triggerJob(jobKey);        }    }    /**     * 更新任务的时间表达式     * @param scheduleJob     * @return Date     */    public Date rescheduleJob(ScheduleJob scheduleJob) throws SchedulerException{        TriggerKey triggerKey = TriggerKey.triggerKey(scheduleJob.getJobName(),                scheduleJob.getJobGroup());        if(scheduler.checkExists(triggerKey)){            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(scheduleJob                    .getCronExpression());            //按新的 cronExpression 表达式重新构建 trigger            trigger = trigger.getTriggerBuilder().withIdentity(triggerKey)                    .withSchedule(scheduleBuilder).build();            //按新的 trigger 重新设置 job 执行            return scheduler.rescheduleJob(triggerKey, trigger);        }        return null;    }    /**     * 查询其中一个任务的状态     * @param scheduleJob     * @return     * @throws SchedulerException     */    public String scheduleJob(ScheduleJob scheduleJob) throws SchedulerException {        String status = null;        TriggerKey triggerKey = TriggerKey.triggerKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());        CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);        if (null != trigger) {            Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());            status = triggerState.name();        }        return status;    }    /**     * 校验 job 是否已经加载     * @param scheduleJob  JOB 基本信息参数     * @return          是否已经加载     */    public boolean checkJobExisted(ScheduleJob scheduleJob) throws SchedulerException {        return scheduler.checkExists(new JobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup()));    }    private String getStatuDesc(String status){        if(status.equalsIgnoreCase(\"NORMAL\")){            return \"正常\";        }else if(status.equalsIgnoreCase(\"PAUSED\")){            return \"暂停\";        }else{            return \"异常\";        }    }}

提供控制层:

import com.innmall.hotelmanager.common.Result;import com.innmall.hotelmanager.service.quartz.QuartzUtils;import com.innmall.hotelmanager.service.quartz.ScheduleJob;import org.quartz.SchedulerException;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;import java.util.List;@RestController@RequestMapping(value = {\"/v1/job\"})public class QuartzController {    private Logger logger = LoggerFactory.getLogger(this.getClass());    @Resource    private QuartzUtils quartzUtils;    //获取定时任务的列表    @RequestMapping(value = {\"/getJobList\"})    public Result getPlanJobList(String openId){        //QuartzUtils quartzUtils = new QuartzUtils();        List<ScheduleJob> list = null;        try {            list = quartzUtils.getPlanJobList();        } catch (SchedulerException e) {            e.printStackTrace();        }        return Result.success(list);    }    //暂停任务    @RequestMapping(value = {\"/pauseJob\"})    public Result pauseJob(String openId){        //QuartzUtils quartzUtils = new QuartzUtils();        ScheduleJob job = new ScheduleJob();        job.setJobGroup(\"innmall_job\");        job.setJobName(\"refreshWxToKenJobDetail\");        try {            quartzUtils.pauseJob(job);        } catch (SchedulerException e) {            e.printStackTrace();        }        return Result.success(\"暂停成功\");    }    //恢复任务    @RequestMapping(value = {\"/resumeJob\"})    public Result resumeJob(String openId){        //QuartzUtils quartzUtils = new QuartzUtils();        ScheduleJob job = new ScheduleJob();        job.setJobGroup(\"innmall_job\");        job.setJobName(\"refreshWxToKenJobDetail\");        try {            quartzUtils.resumeJob(job);        } catch (SchedulerException e) {            e.printStackTrace();        }        return Result.success(\"恢复成功\");    }    //立即触发任务    @RequestMapping(value = {\"/triggerJob\"})    public Result triggerJob(String openId){        //QuartzUtils quartzUtils = new QuartzUtils();        ScheduleJob job = new ScheduleJob();        job.setJobGroup(\"innmall_job\");        job.setJobName(\"refreshWxToKenJobDetail\");        try {            quartzUtils.triggerJob(job);        } catch (SchedulerException e) {            e.printStackTrace();        }        return Result.success(\"触发成功\");    }    //删除任务    @RequestMapping(value = {\"/deleteJob\"})    public Result deleteJob(String openId){        //QuartzUtils quartzUtils = new QuartzUtils();        ScheduleJob job = new ScheduleJob();        job.setJobGroup(\"innmall_job\");        job.setJobName(\"refreshWxToKenJobDetail\");        try {            quartzUtils.deleteJob(job);        } catch (SchedulerException e) {            e.printStackTrace();        }        return Result.success(\"触发成功\");    }}

总结

在本文中,介绍了什么是定时任务,单线程定时任务,多线程定时任务,使用分布式定时任务,定时任务的常用的框架。

小七学习网,助您升职加薪,遇问题可联系:客服微信【1601371900】 备注:来自网站

免责声明: 1、本站信息来自网络,版权争议与本站无关 2、本站所有主题由该帖子作者发表,该帖子作者与本站享有帖子相关版权 3、其他单位或个人使用、转载或引用本文时必须同时征得该帖子作者和本站的同意 4、本帖部分内容转载自其它媒体,但并不代表本站赞同其观点和对其真实性负责 5、用户所发布的一切软件的解密分析文章仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。 6、您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。 7、请支持正版软件、得到更好的正版服务。 8、如有侵权请立即告知本站(邮箱:1099252741@qq.com,备用微信:1099252741),本站将及时予与删除 9、本站所发布的一切破解补丁、注册机和注册信息及软件的解密分析文章和视频仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。