Springboot使用注解的方式整合Elastic-Job

一、java有哪些定时任务的框架

单机

  • timer:是一个定时器类,通过该类可以为指定的定时任务进行配置。TimerTask类是一个定时任务类,该类实现了Runnable接口,缺点异常未检查会中止线程
  • ScheduledExecutorService:相对延迟或者周期作为定时任务调度,缺点没有绝对的日期或者时间
  • spring定时框架:配置简单功能较多,如果系统使用单机的话可以优先考虑spring定时器

分布

  • Quartz:Java事实上的定时任务标准。但Quartz关注点在于定时任务而非数据,并无一套根据数据处理而定制化的流程。虽然Quartz可以基于数据库实现作业的高可用,但缺少分布式并行调度的功能
  • TBSchedule:阿里早期开源的分布式任务调度系统。代码略陈旧,使用timer而非线程池执行任务调度。众所周知,timer在处理异常状况时是有缺陷的。而且TBSchedule作业类型较为单一,只能是获取/处理数据一种模式。还有就是文档缺失比较严重
  • elastic-job:当当开发的弹性分布式任务调度系统,功能丰富强大,采用zookeeper实现分布式协调,实现任务高可用以及分片,目前是版本2.15,并且可以支持云开发
  • Saturn:是唯品会自主研发的分布式的定时任务的调度平台,基于当当的elastic-job 版本1开发,并且可以很好的部署到docker容器上。
  • xxl-job: 是大众点评员工徐雪里于2015年发布的分布式任务调度平台,是一个轻量级分布式任务调度框架,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。

二、elastic-job   xxl-job 及 Quartz综合对比

Springboot使用注解的方式整合Elastic-Job_第1张图片

 

三、Springboot整合Elastic-Job

整合方式可分为两种:xml文件中定义任务和通过注解的方式,由于脚手架使用springboot,所以应减少xml文件的使用,所以此处介绍通过注解的方式来实现;

1、pom文件


    com.dangdang
    elastic-job-lite-core
    2.1.4



    com.dangdang
    elastic-job-lite-spring
    2.1.4

2、定义zookeeper(配置信息不再阐述)

import com.dangdang.ddframe.job.reg.zookeeper.ZookeeperConfiguration;
import com.dangdang.ddframe.job.reg.zookeeper.ZookeeperRegistryCenter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author anngiu
 * @description
 * @date 2019-01-29 11:09
 */
@Configuration
public class ElasticRegCenterConfig {

	//zookeeper地址信息(zookeeper要部署集群)
    @Value("${zookeeper.serverLists}")
    private String serverList;
	
	//命名空间
    @Value("${zookeeper.namespace}")
    private String namespace;
	
	//授权方式(zookeeper权限控制的一种)
    @Value("${zookeeper.digest}")
    private String digest;

    @Bean(initMethod = "init")
    public ZookeeperRegistryCenter regCenter() {
        ZookeeperConfiguration zookeeperConfiguration = new ZookeeperConfiguration(serverList, namespace);
        zookeeperConfiguration.setDigest(digest);
        return new ZookeeperRegistryCenter(zookeeperConfiguration);
    }

}

3、定义监听器,便于统计任务的执行时间

import com.dangdang.ddframe.job.executor.ShardingContexts;
import com.dangdang.ddframe.job.lite.api.listener.ElasticJobListener;
import com.finup.bestriver.utils.biz.DateUtils;
import lombok.extern.slf4j.Slf4j;

import java.util.Date;

/**
 * @author anngiu
 * @description
 * @date 2019-01-29 11:12
 */
@Slf4j
public class BRElasticJobListener implements ElasticJobListener {

    @Override
    public void beforeJobExecuted(ShardingContexts shardingContexts) {

        log.info("定时任务:===>{} 执行开始时间 {} <===",shardingContexts.getJobName(), DateUtils.dateFormat(new Date(),DateUtils.STYLE4));
    }

    @Override
    public void afterJobExecuted(ShardingContexts shardingContexts) {
        log.info("定时任务:===>{} 执行结束时间:{} <===",shardingContexts.getJobName(), DateUtils.dateFormat(new Date(),DateUtils.STYLE4));
    }
}

