精华内容
下载资源
问答
  • 我们在之前有讲过SpringBoot是已经集成了定时任务的,详见:第二十六章:SpringBoot使用@Scheduled创建定时任务,那么我们本章将会采用外置的quartz定时任务框架来完成定时任务的分布式单节点持久化,我们为什么要...
        

    定时任务在企业项目比较常用到,几乎所有的项目都会牵扯该功能模块,定时任务一般会处理指定时间点执行某一些业务逻辑、间隔时间执行某一些业务逻辑等。我们在之前有讲过SpringBoot是已经集成了定时任务的,详见:第二十六章:SpringBoot使用@Scheduled创建定时任务,那么我们本章将会采用外置的quartz定时任务框架来完成定时任务的分布式单节点持久化,我们为什么要持久化定时任务呢?

    在一些项目中定时任务可能是必不可少的,由于某种特殊的原因定时任务可能丢失,如重启定时任务服务项目后,原内存中的定时任务就会被完全释放!那对于我们来说可能是致命的问题。当然也有强制的办法解决这类问题,但是如果我们把定时任务持久化到数据库,像维护普通逻辑数据那样维护任务,就会避免项目中遇到的种种的特殊情况。

    本章目标

    基于SpringBoot架构整合定时任务框架quartz来完成分布式单节点定时任务持久化,将任务持久化到数据库,更好的预防任务丢失。

    SpringBoot 企业级核心技术学习专题


    专题 专题名称 专题描述
    001 Spring Boot 核心技术 讲解SpringBoot一些企业级层面的核心组件
    002 Spring Boot 核心技术章节源码 Spring Boot 核心技术简书每一篇文章码云对应源码
    003 Spring Cloud 核心技术 对Spring Cloud核心技术全面讲解
    004 Spring Cloud 核心技术章节源码 Spring Cloud 核心技术简书每一篇文章对应源码
    005 QueryDSL 核心技术 全面讲解QueryDSL核心技术以及基于SpringBoot整合SpringDataJPA
    006 SpringDataJPA 核心技术 全面讲解SpringDataJPA核心技术
    007 SpringBoot核心技术学习目录 SpringBoot系统的学习目录,敬请关注点赞!!!

    构建项目

    我们使用idea开发工具创建一个SpringBoot项目,pom.xml依赖配置如下所示:

    ...省略部分配置
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <java.version>1.8</java.version>
            <druid.version>1.1.5</druid.version>
            <quartz.version>2.3.0</quartz.version>
        </properties>
    
        <dependencies>
            <!--spring data jpa相关-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-jpa</artifactId>
            </dependency>
            <!--web相关依赖-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!--数据库相关依赖-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>${druid.version}</version>
            </dependency>
            <!--quartz相关依赖-->
            <dependency>
                <groupId>org.quartz-scheduler</groupId>
                <artifactId>quartz</artifactId>
                <version>${quartz.version}</version>
            </dependency>
            <dependency>
                <groupId>org.quartz-scheduler</groupId>
                <artifactId>quartz-jobs</artifactId>
                <version>${quartz.version}</version>
            </dependency>
            <!--定时任务需要依赖context模块-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context-support</artifactId>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    ...省略部分配置
    

    我们采用的是quartz官方最新版本2.3.0,新版本的任务调度框架做出了很多封装,使用也变得简易明了。
    创建初始化完成,下面我们来创建定时任务相关的Configuration配置。

    QuartzConfiguration

    quartzSpring相关框架的整合方式有很多种,我们今天采用jobDetail使用Spring Ioc托管方式来完成整合,我们可以在定时任务实例中使用Spring注入注解完成业务逻辑处理,下面我先把全部的配置贴出来再逐步分析,配置类如下所示:

    package com.hengyu.chapter39.configuration;
    
    import org.quartz.spi.JobFactory;
    import org.quartz.spi.TriggerFiredBundle;
    import org.springframework.beans.factory.annotation.Autowire;
    import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.scheduling.annotation.EnableScheduling;
    import org.springframework.scheduling.quartz.SchedulerFactoryBean;
    import org.springframework.scheduling.quartz.SpringBeanJobFactory;
    
    import javax.sql.DataSource;
    
    /**
     * quartz定时任务配置
     * ========================
     * Created with IntelliJ IDEA.
     * User:恒宇少年
     * Date:2017/11/5
     * Time:14:07
     * 码云:http://git.oschina.net/jnyqy
     * ========================
     * @author  恒宇少年
     */
    @Configuration
    @EnableScheduling
    public class QuartzConfiguration
    {
        /**
         * 继承org.springframework.scheduling.quartz.SpringBeanJobFactory
         * 实现任务实例化方式
         */
        public static class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements
                ApplicationContextAware {
    
            private transient AutowireCapableBeanFactory beanFactory;
    
            @Override
            public void setApplicationContext(final ApplicationContext context) {
                beanFactory = context.getAutowireCapableBeanFactory();
            }
    
            /**
             * 将job实例交给spring ioc托管
             * 我们在job实例实现类内可以直接使用spring注入的调用被spring ioc管理的实例
             * @param bundle
             * @return
             * @throws Exception
             */
            @Override
            protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
                final Object job = super.createJobInstance(bundle);
                /**
                 * 将job实例交付给spring ioc
                 */
                beanFactory.autowireBean(job);
                return job;
            }
        }
    
        /**
         * 配置任务工厂实例
         * @param applicationContext spring上下文实例
         * @return
         */
        @Bean
        public JobFactory jobFactory(ApplicationContext applicationContext)
        {
            /**
             * 采用自定义任务工厂 整合spring实例来完成构建任务
             * see {@link AutowiringSpringBeanJobFactory}
             */
            AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
            jobFactory.setApplicationContext(applicationContext);
            return jobFactory;
        }
    
        /**
         * 配置任务调度器
         * 使用项目数据源作为quartz数据源
         * @param jobFactory 自定义配置任务工厂
         * @param dataSource 数据源实例
         * @return
         * @throws Exception
         */
        @Bean(destroyMethod = "destroy",autowire = Autowire.NO)
        public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory, DataSource dataSource) throws Exception
        {
            SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
            //将spring管理job自定义工厂交由调度器维护
            schedulerFactoryBean.setJobFactory(jobFactory);
            //设置覆盖已存在的任务
            schedulerFactoryBean.setOverwriteExistingJobs(true);
            //项目启动完成后,等待2秒后开始执行调度器初始化
            schedulerFactoryBean.setStartupDelay(2);
            //设置调度器自动运行
            schedulerFactoryBean.setAutoStartup(true);
            //设置数据源,使用与项目统一数据源
            schedulerFactoryBean.setDataSource(dataSource);
            //设置上下文spring bean name
            schedulerFactoryBean.setApplicationContextSchedulerContextKey("applicationContext");
            //设置配置文件位置
            schedulerFactoryBean.setConfigLocation(new ClassPathResource("/quartz.properties"));
            return schedulerFactoryBean;
        }
    }
    

    AutowiringSpringBeanJobFactory

    可以看到上面配置类中,AutowiringSpringBeanJobFactory我们继承了SpringBeanJobFactory类,并且通过实现ApplicationContextAware接口获取ApplicationContext设置方法,通过外部实例化时设置ApplicationContext实例对象,在createJobInstance方法内,我们采用AutowireCapableBeanFactory来托管SpringBeanJobFactory类中createJobInstance方法返回的定时任务实例,这样我们就可以在定时任务类内使用Spring Ioc相关的注解进行注入业务逻辑实例了。

    JobFactory

    任务工厂是在本章配置调度器时所需要的实例,我们通过jobFactory方法注入ApplicationContext实例,来创建一个AutowiringSpringBeanJobFactory对象,并且将对象实例托管到Spring Ioc容器内。

    SchedulerFactoryBean

    我们本章采用的是项目内部数据源的方式来设置调度器的jobSotre,官方quartz有两种持久化的配置方案。

    第一种:采用quartz.properties配置文件配置独立的定时任务数据源,可以与使用项目的数据库完全独立。
    第二种:采用与创建项目统一个数据源,定时任务持久化相关的表与业务逻辑在同一个数据库内。

    可以根据实际的项目需求采取不同的方案,我们本章主要是通过第二种方案来进行讲解,在上面配置类中可以看到方法schedulerFactoryBean内自动注入了JobFactory实例,也就是我们自定义的AutowiringSpringBeanJobFactory任务工厂实例,另外一个参数就是DataSource,在我们引入spring-starter-data-jpa依赖后会根据application.yml文件内的数据源相关配置自动实例化DataSource实例,这里直接注入是没有问题的。

    我们通过调用SchedulerFactoryBean对象的setConfigLocation方法来设置quartz定时任务框架的基本配置,配置文件所在位置:resources/quartz.properties => classpath:/quartz.properties下。

    注意:quartz.properties配置文件一定要放在classpath下,放在别的位置有部分功能不会生效。

    下面我们来看下quartz.properties文件内的配置,如下所示:

    #调度器实例名称
    org.quartz.scheduler.instanceName = quartzScheduler
    
    #调度器实例编号自动生成
    org.quartz.scheduler.instanceId = AUTO
    
    #持久化方式配置
    org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
    
    #持久化方式配置数据驱动,MySQL数据库
    org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
    
    #quartz相关数据表前缀名
    org.quartz.jobStore.tablePrefix = QRTZ_
    
    #开启分布式部署
    org.quartz.jobStore.isClustered = true
    #配置是否使用
    org.quartz.jobStore.useProperties = false
    
    #分布式节点有效性检查时间间隔,单位:毫秒
    org.quartz.jobStore.clusterCheckinInterval = 20000
    
    #线程池实现类
    org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
    
    #执行最大并发线程数量
    org.quartz.threadPool.threadCount = 10
    
    #线程优先级
    org.quartz.threadPool.threadPriority = 5
    
    #配置为守护线程,设置后任务将不会执行
    #org.quartz.threadPool.makeThreadsDaemons=true
    
    #配置是否启动自动加载数据库内的定时任务,默认true
    org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
    

    由于我们下一章需要做分布式多节点自动交付高可用,本章的配置文件加入了分布式相关的配置。
    在上面配置中org.quartz.jobStore.classorg.quartz.jobStore.driverDelegateClass是定时任务持久化的关键配置,配置了数据库持久化定时任务以及采用MySQL数据库进行连接,当然这里我们也可以配置其他的数据库,如下所示:
    PostgreSQLorg.quartz.impl.jdbcjobstore.PostgreSQLDelegate
    Sybase : org.quartz.impl.jdbcjobstore.SybaseDelegate
    MSSQL : org.quartz.impl.jdbcjobstore.MSSQLDelegate
    HSQLDB : org.quartz.impl.jdbcjobstore.HSQLDBDelegate
    Oracle : org.quartz.impl.jdbcjobstore.oracle.OracleDelegate

    org.quartz.jobStore.tablePrefix属性配置了定时任务数据表的前缀,在quartz官方提供的创建表SQL脚本默认就是qrtz_,在对应的XxxDelegate驱动类内也是使用的默认值,所以这里我们如果修改表名前缀,配置可以去掉。

    org.quartz.jobStore.isClustered属性配置了开启定时任务分布式功能,再开启分布式时对应属性org.quartz.scheduler.instanceId 改成Auto配置即可,实例唯一标识会自动生成,这个标识具体生成的内容,我们一会在运行的控制台就可以看到了,定时任务分布式准备好后会输出相关的分布式节点配置信息。

    创建表SQL会在本章源码resources目录下,源码地址https://gitee.com/hengboy/spring-boot-chapter

    准备测试

    我们先来创建一个简单的商品数据表,建表SQL如下所示:

    DROP TABLE IF EXISTS `basic_good_info`;
    CREATE TABLE `basic_good_info` (
      `BGI_ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '商品编号',
      `BGI_NAME` varchar(20) DEFAULT NULL COMMENT '商品名称',
      `BGI_PRICE` decimal(8,2) DEFAULT NULL COMMENT '单价',
      `BGI_UNIT` varchar(10) DEFAULT NULL COMMENT '单位',
      PRIMARY KEY (`BGI_ID`)
    ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8 COMMENT='商品基本信息';
    

    GoodEntity

    我们先来针对表basic_good_info创建一个实体,并且添加JPA相关的配置,如下所示:

    package com.hengyu.chapter39.good.entity;
    
    import lombok.Data;
    
    import javax.persistence.*;
    import java.math.BigDecimal;
    
    /**
     * ========================
     *
     * @author 恒宇少年
     * Created with IntelliJ IDEA.
     * Date:2017/11/5
     * Time:14:59
     * 码云:http://git.oschina.net/jnyqy
     * ========================
     */
    @Entity
    @Table(name = "basic_good_info")
    @Data
    public class GoodInfoEntity
    {
        /**
         * 商品编号
         */
        @Id
        @GeneratedValue
        @Column(name = "bgi_id")
        private Long id;
        /**
         * 商品名称
         */
        @Column(name = "bgi_name")
        private String name;
        /**
         * 商品单位
         */
        @Column(name = "bgi_unit")
        private String unit;
        /**
         * 商品单价
         */
        @Column(name = "bgi_price")
        private BigDecimal price;
    }
    

    下面我们根据商品实体来创建JPA接口,如下所示:

    /**
     * ========================
     * Created with IntelliJ IDEA.
     * Date:2017/11/5
     * Time:14:55
     * 码云:http://git.oschina.net/jnyqy
     * ========================
     * @author 恒宇少年
     */
    public interface GoodInfoRepository
        extends JpaRepository<GoodInfoEntity,Long>
    {
    }
    

    接下来我们再来添加一个商品添加的控制器方法,如下所示:

    /**
     * ========================
     *
     * @author 恒宇少年
     * Created with IntelliJ IDEA.
     * Date:2017/11/5
     * Time:15:02
     * 码云:http://git.oschina.net/jnyqy
     * ========================
     */
    @RestController
    @RequestMapping(value = "/good")
    public class GoodController
    {
        /**
         * 商品业务逻辑实现
         */
        @Autowired
        private GoodInfoService goodInfoService;
        /**
         * 添加商品
         * @return
         */
        @RequestMapping(value = "/save")
        public Long save(GoodInfoEntity good) throws Exception
        {
            return goodInfoService.saveGood(good);
        }
    }
    

    在请求商品添加方法时,我们调用了GoodInfoService内的saveGood方法,传递一个商品的实例作为参数。我们接下来看看该类内相关代码,如下所示:

    /**
     * 商品业务逻辑
     * ========================
     *
     * @author 恒宇少年
     * Created with IntelliJ IDEA.
     * Date:2017/11/5
     * Time:15:04
     * 码云:http://git.oschina.net/jnyqy
     * ========================
     */
    @Service
    @Transactional(rollbackFor = Exception.class)
    public class GoodInfoService
    {
        /**
         * 注入任务调度器
         */
        @Autowired
        private Scheduler scheduler;
        /**
         * 商品数据接口
         */
        @Autowired
        private GoodInfoRepository goodInfoRepository;
    
        /**
         * 保存商品基本信息
         * @param good 商品实例
         * @return
         */
        public Long saveGood(GoodInfoEntity good) throws Exception
        {
            goodInfoRepository.save(good);
            return good.getId();
        }
    

    我们只是作为保存商品的操作,下面我们来模拟一个需求,在商品添加完成后1分钟我们通知后续的逻辑进行下一步处理,同时开始商品库存定时检查的任务。

    定义商品添加定时任务

    我们先来创建一个任务实例,并且继承org.springframework.scheduling.quartz.QuartzJobBean抽象类,重写父抽象类内的executeInternal方法来实现任务的主体逻辑。如下所示:

    /**
     * 商品添加定时任务实现类
     * ========================
     * Created with IntelliJ IDEA.
     * User:恒宇少年
     * Date:2017/11/5
     * Time:14:47
     * 码云:http://git.oschina.net/jnyqy
     * ========================
     * @author 恒宇少年
     */
    public class GoodAddTimer
        extends QuartzJobBean
    {
        /**
         * logback
         */
        static Logger logger = LoggerFactory.getLogger(GoodAddTimer.class);
        /**
         * 定时任务逻辑实现方法
         * 每当触发器触发时会执行该方法逻辑
         * @param jobExecutionContext 任务执行上下文
         * @throws JobExecutionException
         */
        @Override
        protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
            logger.info("商品添加完成后执行任务,任务时间:{}",new Date());
        }
    

    在任务主体逻辑内,我们只是做了一个简单的输出任务执行的时间,下面我们再来创建库存定时检查任务。

    定义商品库存检查任务

    同样需要继承org.springframework.scheduling.quartz.QuartzJobBean抽象类实现抽象类内的executeInternal方法,如下所示:

    /**
     * 商品库存检查定时任务
     * ========================
     *
     * @author 恒宇少年
     * Created with IntelliJ IDEA.
     * Date:2017/11/5
     * Time:15:47
     * 码云:http://git.oschina.net/jnyqy
     * ========================
     */
    public class GoodStockCheckTimer
        extends QuartzJobBean
    {
        /**
         * logback
         */
        static Logger logger = LoggerFactory.getLogger(GoodStockCheckTimer.class);
    
        @Override
        protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
            logger.info("执行库存检查定时任务,执行时间:{}",new Date());
        }
    }
    

    都是简单的做了下日志的输出,下面我们需要重构GoodInfoService内的saveGood方法,对应的添加上面两个任务的创建。

    设置商品添加任务到调度器

    GoodInfoService类内添加buildCreateGoodTimer方法用于实例化商品添加任务,如下所示:

    /**
         * 构建创建商品定时任务
         */
        public void buildCreateGoodTimer() throws Exception
        {
            //设置开始时间为1分钟后
            long startAtTime = System.currentTimeMillis() + 1000 * 60;
            //任务名称
            String name = UUID.randomUUID().toString();
            //任务所属分组
            String group = GoodAddTimer.class.getName();
            //创建任务
            JobDetail jobDetail = JobBuilder.newJob(GoodAddTimer.class).withIdentity(name,group).build();
            //创建任务触发器
            Trigger trigger = TriggerBuilder.newTrigger().withIdentity(name,group).startAt(new Date(startAtTime)).build();
            //将触发器与任务绑定到调度器内
            scheduler.scheduleJob(jobDetail, trigger);
        }
    

    在上面方法中我们定义的GoodAddTimer实例只运行一次,在商品添加完成后延迟1分钟进行调用任务主体逻辑。

    其中任务的名称以及任务的分组是为了区分任务做的限制,在同一个分组下如果加入同样名称的任务,则会提示任务已经存在,添加失败的提示。

    我们通过JobDetail来构建一个任务实例,设置GoodAddTimer类作为任务运行目标对象,当任务被触发时就会执行GoodAddTimer内的executeInternal方法。

    一个任务需要设置对应的触发器,触发器也分为很多种,该任务中我们并没有采用cron表达式来设置触发器,而是调用startAt方法设置任务开始执行时间。

    最后将任务以及任务的触发器共同交付给任务调度器,这样就完成了一个任务的设置。

    设置商品库存检查到任务调度器

    GoodInfoService类内添加buildGoodStockTimer方法用于实例化商品添加任务,如下所示:

    /**
         * 构建商品库存定时任务
         * @throws Exception
         */
        public void buildGoodStockTimer() throws Exception
        {
            //任务名称
            String name = UUID.randomUUID().toString();
            //任务所属分组
            String group = GoodStockCheckTimer.class.getName();
    
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/30 * * * * ?");
            //创建任务
            JobDetail jobDetail = JobBuilder.newJob(GoodStockCheckTimer.class).withIdentity(name,group).build();
            //创建任务触发器
            Trigger trigger = TriggerBuilder.newTrigger().withIdentity(name,group).withSchedule(scheduleBuilder).build();
            //将触发器与任务绑定到调度器内
            scheduler.scheduleJob(jobDetail, trigger);
        }
    

    该任务的触发器我们采用了cron表达式来设置,每隔30秒执行一次任务主体逻辑。

    任务触发器在创建时cron表达式可以搭配startAt方法来同时使用。

    下面我们修改GoodInfoService内的saveGood方法,分别调用设置任务的两个方法,如下所示:

    /**
         * 保存商品基本信息
         * @param good 商品实例
         * @return
         */
        public Long saveGood(GoodInfoEntity good) throws Exception
        {
            goodInfoRepository.save(good);
            //构建创建商品定时任务
            buildCreateGoodTimer();
            //构建商品库存定时任务
            buildGoodStockTimer();
            return good.getId();
        }
    

    下面我们就来测试下任务是否可以顺序的被持久化到数据库,并且是否可以在重启服务后执行重启前添加的任务。

    测试

    下面我们来启动项目,启动成功后,我们来查看控制台输出的分布式节点的信息,如下所示:

    2017-11-05 18:09:40.052  INFO 7708 --- [           main] c.hengyu.chapter39.Chapter39Application  : 【【【【【【定时任务分布式节点 - 1 已启动】】】】】】
    2017-11-05 18:09:42.005  INFO 7708 --- [lerFactoryBean]] o.s.s.quartz.SchedulerFactoryBean        : Starting Quartz Scheduler now, after delay of 2 seconds
    2017-11-05 18:09:42.027  INFO 7708 --- [lerFactoryBean]] o.s.s.quartz.LocalDataSourceJobStore     : ClusterManager: detected 1 failed or restarted instances.
    2017-11-05 18:09:42.027  INFO 7708 --- [lerFactoryBean]] o.s.s.quartz.LocalDataSourceJobStore     : ClusterManager: Scanning for instance "yuqiyu1509876084785"'s failed in-progress jobs.
    2017-11-05 18:09:42.031  INFO 7708 --- [lerFactoryBean]] o.s.s.quartz.LocalDataSourceJobStore     : ClusterManager: ......Freed 1 acquired trigger(s).
    2017-11-05 18:09:42.033  INFO 7708 --- [lerFactoryBean]] org.quartz.core.QuartzScheduler          : Scheduler schedulerFactoryBean_$_yuqiyu1509876579404 started.
    

    定时任务是在项目启动后2秒进行执行初始化,并且通过ClusterManager来完成了instance的创建,创建的节点唯一标识为yuqiyu1509876084785

    编写商品控制器请求方法测试用例,如下所示:

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class Chapter39ApplicationTests {
        /**
         * 模拟mvc测试对象
         */
        private MockMvc mockMvc;
    
        /**
         * web项目上下文
         */
        @Autowired
        private WebApplicationContext webApplicationContext;
    
        /**
         * 所有测试方法执行之前执行该方法
         */
        @Before
        public void before() {
            //获取mockmvc对象实例
            mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
        }
    
        /**
         * 测试添加商品
         * @throws Exception
         */
        @Test
        public void addGood() throws Exception
        {
            MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post("/good/save")
                    .param("name","西瓜")
                    .param("unit","斤")
                    .param("price","12.88")
            )
                    .andDo(MockMvcResultHandlers.print())
                    .andExpect(MockMvcResultMatchers.status().is(200))
                    .andReturn();
            result.getResponse().setCharacterEncoding("UTF-8");
            System.out.println(result.getResponse().getContentAsString());
        }
    

    测试用例相关文章请访问第三十五章:SpringBoot与单元测试的小秘密,我们来执行addGood测试方法,查看控制台输出,如下所示:

    ....省略部分输出
    Hibernate: insert into basic_good_info (bgi_name, bgi_price, bgi_unit) values (?, ?, ?)
    2017-11-05 18:06:35.699 TRACE 7560 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [西瓜]
    2017-11-05 18:06:35.701 TRACE 7560 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [NUMERIC] - [12.88]
    2017-11-05 18:06:35.701 TRACE 7560 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [VARCHAR] - [斤]
    ....省略部分输出
    8
    ....省略部分输出
    

    可以看到我们的商品已被成功的写入到数据库并且输出的主键值,我们的任务是否也成功的被写入到数据库了呢?我们来查看qrtz_job_details表内任务列表,如下所示:

    schedulerFactoryBean    7567c9d7-76f5-47f3-bc5d-b934f4c1063b    com.hengyu.chapter39.timers.GoodStockCheckTimer     com.hengyu.chapter39.timers.GoodStockCheckTimer 0   0   0   0   0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787000737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F40000000000010770800000010000000007800
    schedulerFactoryBean    e5e08ab0-9be3-43fb-93b8-b9490432a5d7    com.hengyu.chapter39.timers.GoodAddTimer        com.hengyu.chapter39.timers.GoodAddTimer    0   0   0   0   0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787000737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F40000000000010770800000010000000007800
    
    

    任务已经被成功的持久化到数据库内,等待1分钟后查看控制台输出内容如下所示:

    2017-11-05 18:12:30.017  INFO 7708 --- [ryBean_Worker-1] c.h.c.timers.GoodStockCheckTimer         : 执行库存检查定时任务,执行时间:Sun Nov 05 18:12:30 CST 2017
    2017-11-05 18:13:00.009  INFO 7708 --- [ryBean_Worker-2] c.h.c.timers.GoodStockCheckTimer         : 执行库存检查定时任务,执行时间:Sun Nov 05 18:13:00 CST 2017
    2017-11-05 18:13:02.090  INFO 7708 --- [ryBean_Worker-3] c.hengyu.chapter39.timers.GoodAddTimer   : 商品添加完成后执行任务,任务时间:Sun Nov 05 18:13:02 CST 2017
    

    根据输出的内容来判定完全吻合我们的配置参数,库存检查为30秒执行一次,而添加成功后的提醒则是1分钟后执行一次。执行完成后就会被直接销毁,我们再来查看数据库表qrtz_job_details,这时就可以看到还剩下1个任务

    重启服务任务是否自动执行?

    下面我们把项目重启下,然后观察控制台的输出内容,如下所示:

    2017-11-05 18:15:54.018  INFO 7536 --- [           main] c.hengyu.chapter39.Chapter39Application  : 【【【【【【定时任务分布式节点 - 1 已启动】】】】】】
    2017-11-05 18:15:55.975  INFO 7536 --- [lerFactoryBean]] o.s.s.quartz.SchedulerFactoryBean        : Starting Quartz Scheduler now, after delay of 2 seconds
    2017-11-05 18:15:56.000  INFO 7536 --- [lerFactoryBean]] org.quartz.core.QuartzScheduler          : Scheduler schedulerFactoryBean_$_yuqiyu1509876953202 started.
    2017-11-05 18:16:15.999  INFO 7536 --- [_ClusterManager] o.s.s.quartz.LocalDataSourceJobStore     : ClusterManager: detected 1 failed or restarted instances.
    2017-11-05 18:16:16.000  INFO 7536 --- [_ClusterManager] o.s.s.quartz.LocalDataSourceJobStore     : ClusterManager: Scanning for instance "yuqiyu1509876579404"'s failed in-progress jobs.
    2017-11-05 18:16:16.005  INFO 7536 --- [_ClusterManager] o.s.s.quartz.LocalDataSourceJobStore     : ClusterManager: ......Freed 1 acquired trigger(s).
    2017-11-05 18:16:16.041  INFO 7536 --- [ryBean_Worker-1] c.h.c.timers.GoodStockCheckTimer         : 执行库存检查定时任务,执行时间:Sun Nov 05 18:16:16 CST 2017
    

    可以看到成功的自动执行了我们在重启之前配置的任务。

    总结

    本章主要讲解了SpringBoot整合quartz定时任务框架,完成了分布式单节点任务持久化,下一章我们会讲解任务参数传递以及分布式多节点任务自动负载。

    本章源码已经上传到码云:
    SpringBoot配套源码地址:https://gitee.com/hengboy/spring-boot-chapter
    SpringCloud配套源码地址:https://gitee.com/hengboy/spring-cloud-chapter
    SpringBoot相关系列文章请访问:目录:SpringBoot学习目录
    QueryDSL相关系列文章请访问:QueryDSL通用查询框架学习目录
    SpringDataJPA相关系列文章请访问:目录:SpringDataJPA学习目录
    SpringBoot相关文章请访问:目录:SpringBoot学习目录,感谢阅读!

    4461954-86f6a13000597ffb.png
    微信扫码关注 - 专注分享

    加入知识星球,恒宇少年带你走以后的技术道路!!!

    4461954-a56b71bb85d168f9.JPG
    微信扫码加入
    展开全文
  • 我们在之前有讲过SpringBoot是已经集成了定时任务的,那么我们本章将会采用外置的quartz定时任务框架来完成定时任务的分布式单节点持久化,我们为什么要持久化定时任务呢?在一些项目中定时任务可能是必不可少的,...

    定时任务在企业项目比较常用到,几乎所有的项目都会牵扯该功能模块,定时任务一般会处理指定时间点执行某一些业务逻辑、间隔时间执行某一些业务逻辑等。我们在之前有讲过SpringBoot是已经集成了定时任务的,那么我们本章将会采用外置的quartz定时任务框架来完成定时任务的分布式单节点持久化,我们为什么要持久化定时任务呢?

    在一些项目中定时任务可能是必不可少的,由于某种特殊的原因定时任务可能丢失,如重启定时任务服务项目后,原内存中的定时任务就会被完全释放!那对于我们来说可能是致命的问题。当然也有强制的办法解决这类问题,但是如果我们把定时任务持久化到数据库,像维护普通逻辑数据那样维护任务,就会避免项目中遇到的种种的特殊情况。

    本章目标

    基于SpringBoot架构整合定时任务框架quartz来完成分布式单节点定时任务持久化,将任务持久化到数据库,更好的预防任务丢失。

    SpringBoot 企业级核心技术学习专题专题专题名称专题描述001讲解SpringBoot一些企业级层面的核心组件

    002Spring Boot 核心技术简书每一篇文章码云对应源码

    003对Spring Cloud核心技术全面讲解

    004Spring Cloud 核心技术简书每一篇文章对应源码

    005全面讲解QueryDSL核心技术以及基于SpringBoot整合SpringDataJPA

    006全面讲解SpringDataJPA核心技术

    007SpringBoot系统的学习目录,敬请关注点赞!!!

    构建项目

    我们使用idea开发工具创建一个SpringBoot项目,pom.xml依赖配置如下所示:...省略部分配置    

    UTF-8

    UTF-8

    1.8

    1.1.5

    2.3.0

    org.springframework.boot

    spring-boot-starter-data-jpa

    org.springframework.boot

    spring-boot-starter-web

    mysql

    mysql-connector-java

    runtime

    com.alibaba

    druid

    ${druid.version}

    org.quartz-scheduler

    quartz

    ${quartz.version}

    org.quartz-scheduler

    quartz-jobs

    ${quartz.version}

    org.springframework

    spring-context-support

    org.projectlombok

    lombok

    org.springframework.boot

    spring-boot-starter-test

    test

    ...省略部分配置

    我们采用的是quartz官方最新版本2.3.0,新版本的任务调度框架做出了很多封装,使用也变得简易明了。

    创建初始化完成,下面我们来创建定时任务相关的Configuration配置。

    QuartzConfiguration

    quartz与Spring相关框架的整合方式有很多种,我们今天采用jobDetail使用Spring Ioc托管方式来完成整合,我们可以在定时任务实例中使用Spring注入注解完成业务逻辑处理,下面我先把全部的配置贴出来再逐步分析,配置类如下所示:package com.hengyu.chapter39.configuration;import org.quartz.spi.JobFactory;import org.quartz.spi.TriggerFiredBundle;import org.springframework.beans.factory.annotation.Autowire;import org.springframework.beans.factory.config.AutowireCapableBeanFactory;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.io.ClassPathResource;import org.springframework.scheduling.annotation.EnableScheduling;import org.springframework.scheduling.quartz.SchedulerFactoryBean;import org.springframework.scheduling.quartz.SpringBeanJobFactory;import javax.sql.DataSource;/**

    * quartz定时任务配置

    * ========================

    * Created with IntelliJ IDEA.

    * User:恒宇少年

    * Date:2017/11/5

    * Time:14:07

    * 码云:http://git.oschina.net/jnyqy

    * ========================

    * @author  恒宇少年

    */@Configuration@EnableSchedulingpublic class QuartzConfiguration{    /**

    * 继承org.springframework.scheduling.quartz.SpringBeanJobFactory

    * 实现任务实例化方式

    */

    public static class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements

    ApplicationContextAware {        private transient AutowireCapableBeanFactory beanFactory;        @Override

    public void setApplicationContext(final ApplicationContext context) {

    beanFactory = context.getAutowireCapableBeanFactory();

    }        /**

    * 将job实例交给spring ioc托管

    * 我们在job实例实现类内可以直接使用spring注入的调用被spring ioc管理的实例

    * @param bundle

    * @return

    * @throws Exception

    */

    @Override

    protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {            final Object job = super.createJobInstance(bundle);            /**

    * 将job实例交付给spring ioc

    */

    beanFactory.autowireBean(job);            return job;

    }

    }    /**

    * 配置任务工厂实例

    * @param applicationContext spring上下文实例

    * @return

    */

    @Bean

    public JobFactory jobFactory(ApplicationContext applicationContext)

    {        /**

    * 采用自定义任务工厂 整合spring实例来完成构建任务

    * see {@link AutowiringSpringBeanJobFactory}

    */

    AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();

    jobFactory.setApplicationContext(applicationContext);        return jobFactory;

    }    /**

    * 配置任务调度器

    * 使用项目数据源作为quartz数据源

    * @param jobFactory 自定义配置任务工厂

    * @param dataSource 数据源实例

    * @return

    * @throws Exception

    */

    @Bean(destroyMethod = "destroy",autowire = Autowire.NO)    public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory, DataSource dataSource) throws Exception    {

    SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();        //将spring管理job自定义工厂交由调度器维护

    schedulerFactoryBean.setJobFactory(jobFactory);        //设置覆盖已存在的任务

    schedulerFactoryBean.setOverwriteExistingJobs(true);        //项目启动完成后,等待2秒后开始执行调度器初始化

    schedulerFactoryBean.setStartupDelay(2);        //设置调度器自动运行

    schedulerFactoryBean.setAutoStartup(true);        //设置数据源,使用与项目统一数据源

    schedulerFactoryBean.setDataSource(dataSource);        //设置上下文spring bean name

    schedulerFactoryBean.setApplicationContextSchedulerContextKey("applicationContext");        //设置配置文件位置

    schedulerFactoryBean.setConfigLocation(new ClassPathResource("/quartz.properties"));        return schedulerFactoryBean;

    }

    }

    AutowiringSpringBeanJobFactory

    可以看到上面配置类中,AutowiringSpringBeanJobFactory我们继承了SpringBeanJobFactory类,并且通过实现ApplicationContextAware接口获取ApplicationContext设置方法,通过外部实例化时设置ApplicationContext实例对象,在createJobInstance方法内,我们采用AutowireCapableBeanFactory来托管SpringBeanJobFactory类中createJobInstance方法返回的定时任务实例,这样我们就可以在定时任务类内使用Spring Ioc相关的注解进行注入业务逻辑实例了。

    JobFactory

    任务工厂是在本章配置调度器时所需要的实例,我们通过jobFactory方法注入ApplicationContext实例,来创建一个AutowiringSpringBeanJobFactory对象,并且将对象实例托管到Spring Ioc容器内。

    SchedulerFactoryBean

    我们本章采用的是项目内部数据源的方式来设置调度器的jobSotre,官方quartz有两种持久化的配置方案。

    第一种:采用quartz.properties配置文件配置独立的定时任务数据源,可以与使用项目的数据库完全独立。

    第二种:采用与创建项目统一个数据源,定时任务持久化相关的表与业务逻辑在同一个数据库内。

    可以根据实际的项目需求采取不同的方案,我们本章主要是通过第二种方案来进行讲解,在上面配置类中可以看到方法schedulerFactoryBean内自动注入了JobFactory实例,也就是我们自定义的AutowiringSpringBeanJobFactory任务工厂实例,另外一个参数就是DataSource,在我们引入spring-starter-data-jpa依赖后会根据application.yml文件内的数据源相关配置自动实例化DataSource实例,这里直接注入是没有问题的。

    我们通过调用SchedulerFactoryBean对象的setConfigLocation方法来设置quartz定时任务框架的基本配置,配置文件所在位置:resources/quartz.properties => classpath:/quartz.properties下。注意:quartz.properties配置文件一定要放在classpath下,放在别的位置有部分功能不会生效。

    下面我们来看下quartz.properties文件内的配置,如下所示:#调度器实例名称org.quartz.scheduler.instanceName = quartzScheduler#调度器实例编号自动生成org.quartz.scheduler.instanceId = AUTO#持久化方式配置org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX#持久化方式配置数据驱动,MySQL数据库org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate#quartz相关数据表前缀名org.quartz.jobStore.tablePrefix = QRTZ_#开启分布式部署org.quartz.jobStore.isClustered = true#配置是否使用org.quartz.jobStore.useProperties = false#分布式节点有效性检查时间间隔,单位:毫秒org.quartz.jobStore.clusterCheckinInterval = 20000#线程池实现类org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool#执行最大并发线程数量org.quartz.threadPool.threadCount = 10#线程优先级org.quartz.threadPool.threadPriority = 5#配置为守护线程,设置后任务将不会执行#org.quartz.threadPool.makeThreadsDaemons=true#配置是否启动自动加载数据库内的定时任务,默认trueorg.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true

    由于我们下一章需要做分布式多节点自动交付高可用,本章的配置文件加入了分布式相关的配置。

    在上面配置中org.quartz.jobStore.class与org.quartz.jobStore.driverDelegateClass是定时任务持久化的关键配置,配置了数据库持久化定时任务以及采用MySQL数据库进行连接,当然这里我们也可以配置其他的数据库,如下所示:

    PostgreSQL : org.quartz.impl.jdbcjobstore.PostgreSQLDelegate

    Sybase : org.quartz.impl.jdbcjobstore.SybaseDelegate

    MSSQL : org.quartz.impl.jdbcjobstore.MSSQLDelegate

    HSQLDB : org.quartz.impl.jdbcjobstore.HSQLDBDelegate

    Oracle : org.quartz.impl.jdbcjobstore.oracle.OracleDelegate

    org.quartz.jobStore.tablePrefix属性配置了定时任务数据表的前缀,在quartz官方提供的创建表SQL脚本默认就是qrtz_,在对应的XxxDelegate驱动类内也是使用的默认值,所以这里我们如果修改表名前缀,配置可以去掉。

    org.quartz.jobStore.isClustered属性配置了开启定时任务分布式功能,再开启分布式时对应属性org.quartz.scheduler.instanceId 改成Auto配置即可,实例唯一标识会自动生成,这个标识具体生成的内容,我们一会在运行的控制台就可以看到了,定时任务分布式准备好后会输出相关的分布式节点配置信息。

    准备测试

    我们先来创建一个简单的商品数据表,建表SQL如下所示:DROP TABLE IF EXISTS `basic_good_info`;

    CREATE TABLE `basic_good_info` (

    `BGI_ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '商品编号',

    `BGI_NAME` varchar(20) DEFAULT NULL COMMENT '商品名称',

    `BGI_PRICE` decimal(8,2) DEFAULT NULL COMMENT '单价',

    `BGI_UNIT` varchar(10) DEFAULT NULL COMMENT '单位',

    PRIMARY KEY (`BGI_ID`)

    ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8 COMMENT='商品基本信息';

    GoodEntity

    我们先来针对表basic_good_info创建一个实体,并且添加JPA相关的配置,如下所示:package com.hengyu.chapter39.good.entity;import lombok.Data;import javax.persistence.*;import java.math.BigDecimal;/**

    * ========================

    *

    * @author 恒宇少年

    * Created with IntelliJ IDEA.

    * Date:2017/11/5

    * Time:14:59

    * 码云:http://git.oschina.net/jnyqy

    * ========================

    */@Entity@Table(name = "basic_good_info")

    @Data

    public class GoodInfoEntity

    {    /**

    * 商品编号

    */

    @Id

    @GeneratedValue

    @Column(name = "bgi_id")

    private Long id;    /**

    * 商品名称

    */

    @Column(name = "bgi_name")

    private String name;    /**

    * 商品单位

    */

    @Column(name = "bgi_unit")

    private String unit;    /**

    * 商品单价

    */

    @Column(name = "bgi_price")

    private BigDecimal price;

    }

    下面我们根据商品实体来创建JPA接口,如下所示:/**

    * ========================

    * Created with IntelliJ IDEA.

    * Date:2017/11/5

    * Time:14:55

    * 码云:http://git.oschina.net/jnyqy

    * ========================

    * @author 恒宇少年

    */public interface GoodInfoRepository

    extends JpaRepository{

    }

    接下来我们再来添加一个商品添加的控制器方法,如下所示:/**

    * ========================

    *

    * @author 恒宇少年

    * Created with IntelliJ IDEA.

    * Date:2017/11/5

    * Time:15:02

    * 码云:http://git.oschina.net/jnyqy

    * ========================

    */@RestController@RequestMapping(value = "/good")public class GoodController{    /**

    * 商品业务逻辑实现

    */

    @Autowired

    private GoodInfoService goodInfoService;    /**

    * 添加商品

    * @return

    */

    @RequestMapping(value = "/save")    public Long save(GoodInfoEntity good) throws Exception    {        return goodInfoService.saveGood(good);

    }

    }

    在请求商品添加方法时,我们调用了GoodInfoService内的saveGood方法,传递一个商品的实例作为参数。我们接下来看看该类内相关代码,如下所示:/**

    * 商品业务逻辑

    * ========================

    *

    * @author 恒宇少年

    * Created with IntelliJ IDEA.

    * Date:2017/11/5

    * Time:15:04

    * 码云:http://git.oschina.net/jnyqy

    * ========================

    */@Service@Transactional(rollbackFor = Exception.class)public class GoodInfoService{    /**

    * 注入任务调度器

    */

    @Autowired

    private Scheduler scheduler;    /**

    * 商品数据接口

    */

    @Autowired

    private GoodInfoRepository goodInfoRepository;    /**

    * 保存商品基本信息

    * @param good 商品实例

    * @return

    */

    public Long saveGood(GoodInfoEntity good) throws Exception    {

    goodInfoRepository.save(good);        return good.getId();

    }

    我们只是作为保存商品的操作,下面我们来模拟一个需求,在商品添加完成后1分钟我们通知后续的逻辑进行下一步处理,同时开始商品库存定时检查的任务。

    定义商品添加定时任务

    我们先来创建一个任务实例,并且继承org.springframework.scheduling.quartz.QuartzJobBean抽象类,重写父抽象类内的executeInternal方法来实现任务的主体逻辑。如下所示:/**

    * 商品添加定时任务实现类

    * ========================

    * Created with IntelliJ IDEA.

    * User:恒宇少年

    * Date:2017/11/5

    * Time:14:47

    * 码云:http://git.oschina.net/jnyqy

    * ========================

    * @author 恒宇少年

    */public class GoodAddTimer

    extends QuartzJobBean{    /**

    * logback

    */

    static Logger logger = LoggerFactory.getLogger(GoodAddTimer.class);    /**

    * 定时任务逻辑实现方法

    * 每当触发器触发时会执行该方法逻辑

    * @param jobExecutionContext 任务执行上下文

    * @throws JobExecutionException

    */

    @Override

    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {

    logger.info("商品添加完成后执行任务,任务时间:{}",new Date());

    }

    在任务主体逻辑内,我们只是做了一个简单的输出任务执行的时间,下面我们再来创建库存定时检查任务。

    定义商品库存检查任务

    同样需要继承org.springframework.scheduling.quartz.QuartzJobBean抽象类实现抽象类内的executeInternal方法,如下所示:/**

    * 商品库存检查定时任务

    * ========================

    *

    * @author 恒宇少年

    * Created with IntelliJ IDEA.

    * Date:2017/11/5

    * Time:15:47

    * 码云:http://git.oschina.net/jnyqy

    * ========================

    */public class GoodStockCheckTimer

    extends QuartzJobBean{    /**

    * logback

    */

    static Logger logger = LoggerFactory.getLogger(GoodStockCheckTimer.class);    @Override

    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {

    logger.info("执行库存检查定时任务,执行时间:{}",new Date());

    }

    }

    都是简单的做了下日志的输出,下面我们需要重构GoodInfoService内的saveGood方法,对应的添加上面两个任务的创建。

    设置商品添加任务到调度器

    在GoodInfoService类内添加buildCreateGoodTimer方法用于实例化商品添加任务,如下所示:/**

    * 构建创建商品定时任务

    */

    public void buildCreateGoodTimer() throws Exception    {        //设置开始时间为1分钟后

    long startAtTime = System.currentTimeMillis() + 1000 * 60;        //任务名称

    String name = UUID.randomUUID().toString();        //任务所属分组

    String group = GoodAddTimer.class.getName();        //创建任务

    JobDetail jobDetail = JobBuilder.newJob(GoodAddTimer.class).withIdentity(name,group).build();        //创建任务触发器

    Trigger trigger = TriggerBuilder.newTrigger().withIdentity(name,group).startAt(new Date(startAtTime)).build();        //将触发器与任务绑定到调度器内

    scheduler.scheduleJob(jobDetail, trigger);

    }

    在上面方法中我们定义的GoodAddTimer实例只运行一次,在商品添加完成后延迟1分钟进行调用任务主体逻辑。其中任务的名称以及任务的分组是为了区分任务做的限制,在同一个分组下如果加入同样名称的任务,则会提示任务已经存在,添加失败的提示。

    我们通过JobDetail来构建一个任务实例,设置GoodAddTimer类作为任务运行目标对象,当任务被触发时就会执行GoodAddTimer内的executeInternal方法。

    一个任务需要设置对应的触发器,触发器也分为很多种,该任务中我们并没有采用cron表达式来设置触发器,而是调用startAt方法设置任务开始执行时间。

    最后将任务以及任务的触发器共同交付给任务调度器,这样就完成了一个任务的设置。

    设置商品库存检查到任务调度器

    在GoodInfoService类内添加buildGoodStockTimer方法用于实例化商品添加任务,如下所示:/**

    * 构建商品库存定时任务

    * @throws Exception

    */

    public void buildGoodStockTimer() throws Exception    {        //任务名称

    String name = UUID.randomUUID().toString();        //任务所属分组

    String group = GoodStockCheckTimer.class.getName();

    CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/30 * * * * ?");        //创建任务

    JobDetail jobDetail = JobBuilder.newJob(GoodStockCheckTimer.class).withIdentity(name,group).build();        //创建任务触发器

    Trigger trigger = TriggerBuilder.newTrigger().withIdentity(name,group).withSchedule(scheduleBuilder).build();        //将触发器与任务绑定到调度器内

    scheduler.scheduleJob(jobDetail, trigger);

    }

    该任务的触发器我们采用了cron表达式来设置,每隔30秒执行一次任务主体逻辑。任务触发器在创建时cron表达式可以搭配startAt方法来同时使用。

    下面我们修改GoodInfoService内的saveGood方法,分别调用设置任务的两个方法,如下所示:/**

    * 保存商品基本信息

    * @param good 商品实例

    * @return

    */

    public Long saveGood(GoodInfoEntity good) throws Exception    {

    goodInfoRepository.save(good);        //构建创建商品定时任务

    buildCreateGoodTimer();        //构建商品库存定时任务

    buildGoodStockTimer();        return good.getId();

    }

    下面我们就来测试下任务是否可以顺序的被持久化到数据库,并且是否可以在重启服务后执行重启前添加的任务。

    测试

    下面我们来启动项目,启动成功后,我们来查看控制台输出的分布式节点的信息,如下所示:2017-11-05 18:09:40.052  INFO 7708 --- [           main] c.hengyu.chapter39.Chapter39Application  : 【【【【【【定时任务分布式节点 - 1 已启动】】】】】】2017-11-05 18:09:42.005  INFO 7708 --- [lerFactoryBean]] o.s.s.quartz.SchedulerFactoryBean        : Starting Quartz Scheduler now, after delay of 2 seconds2017-11-05 18:09:42.027  INFO 7708 --- [lerFactoryBean]] o.s.s.quartz.LocalDataSourceJobStore     : ClusterManager: detected 1 failed or restarted instances.2017-11-05 18:09:42.027  INFO 7708 --- [lerFactoryBean]] o.s.s.quartz.LocalDataSourceJobStore     : ClusterManager: Scanning for instance "yuqiyu1509876084785"'s failed in-progress jobs.

    2017-11-05 18:09:42.031  INFO 7708 --- [lerFactoryBean]] o.s.s.quartz.LocalDataSourceJobStore     : ClusterManager: ......Freed 1 acquired trigger(s).

    2017-11-05 18:09:42.033  INFO 7708 --- [lerFactoryBean]] org.quartz.core.QuartzScheduler          : Scheduler schedulerFactoryBean_$_yuqiyu1509876579404 started.

    定时任务是在项目启动后2秒进行执行初始化,并且通过ClusterManager来完成了instance的创建,创建的节点唯一标识为yuqiyu1509876084785。

    编写商品控制器请求方法测试用例,如下所示:@RunWith(SpringRunner.class)@SpringBootTestpublic class Chapter39ApplicationTests {    /**

    * 模拟mvc测试对象

    */

    private MockMvc mockMvc;    /**

    * web项目上下文

    */

    @Autowired

    private WebApplicationContext webApplicationContext;    /**

    * 所有测试方法执行之前执行该方法

    */

    @Before

    public void before() {        //获取mockmvc对象实例

    mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();

    }    /**

    * 测试添加商品

    * @throws Exception

    */

    @Test

    public void addGood() throws Exception    {

    MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post("/good/save")

    .param("name","西瓜")

    .param("unit","斤")

    .param("price","12.88")

    )

    .andDo(MockMvcResultHandlers.print())

    .andExpect(MockMvcResultMatchers.status().is(200))

    .andReturn();

    result.getResponse().setCharacterEncoding("UTF-8");

    System.out.println(result.getResponse().getContentAsString());

    }

    测试用例相关文章请访问第三十五章:SpringBoot与单元测试的小秘密,我们来执行addGood测试方法,查看控制台输出,如下所示:....省略部分输出Hibernate: insert into basic_good_info (bgi_name, bgi_price, bgi_unit) values (?, ?, ?)

    2017-11-05 18:06:35.699 TRACE 7560 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [西瓜]2017-11-05 18:06:35.701 TRACE 7560 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [NUMERIC] - [12.88]2017-11-05 18:06:35.701 TRACE 7560 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [VARCHAR] - [斤]....省略部分输出

    8

    ....省略部分输出

    可以看到我们的商品已被成功的写入到数据库并且输出的主键值,我们的任务是否也成功的被写入到数据库了呢?我们来查看qrtz_job_details表内任务列表,如下所示:schedulerFactoryBean    7567c9d7-76f5-47f3-bc5d-b934f4c1063b    com.hengyu.chapter39.timers.GoodStockCheckTimer     com.hengyu.chapter39.timers.GoodStockCheckTimer 0   0   0   0   0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787000737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F40000000000010770800000010000000007800schedulerFactoryBean    e5e08ab0-9be3-43fb-93b8-b9490432a5d7    com.hengyu.chapter39.timers.GoodAddTimer        com.hengyu.chapter39.timers.GoodAddTimer    0   0   0   0   0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787000737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F40000000000010770800000010000000007800

    任务已经被成功的持久化到数据库内,等待1分钟后查看控制台输出内容如下所示:2017-11-05 18:12:30.017  INFO 7708 --- [ryBean_Worker-1] c.h.c.timers.GoodStockCheckTimer         : 执行库存检查定时任务,执行时间:Sun Nov 05 18:12:30 CST 2017

    2017-11-05 18:13:00.009  INFO 7708 --- [ryBean_Worker-2] c.h.c.timers.GoodStockCheckTimer         : 执行库存检查定时任务,执行时间:Sun Nov 05 18:13:00 CST 2017

    2017-11-05 18:13:02.090  INFO 7708 --- [ryBean_Worker-3] c.hengyu.chapter39.timers.GoodAddTimer   : 商品添加完成后执行任务,任务时间:Sun Nov 05 18:13:02 CST 2017

    根据输出的内容来判定完全吻合我们的配置参数,库存检查为30秒执行一次,而添加成功后的提醒则是1分钟后执行一次。执行完成后就会被直接销毁,我们再来查看数据库表qrtz_job_details,这时就可以看到还剩下1个任务。

    重启服务任务是否自动执行?

    下面我们把项目重启下,然后观察控制台的输出内容,如下所示:2017-11-05 18:15:54.018  INFO 7536 --- [           main] c.hengyu.chapter39.Chapter39Application  : 【【【【【【定时任务分布式节点 - 1 已启动】】】】】】2017-11-05 18:15:55.975  INFO 7536 --- [lerFactoryBean]] o.s.s.quartz.SchedulerFactoryBean        : Starting Quartz Scheduler now, after delay of 2 seconds2017-11-05 18:15:56.000  INFO 7536 --- [lerFactoryBean]] org.quartz.core.QuartzScheduler          : Scheduler schedulerFactoryBean_$_yuqiyu1509876953202 started.2017-11-05 18:16:15.999  INFO 7536 --- [_ClusterManager] o.s.s.quartz.LocalDataSourceJobStore     : ClusterManager: detected 1 failed or restarted instances.2017-11-05 18:16:16.000  INFO 7536 --- [_ClusterManager] o.s.s.quartz.LocalDataSourceJobStore     : ClusterManager: Scanning for instance "yuqiyu1509876579404"'s failed in-progress jobs.

    2017-11-05 18:16:16.005  INFO 7536 --- [_ClusterManager] o.s.s.quartz.LocalDataSourceJobStore     : ClusterManager: ......Freed 1 acquired trigger(s).

    2017-11-05 18:16:16.041  INFO 7536 --- [ryBean_Worker-1] c.h.c.timers.GoodStockCheckTimer         : 执行库存检查定时任务,执行时间:Sun Nov 05 18:16:16 CST 2017

    可以看到成功的自动执行了我们在重启之前配置的任务。

    总结

    本章主要讲解了SpringBoot整合quartz定时任务框架,完成了分布式单节点任务持久化,下一章我们会讲解任务参数传递以及分布式多节点任务自动负载。

    作者:恒宇少年

    链接:https://www.jianshu.com/p/d52d62fb2ac6

    展开全文
  • 我们在之前有讲过SpringBoot是已经集成了定时任务的,详见:第二十六章:SpringBoot使用@Scheduled创建定时任务,那么我们本章将会采用外置的quartz定时任务框架来完成定时任务的分布式单节点持久化,我们为什么要...

    定时任务在企业项目比较常用到,几乎所有的项目都会牵扯该功能模块,定时任务一般会处理指定时间点执行某一些业务逻辑、间隔时间执行某一些业务逻辑等。我们在之前有讲过SpringBoot是已经集成了定时任务的,详见:第二十六章:SpringBoot使用@Scheduled创建定时任务,那么我们本章将会采用外置的quartz定时任务框架来完成定时任务的分布式单节点持久化,我们为什么要持久化定时任务呢?

    在一些项目中定时任务可能是必不可少的,由于某种特殊的原因定时任务可能丢失,如重启定时任务服务项目后,原内存中的定时任务就会被完全释放!那对于我们来说可能是致命的问题。当然也有强制的办法解决这类问题,但是如果我们把定时任务持久化到数据库,像维护普通逻辑数据那样维护任务,就会避免项目中遇到的种种的特殊情况。

    本章目标

    基于SpringBoot架构整合定时任务框架quartz来完成分布式单节点定时任务持久化,将任务持久化到数据库,更好的预防任务丢失。

    SpringBoot 企业级核心技术学习专题

    专题 专题名称 专题描述
    001 Spring Boot 核心技术 讲解SpringBoot一些企业级层面的核心组件
    002 Spring Boot 核心技术章节源码 Spring Boot 核心技术简书每一篇文章码云对应源码
    003 Spring Cloud 核心技术 对Spring Cloud核心技术全面讲解
    004 Spring Cloud 核心技术章节源码 Spring Cloud 核心技术简书每一篇文章对应源码
    005 QueryDSL 核心技术 全面讲解QueryDSL核心技术以及基于SpringBoot整合SpringDataJPA
    006 SpringDataJPA 核心技术 全面讲解SpringDataJPA核心技术

    构建项目

    我们使用idea开发工具创建一个SpringBoot项目,pom.xml依赖配置如下所示:

    ...省略部分配置
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <java.version>1.8</java.version>
            <druid.version>1.1.5</druid.version>
            <quartz.version>2.3.0</quartz.version>
        </properties>
    
        <dependencies>
            <!--spring data jpa相关-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-jpa</artifactId>
            </dependency>
            <!--web相关依赖-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!--数据库相关依赖-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>${druid.version}</version>
            </dependency>
            <!--quartz相关依赖-->
            <dependency>
                <groupId>org.quartz-scheduler</groupId>
                <artifactId>quartz</artifactId>
                <version>${quartz.version}</version>
            </dependency>
            <dependency>
                <groupId>org.quartz-scheduler</groupId>
                <artifactId>quartz-jobs</artifactId>
                <version>${quartz.version}</version>
            </dependency>
            <!--定时任务需要依赖context模块-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context-support</artifactId>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    ...省略部分配置

    我们采用的是quartz官方最新版本2.3.0,新版本的任务调度框架做出了很多封装,使用也变得简易明了。
    创建初始化完成,下面我们来创建定时任务相关的Configuration配置。

    QuartzConfiguration

    quartzSpring相关框架的整合方式有很多种,我们今天采用jobDetail使用Spring Ioc托管方式来完成整合,我们可以在定时任务实例中使用Spring注入注解完成业务逻辑处理,下面我先把全部的配置贴出来再逐步分析,配置类如下所示:

    package com.hengyu.chapter39.configuration;
    
    import org.quartz.spi.JobFactory;
    import org.quartz.spi.TriggerFiredBundle;
    import org.springframework.beans.factory.annotation.Autowire;
    import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.scheduling.annotation.EnableScheduling;
    import org.springframework.scheduling.quartz.SchedulerFactoryBean;
    import org.springframework.scheduling.quartz.SpringBeanJobFactory;
    
    import javax.sql.DataSource;
    
    /**
     * quartz定时任务配置
     * ========================
     * Created with IntelliJ IDEA.
     * User:恒宇少年
     * Date:2017/11/5
     * Time:14:07
     * 码云:http://git.oschina.net/jnyqy
     * ========================
     * @author  恒宇少年
     */
    @Configuration
    @EnableScheduling
    public class QuartzConfiguration
    {
        /**
         * 继承org.springframework.scheduling.quartz.SpringBeanJobFactory
         * 实现任务实例化方式
         */
        public static class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements
                ApplicationContextAware {
    
            private transient AutowireCapableBeanFactory beanFactory;
    
            @Override
            public void setApplicationContext(final ApplicationContext context) {
                beanFactory = context.getAutowireCapableBeanFactory();
            }
    
            /**
             * 将job实例交给spring ioc托管
             * 我们在job实例实现类内可以直接使用spring注入的调用被spring ioc管理的实例
             * @param bundle
             * @return
             * @throws Exception
             */
            @Override
            protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
                final Object job = super.createJobInstance(bundle);
                /**
                 * 将job实例交付给spring ioc
                 */
                beanFactory.autowireBean(job);
                return job;
            }
        }
    
        /**
         * 配置任务工厂实例
         * @param applicationContext spring上下文实例
         * @return
         */
        @Bean
        public JobFactory jobFactory(ApplicationContext applicationContext)
        {
            /**
             * 采用自定义任务工厂 整合spring实例来完成构建任务
             * see {@link AutowiringSpringBeanJobFactory}
             */
            AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
            jobFactory.setApplicationContext(applicationContext);
            return jobFactory;
        }
    
        /**
         * 配置任务调度器
         * 使用项目数据源作为quartz数据源
         * @param jobFactory 自定义配置任务工厂
         * @param dataSource 数据源实例
         * @return
         * @throws Exception
         */
        @Bean(destroyMethod = "destroy",autowire = Autowire.NO)
        public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory, DataSource dataSource) throws Exception
        {
            SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
            //将spring管理job自定义工厂交由调度器维护
            schedulerFactoryBean.setJobFactory(jobFactory);
            //设置覆盖已存在的任务
            schedulerFactoryBean.setOverwriteExistingJobs(true);
            //项目启动完成后,等待2秒后开始执行调度器初始化
            schedulerFactoryBean.setStartupDelay(2);
            //设置调度器自动运行
            schedulerFactoryBean.setAutoStartup(true);
            //设置数据源,使用与项目统一数据源
            schedulerFactoryBean.setDataSource(dataSource);
            //设置上下文spring bean name
            schedulerFactoryBean.setApplicationContextSchedulerContextKey("applicationContext");
            //设置配置文件位置
            schedulerFactoryBean.setConfigLocation(new ClassPathResource("/quartz.properties"));
            return schedulerFactoryBean;
        }
    }

    AutowiringSpringBeanJobFactory

    可以看到上面配置类中,AutowiringSpringBeanJobFactory我们继承了SpringBeanJobFactory类,并且通过实现ApplicationContextAware接口获取ApplicationContext设置方法,通过外部实例化时设置ApplicationContext实例对象,在createJobInstance方法内,我们采用AutowireCapableBeanFactory来托管SpringBeanJobFactory类中createJobInstance方法返回的定时任务实例,这样我们就可以在定时任务类内使用Spring Ioc相关的注解进行注入业务逻辑实例了。

    JobFactory

    任务工厂是在本章配置调度器时所需要的实例,我们通过jobFactory方法注入ApplicationContext实例,来创建一个AutowiringSpringBeanJobFactory对象,并且将对象实例托管到Spring Ioc容器内。

    SchedulerFactoryBean

    我们本章采用的是项目内部数据源的方式来设置调度器的jobSotre,官方quartz有两种持久化的配置方案。

    第一种:采用quartz.properties配置文件配置独立的定时任务数据源,可以与使用项目的数据库完全独立。
    第二种:采用与创建项目统一个数据源,定时任务持久化相关的表与业务逻辑在同一个数据库内。

    可以根据实际的项目需求采取不同的方案,我们本章主要是通过第二种方案来进行讲解,在上面配置类中可以看到方法schedulerFactoryBean内自动注入了JobFactory实例,也就是我们自定义的AutowiringSpringBeanJobFactory任务工厂实例,另外一个参数就是DataSource,在我们引入spring-starter-data-jpa依赖后会根据application.yml文件内的数据源相关配置自动实例化DataSource实例,这里直接注入是没有问题的。

    我们通过调用SchedulerFactoryBean对象的setConfigLocation方法来设置quartz定时任务框架的基本配置,配置文件所在位置:resources/quartz.properties => classpath:/quartz.properties下。

    注意:quartz.properties配置文件一定要放在classpath下,放在别的位置有部分功能不会生效。

    下面我们来看下quartz.properties文件内的配置,如下所示:

    #调度器实例名称
    org.quartz.scheduler.instanceName = quartzScheduler
    
    #调度器实例编号自动生成
    org.quartz.scheduler.instanceId = AUTO
    
    #持久化方式配置
    org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
    
    #持久化方式配置数据驱动,MySQL数据库
    org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
    
    #quartz相关数据表前缀名
    org.quartz.jobStore.tablePrefix = QRTZ_
    
    #开启分布式部署
    org.quartz.jobStore.isClustered = true
    #配置是否使用
    org.quartz.jobStore.useProperties = false
    
    #分布式节点有效性检查时间间隔,单位:毫秒
    org.quartz.jobStore.clusterCheckinInterval = 20000
    
    #线程池实现类
    org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
    
    #执行最大并发线程数量
    org.quartz.threadPool.threadCount = 10
    
    #线程优先级
    org.quartz.threadPool.threadPriority = 5
    
    #配置为守护线程,设置后任务将不会执行
    #org.quartz.threadPool.makeThreadsDaemons=true
    
    #配置是否启动自动加载数据库内的定时任务,默认true
    org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true

    由于我们下一章需要做分布式多节点自动交付高可用,本章的配置文件加入了分布式相关的配置。
    在上面配置中org.quartz.jobStore.classorg.quartz.jobStore.driverDelegateClass是定时任务持久化的关键配置,配置了数据库持久化定时任务以及采用MySQL数据库进行连接,当然这里我们也可以配置其他的数据库,如下所示:
    PostgreSQLorg.quartz.impl.jdbcjobstore.PostgreSQLDelegate
    Sybase : org.quartz.impl.jdbcjobstore.SybaseDelegate
    MSSQL : org.quartz.impl.jdbcjobstore.MSSQLDelegate
    HSQLDB : org.quartz.impl.jdbcjobstore.HSQLDBDelegate
    Oracle : org.quartz.impl.jdbcjobstore.oracle.OracleDelegate

    org.quartz.jobStore.tablePrefix属性配置了定时任务数据表的前缀,在quartz官方提供的创建表SQL脚本默认就是qrtz_,在对应的XxxDelegate驱动类内也是使用的默认值,所以这里我们如果修改表名前缀,配置可以去掉。

    org.quartz.jobStore.isClustered属性配置了开启定时任务分布式功能,再开启分布式时对应属性org.quartz.scheduler.instanceId 改成Auto配置即可,实例唯一标识会自动生成,这个标识具体生成的内容,我们一会在运行的控制台就可以看到了,定时任务分布式准备好后会输出相关的分布式节点配置信息。

    创建表SQL会在本章源码resources目录下,源码地址https://gitee.com/hengboy/spring-boot-chapter

    准备测试

    我们先来创建一个简单的商品数据表,建表SQL如下所示:

    DROP TABLE IF EXISTS `basic_good_info`;
    CREATE TABLE `basic_good_info` (
      `BGI_ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '商品编号',
      `BGI_NAME` varchar(20) DEFAULT NULL COMMENT '商品名称',
      `BGI_PRICE` decimal(8,2) DEFAULT NULL COMMENT '单价',
      `BGI_UNIT` varchar(10) DEFAULT NULL COMMENT '单位',
      PRIMARY KEY (`BGI_ID`)
    ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8 COMMENT='商品基本信息';

    GoodEntity

    我们先来针对表basic_good_info创建一个实体,并且添加JPA相关的配置,如下所示:

    package com.hengyu.chapter39.good.entity;
    
    import lombok.Data;
    
    import javax.persistence.*;
    import java.math.BigDecimal;
    
    /**
     * ========================
     *
     * @author 恒宇少年
     * Created with IntelliJ IDEA.
     * Date:2017/11/5
     * Time:14:59
     * 码云:http://git.oschina.net/jnyqy
     * ========================
     */
    @Entity
    @Table(name = "basic_good_info")
    @Data
    public class GoodInfoEntity
    {
        /**
         * 商品编号
         */
        @Id
        @GeneratedValue
        @Column(name = "bgi_id")
        private Long id;
        /**
         * 商品名称
         */
        @Column(name = "bgi_name")
        private String name;
        /**
         * 商品单位
         */
        @Column(name = "bgi_unit")
        private String unit;
        /**
         * 商品单价
         */
        @Column(name = "bgi_price")
        private BigDecimal price;
    }

    下面我们根据商品实体来创建JPA接口,如下所示:

    /**
     * ========================
     * Created with IntelliJ IDEA.
     * Date:2017/11/5
     * Time:14:55
     * 码云:http://git.oschina.net/jnyqy
     * ========================
     * @author 恒宇少年
     */
    public interface GoodInfoRepository
        extends JpaRepository<GoodInfoEntity,Long>
    {
    }

    接下来我们再来添加一个商品添加的控制器方法,如下所示:

    /**
     * ========================
     *
     * @author 恒宇少年
     * Created with IntelliJ IDEA.
     * Date:2017/11/5
     * Time:15:02
     * 码云:http://git.oschina.net/jnyqy
     * ========================
     */
    @RestController
    @RequestMapping(value = "/good")
    public class GoodController
    {
        /**
         * 商品业务逻辑实现
         */
        @Autowired
        private GoodInfoService goodInfoService;
        /**
         * 添加商品
         * @return
         */
        @RequestMapping(value = "/save")
        public Long save(GoodInfoEntity good) throws Exception
        {
            return goodInfoService.saveGood(good);
        }
    }

    在请求商品添加方法时,我们调用了GoodInfoService内的saveGood方法,传递一个商品的实例作为参数。我们接下来看看该类内相关代码,如下所示:

    /**
     * 商品业务逻辑
     * ========================
     *
     * @author 恒宇少年
     * Created with IntelliJ IDEA.
     * Date:2017/11/5
     * Time:15:04
     * 码云:http://git.oschina.net/jnyqy
     * ========================
     */
    @Service
    @Transactional(rollbackFor = Exception.class)
    public class GoodInfoService
    {
        /**
         * 注入任务调度器
         */
        @Autowired
        private Scheduler scheduler;
        /**
         * 商品数据接口
         */
        @Autowired
        private GoodInfoRepository goodInfoRepository;
    
        /**
         * 保存商品基本信息
         * @param good 商品实例
         * @return
         */
        public Long saveGood(GoodInfoEntity good) throws Exception
        {
            goodInfoRepository.save(good);
            return good.getId();
        }

    我们只是作为保存商品的操作,下面我们来模拟一个需求,在商品添加完成后1分钟我们通知后续的逻辑进行下一步处理,同时开始商品库存定时检查的任务。

    定义商品添加定时任务

    我们先来创建一个任务实例,并且继承org.springframework.scheduling.quartz.QuartzJobBean抽象类,重写父抽象类内的executeInternal方法来实现任务的主体逻辑。如下所示:

    /**
     * 商品添加定时任务实现类
     * ========================
     * Created with IntelliJ IDEA.
     * User:恒宇少年
     * Date:2017/11/5
     * Time:14:47
     * 码云:http://git.oschina.net/jnyqy
     * ========================
     * @author 恒宇少年
     */
    public class GoodAddTimer
        extends QuartzJobBean
    {
        /**
         * logback
         */
        static Logger logger = LoggerFactory.getLogger(GoodAddTimer.class);
        /**
         * 定时任务逻辑实现方法
         * 每当触发器触发时会执行该方法逻辑
         * @param jobExecutionContext 任务执行上下文
         * @throws JobExecutionException
         */
        @Override
        protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
            logger.info("商品添加完成后执行任务,任务时间:{}",new Date());
        }

    在任务主体逻辑内,我们只是做了一个简单的输出任务执行的时间,下面我们再来创建库存定时检查任务。

    定义商品库存检查任务

    同样需要继承org.springframework.scheduling.quartz.QuartzJobBean抽象类实现抽象类内的executeInternal方法,如下所示:

    /**
     * 商品库存检查定时任务
     * ========================
     *
     * @author 恒宇少年
     * Created with IntelliJ IDEA.
     * Date:2017/11/5
     * Time:15:47
     * 码云:http://git.oschina.net/jnyqy
     * ========================
     */
    public class GoodStockCheckTimer
        extends QuartzJobBean
    {
        /**
         * logback
         */
        static Logger logger = LoggerFactory.getLogger(GoodStockCheckTimer.class);
    
        @Override
        protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
            logger.info("执行库存检查定时任务,执行时间:{}",new Date());
        }
    }

    都是简单的做了下日志的输出,下面我们需要重构GoodInfoService内的saveGood方法,对应的添加上面两个任务的创建。

    设置商品添加任务到调度器

    GoodInfoService类内添加buildCreateGoodTimer方法用于实例化商品添加任务,如下所示:

    /**
         * 构建创建商品定时任务
         */
        public void buildCreateGoodTimer() throws Exception
        {
            //设置开始时间为1分钟后
            long startAtTime = System.currentTimeMillis() + 1000 * 60;
            //任务名称
            String name = UUID.randomUUID().toString();
            //任务所属分组
            String group = GoodAddTimer.class.getName();
            //创建任务
            JobDetail jobDetail = JobBuilder.newJob(GoodAddTimer.class).withIdentity(name,group).build();
            //创建任务触发器
            Trigger trigger = TriggerBuilder.newTrigger().withIdentity(name,group).startAt(new Date(startAtTime)).build();
            //将触发器与任务绑定到调度器内
            scheduler.scheduleJob(jobDetail, trigger);
        }

    在上面方法中我们定义的GoodAddTimer实例只运行一次,在商品添加完成后延迟1分钟进行调用任务主体逻辑。

    其中任务的名称以及任务的分组是为了区分任务做的限制,在同一个分组下如果加入同样名称的任务,则会提示任务已经存在,添加失败的提示。

    我们通过JobDetail来构建一个任务实例,设置GoodAddTimer类作为任务运行目标对象,当任务被触发时就会执行GoodAddTimer内的executeInternal方法。

    一个任务需要设置对应的触发器,触发器也分为很多种,该任务中我们并没有采用cron表达式来设置触发器,而是调用startAt方法设置任务开始执行时间。

    最后将任务以及任务的触发器共同交付给任务调度器,这样就完成了一个任务的设置。

    设置商品库存检查到任务调度器

    GoodInfoService类内添加buildGoodStockTimer方法用于实例化商品添加任务,如下所示:

    /**
         * 构建商品库存定时任务
         * @throws Exception
         */
        public void buildGoodStockTimer() throws Exception
        {
            //任务名称
            String name = UUID.randomUUID().toString();
            //任务所属分组
            String group = GoodStockCheckTimer.class.getName();
    
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/30 * * * * ?");
            //创建任务
            JobDetail jobDetail = JobBuilder.newJob(GoodStockCheckTimer.class).withIdentity(name,group).build();
            //创建任务触发器
            Trigger trigger = TriggerBuilder.newTrigger().withIdentity(name,group).withSchedule(scheduleBuilder).build();
            //将触发器与任务绑定到调度器内
            scheduler.scheduleJob(jobDetail, trigger);
        }

    该任务的触发器我们采用了cron表达式来设置,每隔30秒执行一次任务主体逻辑。

    任务触发器在创建时cron表达式可以搭配startAt方法来同时使用。

    下面我们修改GoodInfoService内的saveGood方法,分别调用设置任务的两个方法,如下所示:

    /**
         * 保存商品基本信息
         * @param good 商品实例
         * @return
         */
        public Long saveGood(GoodInfoEntity good) throws Exception
        {
            goodInfoRepository.save(good);
            //构建创建商品定时任务
            buildCreateGoodTimer();
            //构建商品库存定时任务
            buildGoodStockTimer();
            return good.getId();
        }

    下面我们就来测试下任务是否可以顺序的被持久化到数据库,并且是否可以在重启服务后执行重启前添加的任务。

    测试

    下面我们来启动项目,启动成功后,我们来查看控制台输出的分布式节点的信息,如下所示:

    2017-11-05 18:09:40.052  INFO 7708 --- [           main] c.hengyu.chapter39.Chapter39Application  : 【【【【【【定时任务分布式节点 - 1 已启动】】】】】】
    2017-11-05 18:09:42.005  INFO 7708 --- [lerFactoryBean]] o.s.s.quartz.SchedulerFactoryBean        : Starting Quartz Scheduler now, after delay of 2 seconds
    2017-11-05 18:09:42.027  INFO 7708 --- [lerFactoryBean]] o.s.s.quartz.LocalDataSourceJobStore     : ClusterManager: detected 1 failed or restarted instances.
    2017-11-05 18:09:42.027  INFO 7708 --- [lerFactoryBean]] o.s.s.quartz.LocalDataSourceJobStore     : ClusterManager: Scanning for instance "yuqiyu1509876084785"'s failed in-progress jobs.
    2017-11-05 18:09:42.031  INFO 7708 --- [lerFactoryBean]] o.s.s.quartz.LocalDataSourceJobStore     : ClusterManager: ......Freed 1 acquired trigger(s).
    2017-11-05 18:09:42.033  INFO 7708 --- [lerFactoryBean]] org.quartz.core.QuartzScheduler          : Scheduler schedulerFactoryBean_$_yuqiyu1509876579404 started.

    定时任务是在项目启动后2秒进行执行初始化,并且通过ClusterManager来完成了instance的创建,创建的节点唯一标识为yuqiyu1509876084785

    编写商品控制器请求方法测试用例,如下所示:

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class Chapter39ApplicationTests {
        /**
         * 模拟mvc测试对象
         */
        private MockMvc mockMvc;
    
        /**
         * web项目上下文
         */
        @Autowired
        private WebApplicationContext webApplicationContext;
    
        /**
         * 所有测试方法执行之前执行该方法
         */
        @Before
        public void before() {
            //获取mockmvc对象实例
            mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
        }
    
        /**
         * 测试添加商品
         * @throws Exception
         */
        @Test
        public void addGood() throws Exception
        {
            MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post("/good/save")
                    .param("name","西瓜")
                    .param("unit","斤")
                    .param("price","12.88")
            )
                    .andDo(MockMvcResultHandlers.print())
                    .andExpect(MockMvcResultMatchers.status().is(200))
                    .andReturn();
            result.getResponse().setCharacterEncoding("UTF-8");
            System.out.println(result.getResponse().getContentAsString());
        }

    测试用例相关文章请访问第三十五章:SpringBoot与单元测试的小秘密,我们来执行addGood测试方法,查看控制台输出,如下所示:

    ....省略部分输出
    Hibernate: insert into basic_good_info (bgi_name, bgi_price, bgi_unit) values (?, ?, ?)
    2017-11-05 18:06:35.699 TRACE 7560 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [西瓜]
    2017-11-05 18:06:35.701 TRACE 7560 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [NUMERIC] - [12.88]
    2017-11-05 18:06:35.701 TRACE 7560 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [VARCHAR] - [斤]
    ....省略部分输出
    8
    ....省略部分输出

    可以看到我们的商品已被成功的写入到数据库并且输出的主键值,我们的任务是否也成功的被写入到数据库了呢?我们来查看qrtz_job_details表内任务列表,如下所示:

    schedulerFactoryBean    7567c9d7-76f5-47f3-bc5d-b934f4c1063b    com.hengyu.chapter39.timers.GoodStockCheckTimer     com.hengyu.chapter39.timers.GoodStockCheckTimer 0   0   0   0   0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787000737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F40000000000010770800000010000000007800
    schedulerFactoryBean    e5e08ab0-9be3-43fb-93b8-b9490432a5d7    com.hengyu.chapter39.timers.GoodAddTimer        com.hengyu.chapter39.timers.GoodAddTimer    0   0   0   0   0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787000737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F40000000000010770800000010000000007800
    

    任务已经被成功的持久化到数据库内,等待1分钟后查看控制台输出内容如下所示:

    2017-11-05 18:12:30.017  INFO 7708 --- [ryBean_Worker-1] c.h.c.timers.GoodStockCheckTimer         : 执行库存检查定时任务,执行时间:Sun Nov 05 18:12:30 CST 2017
    2017-11-05 18:13:00.009  INFO 7708 --- [ryBean_Worker-2] c.h.c.timers.GoodStockCheckTimer         : 执行库存检查定时任务,执行时间:Sun Nov 05 18:13:00 CST 2017
    2017-11-05 18:13:02.090  INFO 7708 --- [ryBean_Worker-3] c.hengyu.chapter39.timers.GoodAddTimer   : 商品添加完成后执行任务,任务时间:Sun Nov 05 18:13:02 CST 2017

    根据输出的内容来判定完全吻合我们的配置参数,库存检查为30秒执行一次,而添加成功后的提醒则是1分钟后执行一次。执行完成后就会被直接销毁,我们再来查看数据库表qrtz_job_details,这时就可以看到还剩下1个任务

    重启服务任务是否自动执行?

    下面我们把项目重启下,然后观察控制台的输出内容,如下所示:

    2017-11-05 18:15:54.018  INFO 7536 --- [           main] c.hengyu.chapter39.Chapter39Application  : 【【【【【【定时任务分布式节点 - 1 已启动】】】】】】
    2017-11-05 18:15:55.975  INFO 7536 --- [lerFactoryBean]] o.s.s.quartz.SchedulerFactoryBean        : Starting Quartz Scheduler now, after delay of 2 seconds
    2017-11-05 18:15:56.000  INFO 7536 --- [lerFactoryBean]] org.quartz.core.QuartzScheduler          : Scheduler schedulerFactoryBean_$_yuqiyu1509876953202 started.
    2017-11-05 18:16:15.999  INFO 7536 --- [_ClusterManager] o.s.s.quartz.LocalDataSourceJobStore     : ClusterManager: detected 1 failed or restarted instances.
    2017-11-05 18:16:16.000  INFO 7536 --- [_ClusterManager] o.s.s.quartz.LocalDataSourceJobStore     : ClusterManager: Scanning for instance "yuqiyu1509876579404"'s failed in-progress jobs.
    2017-11-05 18:16:16.005  INFO 7536 --- [_ClusterManager] o.s.s.quartz.LocalDataSourceJobStore     : ClusterManager: ......Freed 1 acquired trigger(s).
    2017-11-05 18:16:16.041  INFO 7536 --- [ryBean_Worker-1] c.h.c.timers.GoodStockCheckTimer         : 执行库存检查定时任务,执行时间:Sun Nov 05 18:16:16 CST 2017

    可以看到成功的自动执行了我们在重启之前配置的任务。

    总结

    本章主要讲解了SpringBoot整合quartz定时任务框架,完成了分布式单节点任务持久化,下一章我们会讲解任务参数传递以及分布式多节点任务自动负载。

    本章源码已经上传到码云:
    SpringBoot配套源码地址:https://gitee.com/hengboy/spring-boot-chapter
    SpringCloud配套源码地址:https://gitee.com/hengboy/spring-cloud-chapter
    SpringBoot相关系列文章请访问:目录:SpringBoot学习目录
    QueryDSL相关系列文章请访问:QueryDSL通用查询框架学习目录
    SpringDataJPA相关系列文章请访问:目录:SpringDataJPA学习目录
    SpringBoot相关文章请访问:目录:SpringBoot学习目录,感谢阅读!
    欢迎加入QQ技术交流群,共同进步。
    QQ技术交流群

    展开全文
  • 在m个节点的分布式计算系统中,有一批任务需要执行,每个任务需要的时间是array[i],每个节点...请问任务完成的最短时间? import java.util.Scanner; public class ArrageCom { static int [] stack; static ...

    在m个节点的分布式计算系统中,有一批任务需要执行,每个任务需要的时间是array[i],每个节点同一时间只能执行一个任务,每个节点只能执行连续的任务,例如 i, i+1, i+2但是不能执行 i, i+2。请问任务完成的最短时间?

    import java.util.Scanner;
    
    public class ArrageCom {
    	static int [] stack; 
    	static int [] stack_book;
    	static int top;
    	static int [] a = {1, 1, 1, 0, 0};
    	static int[] array = {1, 5, 3, 4, 2};
    	
    	static int schedule(int k) {
    		int time = 0;
    		time += array[k];
    		for(int i=k+1;i<array.length;i++) {
    			if(a[i] == 0) {
    				time += array[i];
    			}else {
    				break;
    			}
    		}
    		return time;
    	}
    	
    	public static void printStack(int size) {
    		for(int i=0;i<size;i++) {
    			//System.out.print(stack[i] + " ");
    			System.out.print(a[stack[i]] + " ");
    		}
    		System.out.println();
    	}
    	
    	public static void comDup(int str[], int size, int cur) {
    		if(cur == size) {
    			printStack(size);
    			return;
    		}
    		
    		for(int i=0;i<str.length;i++) {
    			if(stack_book[i] == 0) {
    				stack[top] = str[i];
    				stack_book[i] = 1;
    				top++;
    				comDup(str, size, cur+1);
    				top--;
    				stack_book[i] = 0;
    			}
    
    		}
    		
    		
    	}
    	
    	public static boolean selectCom() {
    		int m = 3, n = 5;
    		boolean flag = false;
    
    		for(int t=0;t<a.length;t++) {
    			if(a[t] == 1) {
    				System.out.print(schedule(t) + " ");
    			}
    			//System.out.print(a[t] + " ");
    		}
    		System.out.println();
    		do {
    			flag = false;
    			for(int i=1;i<n-1;i++) {
    				if(a[i]==1 && a[i+1]==0) {
    					a[i] = 0;
    					a[i+1] = 1;
    					
    					int count = 1;
    					for(int j=1;j<i;j++) {
    						if(a[j]==1) {
    							count++;
    						}
    					}
    					for(int j=1;j<count;j++) {
    						a[j] = 1;
    					}
    					for(int j=count;j<i;j++) {
    						a[j] = 0;
    					}
    					flag = true;
    					
    					for(int t=0;t<a.length;t++) {
    						if(a[t] == 1) {
    							System.out.print(schedule(t) + " ");
    						}
    						//System.out.print(a[t] + " ");
    					}
    					System.out.println();
    					
    					break;
    				}
    			}
    		}while(flag);
    		
    		return flag;
    	}
    	
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    //		Scanner in = new Scanner(System.in);
    //		int size = in.nextInt();
    //		stack = new int[size];
    //		stack_book = new int[size];
    //		for(int i=0;i<size;i++) {
    //			stack_book[i] = 0;
    //		}
    //		top = 0;
    //		int [] combStr = new int[size];
    //		for(int i=0;i<size;i++) {
    //			combStr[i] = i;
    //		}
    		//comDup(combStr, size, 0);
    		boolean flag = false;
    		flag = selectCom();
    	}
    
    }
    
    
    展开全文
  • 我们在之前有讲过SpringBoot是已经集成了定时任务的,详见:第二十六章:SpringBoot使用@Scheduled创建定时任务,那么我们本章将会采用外置的quartz定时任务框架来完成定时任务的分布式单节点持久化,我们为什么要...
  • 我们在之前有讲过SpringBoot是已经集成了定时任务的,那么我们本章将会采用外置的quartz定时任务框架来完成定时任务的分布式单节点持久化,我们为什么要持久化定时任务呢?在一些项目中定时任务可能是必不可少的,...
  •  (一) 马上做一个分布式漏洞扫描与攻击的项目,这段时间一直选技术路线以及做大量的demo。...WebService基于SOAP协议通信我觉得太麻烦,因为我的需求是主控端分发任务队列给节点节点执行,完成之后回
  • 针对当前Hadoop集群固有的任务级调度分配方法在运行中存在的负载分布不均的现象,着重对集群节点的执行能力进行了分析与...实验结果表明集群的总任务完成时间明显地缩减,各节点的负载更加均衡,节点资源的利用更为合理.
  • 一、前言 ...老办法是打时间差,按照一般上传耗时3个小时计算,所有任务的起点都配置在凌晨4点开始,这在大多数时候都能生效,但在一些流量特别大的日子就会异常,数据没传完,但任务已经开始执行...
  • 对高性能计算集群在运行过程中如何通过关闭闲置节点来实现...模拟实验表明,基于任务负载监测的节点启停机制能够有效地自动启停系统中闲置的节点,从而降低系统功耗,并且对系统中作业的整体完成时间基本不造成影响。
  • 但是,与上个月相比,开放挖矿以来MATRIX出块效率有所降低,有大量矿工在完成抵押之后节点并没有正常启动,这导致节点当选后无法正常工作,整个系统需要耗费时间确认节点在线情况并重新分配任务,进一步致使出块效率...
  • 解题思路:先赋值后改变地址,先把该节点的下一个结点的数据赋值给该结点,然后再把指针指向下下个结点的地址,完成这两步之后,该结点就被下一结点覆盖了,完成了删除该结点的任务。 关键代码:
  • 说到工作高效,那不得不说一下这个公式 效率 = 时间 x 专注度 时间 时间是你为这件事情投入的...如果实在是有事情不得不终止这个任务,一定要把当前的节点记录下来,以便以后开始时能够更快的进入状态。 专注度 而专...
  • Treeview 添加节点

    2019-05-28 09:22:31
    本次任务完成时间:2019年5月19日~2019年5月28日 开发工具与关键技术: VS2015、C# NewModule 是一个list 集合。 里面放了多条数据。根据这些数据给treeview添加节点。 Digital 是根据NewModule集合截取每条数据的...
  • 拖动treeNode节点

    2019-06-28 10:18:36
    本次任务完成时间:2019年6月28日~2019年6月28日 开发工具与关键技术: VS2015、C# 想要在一个treeView实现拖拽功能,那么首先需要在窗体加载的时候,将treeView的AllowDrop设置为true(设置treeView控件接受用户...
  • 节点可以向另外的节点发送一条请求信息,另外的节点任务执行完成后返回结果信息。但是短时间任务这很有用,比如讲四元数姿态转换成欧拉角。但是,对于需要长时间完成任务,而我们又像知道任务执行的中间状态。...
  • 新领导给的小任务,业余时间完成,嗯,业余时间。还好任务很简单:从XML文件中挑选出具有某个标识的节点。核心功能核心功能:解析xml,用正则匹配节点的一个属性,如果匹配成功,保存该节点。xml解析库的选择python...
  • 所有节点共同合作完成一个共同的任务。传感器节点硬件通信架构的设计必须充分考虑电池方面的限制。一般情况下,节点支持以下功能:动态配置,以支持多种网络功能;节点可以动态配置成网关,普通节点等;远程可编程,...
  • 获取treeview中勾选节点

    千次阅读 2019-05-17 15:17:15
    本次任务完成时间:2019年5月9日~2019年5月17日 开发工具与关键技术: VS2015、C# 1.这是一个只有四级的treeview.我是通过根节点去遍历它的所有子节点,再根据它的子 节点状态是否为true来判断出勾选状态,如果状态...
  • 多核划分方法-节点优先值计算实时系统节点优先级计算方式JAVA实现 ...依赖关系:任务 J 依赖于任务 I, 是指任务 I 完成后,任务 J 才能开始执行,此时称任务 I 称为任务 J 的前驱(Predecessor),任务 J 为任务 I 的
  • 简称WSNs)是由部署在监测区域内大量的廉价微型传感器节点组成,通过无线通信形成一个多跳自组织网络系统,能够实时监测、感知和采集网络分布区域内监视对象的各种信息,并加以处理,完成数据采集和监测任务。...
  • threadpool是为了解决这个问题而添加的,但是我很难等到任务完成后再进入下一个迭代。只有在完成一个节点的所有任务后,我们才能继续。我们需要所有任务的结果,然后才能按节点搜索下一个最近的任务。我试过执行...
  • 能实时的完成上述业务的处理,不存在定时器那样到了定时器执行的时间,才会执行业务逻辑 流程 消息生产者首先会发送消息到延迟队列上(register_delay_queue),并会给消息设置一个过期时间,过期时间到了后,消息会转发到...
  • 针对异构Hadoop云计算平台中任务调度问题进行了研究,并分析了现有推测执行调度和LATE调度...实验结果表明,提出的方案能够为异构Hadoop平台合理分配任务,有效降低了任务完成时间和响应延迟,同时提高了平台吞吐量。
  • 提出一种基于超级节点模式的网格管理模型,引入市场经济机制,允许网格用户提出任务的截止完成时间、费用上限以及时间、费用偏好参数,并作为用户QoS参数给出对应的网格复合任务调度算法。根据此算法,利用层次颜色...
  • 集群、分布式、节点

    2020-11-02 22:29:22
    通俗来说,不同的业务,分布在不同的地方,优点在于以缩短单个任务的执行时间来提升效率,且便于扩展,复用性高。 关于节点 节点就相当于集群中的一个服务器。 集群与分布式的区别 1.集群是个物理形态,分布式是个...
  • 虚拟平台中解决负载均衡的方法中,主要创新点体现在:考虑到动态负载均衡的实现,综合了易于实现而且性能优良的常见的负载均衡调度策略,提出以“单位时间任务的到达数/单位时间任务完成数”、“CPU利用率*连接数...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 672
精华内容 268
关键字:

任务完成时间节点