一、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
整合方式可分为两种:xml文件中定义任务和通过注解的方式,由于脚手架使用springboot,所以应减少xml文件的使用,所以此处介绍通过注解的方式来实现;
1、pom文件
<dependency>
<groupId>com.dangdang</groupId>
<artifactId>elastic-job-lite-core</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>com.dangdang</groupId>
<artifactId>elastic-job-lite-spring</artifactId>
<version>2.1.4</version>
</dependency>
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两张表以及若干索引。
任务注册成功后 管理控制台的页面不再阐述。