4、自定时任务注册注解

import java.lang.annotation.*;

/**
 * @author anngiu
 * @description
 * @date 2019-01-29 20:53
 */
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface ElasticSchedulerParam {

    /**
     * 任务名称
     * @return
     */
    String jobName();

    /**
     * cron表达式,用于控制作业触发时间
     * @return
     */
    String cron();

    /**
     * 分片参数
     * @return
     */
    String shardingItemParameters() default "";

    /**
     * 总分片数
     * @return
     */
    int shardingTotalCount() default 1;

    /**
     * 任务描述
     * @return
     */
    String description() default "";
    /**
     * 是否自动失效转移
     * @return
     */
    boolean misfire() default false;

    /**
     * 错过是否重执行
     * @return
     */
    boolean failover() default false;

    /**
     * 作业是否启动时禁止
     * @return
     */
    boolean disabled() default false;

    /**
     * 任务全路径
     * @return
     */
    String jobClassFullPath();
}

5、定义参数VO

import com.dangdang.ddframe.job.api.simple.SimpleJob;
import lombok.Data;

/**
 * @author anngiu
 * @description
 * @date 2019-01-30 09:13
 */
@Data
public class ElasticJobParamVo {

    private String jobName;

    private SimpleJob jobClass;

    private String cron;

    private int shardingTotalCount;

    private String description;

    private String shardingItemParameters;

    private String classFullPath;

    private boolean misfire;

    private boolean failover;

    private boolean disabled;
}

6、定义注解扫描方法

import com.dangdang.ddframe.job.api.simple.SimpleJob;
import com.finup.bestriver.annotation.ElasticSchedulerParam;
import com.finup.bestriver.handler.ElasticJobHandler;
import com.finup.bestriver.vo.ElasticJobParamVo;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;

/**
 * @author anngiu
 * @description
 * @date 2019-01-29 20:57
 */
@Component
public class ElasticSchedulerInit implements ApplicationContextAware, InitializingBean {

    private ApplicationContext applicationContext;

    @Autowired
    private ElasticJobHandler elasticJobHandler;

    @Override
    public void afterPropertiesSet(){
        registrJob(applicationContext);
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext=applicationContext;
    }

    private void registrJob(ApplicationContext applicationContext) {
        String[] beanNamesForAnnotation = applicationContext.getBeanNamesForAnnotation(ElasticSchedulerParam.class);
        for (String beanName : beanNamesForAnnotation) {
            Class handlerType = applicationContext.getType(beanName);
            Object bean = applicationContext.getBean(beanName);
            ElasticSchedulerParam annotation = AnnotationUtils.findAnnotation(handlerType, ElasticSchedulerParam.class);
            addJobToContext(annotation,bean);
        }
    }

    /**
     * 将任务添加到容器中
     * @param elasticScheduler
     * @param bean
     */
    private void addJobToContext(ElasticSchedulerParam elasticScheduler, Object bean) {
        ElasticJobParamVo elasticJobParamVo = new ElasticJobParamVo();
        elasticJobParamVo.setCron(elasticScheduler.cron());
        elasticJobParamVo.setJobName(elasticScheduler.jobName());
        elasticJobParamVo.setShardingItemParameters(elasticScheduler.shardingItemParameters());
        elasticJobParamVo.setShardingTotalCount(elasticScheduler.shardingTotalCount());
        elasticJobParamVo.setClassFullPath(elasticScheduler.jobClassFullPath());
        elasticJobParamVo.setJobClass((SimpleJob)bean);
        elasticJobParamVo.setDescription(elasticScheduler.description());
        elasticJobParamVo.setMisfire(elasticScheduler.misfire());
        elasticJobParamVo.setFailover(elasticScheduler.failover());
        elasticJobParamVo.setDisabled(elasticScheduler.disabled());
        elasticJobHandler.addJob(elasticJobParamVo);
    }
}

7、在任务bean对象初始化过程中加载到SpringJobScheduler中

import com.dangdang.ddframe.job.config.JobCoreConfiguration;
import com.dangdang.ddframe.job.config.simple.SimpleJobConfiguration;
import com.dangdang.ddframe.job.event.JobEventConfiguration;
import com.dangdang.ddframe.job.lite.api.listener.ElasticJobListener;
import com.dangdang.ddframe.job.lite.config.LiteJobConfiguration;
import com.dangdang.ddframe.job.lite.spring.api.SpringJobScheduler;
import com.dangdang.ddframe.job.reg.zookeeper.ZookeeperRegistryCenter;
import com.finup.bestriver.vo.ElasticJobParamVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * @author anngiu
 * @description 添加job方法
 * @date 2019-01-30 09:12
 */
@Component
public class ElasticJobHandler {

    @Autowired
    private ZookeeperRegistryCenter regCenter;

    @Resource
    private JobEventConfiguration jobEventConfiguration;

    @Resource
    private ElasticJobListener elasticJobListener;

    /**
     * 添加job到SpringJobScheduler
     * @param elasticJobParamVo
     */
    public void addJob(ElasticJobParamVo elasticJobParamVo){
        LiteJobConfiguration jobConfig = LiteJobConfiguration.newBuilder(new SimpleJobConfiguration(
            JobCoreConfiguration.newBuilder(elasticJobParamVo.getJobName(), elasticJobParamVo.getCron(), elasticJobParamVo.getShardingTotalCount())
                .shardingItemParameters(elasticJobParamVo.getShardingItemParameters())
                .description(elasticJobParamVo.getDescription())
                .misfire(elasticJobParamVo.isMisfire())
                .failover(elasticJobParamVo.isFailover())
                .jobProperties("job_exception_handler","com.finup.bestriver.handler.BestRiverExceptionHandler")
                .build()
			//此处的jobClass需要自己指定,如果通过jobClass.getCanonicalName()的方式,在项目重启时会报错(动态代理生成的job bean对象每次重启都会变化)。
            , elasticJobParamVo.getClassFullPath())
        ).overwrite(true)
                .disabled(elasticJobParamVo.isDisabled())
                .build();

        new SpringJobScheduler(elasticJobParamVo.getJobClass(), regCenter, jobConfig, jobEventConfiguration, elasticJobListener).init();
    }
}

BestRiverExceptionHandler为job异常处理器,对任务执行过程中出现的异常做统一处理,比如:打印日志、提醒。。。。。

8、使用注解

import com.dangdang.ddframe.job.api.ShardingContext;
import com.dangdang.ddframe.job.api.simple.SimpleJob;
import com.finup.bestriver.annotation.ElasticSchedulerParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * @author anngiu
 * @Description 
 * @date 2019年01月25日15:25:12
 */
@Component
@Slf4j
@ElasticSchedulerParam(jobName = "finupInfoUpdateJob",cron = "0 0 2 * * ?",description = "Springboot整合Elastic-Job",
        jobClassFullPath = "com.finup.bestriver.job.FinupInfoUpdateJob")
public class InfoUpdateJob implements SimpleJob {

    @Override
    public void execute(ShardingContext shardingContext) {
        
    }

}

 

另:Elastic-Job提供了事件追踪功能,可通过事件订阅的方式处理调度过程的重要事件,用于查询、统计和监控。Elastic-Job目前提供了基于关系型数据库两种事件订阅方式记录事件。我们只需要将添加如下配置即可:

import com.dangdang.ddframe.job.event.JobEventConfiguration;
import com.dangdang.ddframe.job.event.rdb.JobEventRdbConfiguration;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

/**
 * @author anngiu
 * @description
 * @date 2019-01-29 11:11
 */
@Configuration
public class ElasticJobConfig {
    
    @Bean
    public JobEventConfiguration jobEventConfiguration(@Qualifier("primaryDataSource")DataSource dataSource) {
        return new JobEventRdbConfiguration(dataSource);
    }
}

项目运行后,Elastic-Job会自动在指定的数据库中创建JOB_EXECUTION_LOG和JOB_STATUS_TRACE_LOG两张表以及若干索引。

任务注册成功后 管理控制台的页面不再阐述。

你可能感兴趣的:(分布式)