最近接了一个新的需求,需要使用到定时任务,由于我们的系统是分布式的,所以Spring
自带的定时任务无法满足我们当前系统对定时任务的需要。我们需要一个能够便捷、高可用的分布式定时任务框架。比较了几个主流的分布式框架,我最终pick了当当开源项目ElasticJob
。
ElasticJob
是一种分布式调度解决方案,由两个单独的项目ElasticJob-Lite
和ElasticJob-Cloud
组成。
通过灵活的调度,资源管理和作业管理功能,它创建了适合Internet场景的分布式调度解决方案,并通过开放式架构设计提供了多元化的作业生态系统。它为每个项目使用统一的作业API。开发人员只需要一次编写代码,就可以随意部署。
ElasticJob-Lite
和ElasticJob-Cloud
存在一定的区别
ElasticJob提供了三种作业类型:
DataFlow
类型用于处理数据流,它又提供2种作业类型,分别是ThroughputDataFlow
和SequenceDataFlow
。需要继承相应的抽象类。Script
类型用于处理脚本,可直接使用,无需编码。Simple
类型作业意为简单实现,未经任何封装的类型。需要继承AbstractSimpleElasticJob
,该类只提供了一个方法用于覆盖,此方法将被定时执行。用于执行普通的定时任务,与Quartz
原生接口相似,只是增加了弹性扩缩容和分片等功能。Simple
类型作业就可以满足我们队分布式定时任务的要求。elastic-job-lite-console
是ElasticJob
提供的可视化运维平台:elastic-job-lite-console安装与下载
Elastic Job
并无直接关系,是通过读取Elastic Job
的注册中心数据展现作业状态,或更新注册中心数据修改全局配置。Zookeeper address
maven依赖
<dependency>
<groupId>com.dangdang</groupId>
<artifactId>elastic-job-lite-core</artifactId>
<version>2.1.4</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
<exclusion>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.dangdang</groupId>
<artifactId>elastic-job-lite-spring</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.7</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
SimpleProducerJob.class:一个用于测试的定时任务
@Slf4j
@Service
public class SimpleProducerJob implements SimpleJob {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public void execute(ShardingContext shardingContext) {
log.info("当前时间为"+df.format(System.currentTimeMillis()));
}
}
Elastic-job.xml配置文件:用于配置作业注册中心和定时作业
sharding-total-count
、sharding-item-parameters
这两个参数用于配置Elastic-job
的分片。Elastic-job
借助了分片机制和 失效转移机制可以通过异步并行的方式解决大批数据进行推送时的性能问题。Elastic-job.xml
更多配置可参考:Elastic-Job开发指南<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:reg="http://www.dangdang.com/schema/ddframe/reg"
xmlns:job="http://www.dangdang.com/schema/ddframe/job"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.dangdang.com/schema/ddframe/reg
http://www.dangdang.com/schema/ddframe/reg/reg.xsd
http://www.dangdang.com/schema/ddframe/job
http://www.dangdang.com/schema/ddframe/job/job.xsd
">
<!--配置作业注册中心 -->
<reg:zookeeper id="regCenter" server-lists="127.0.0.1:2181" namespace="spring-dubbo-producer" base-sleep-time-milliseconds="1000" max-sleep-time-milliseconds="3000" max-retries="3" />
<!--样例job-->
<job:simple id="simpleProducerJob" class="com.luo.producer.task.SimpleProducerJob" registry-center-ref="regCenter" cron="*/5 * * * * ?" sharding-total-count="2" sharding-item-parameters="0=A,1=B" overwrite="true"/>
</beans>
cron
即时间表达式,常用的如下:
每隔5秒执行一次:*/5 * * * * ?
每隔1分钟执行一次:0 */1 * * * ?
每天23点执行一次:0 0 23 * * ?
每天凌晨1点执行一次:0 0 1 * * ?
每月1号凌晨1点执行一次:0 0 1 1 * ?
每月最后一天23点执行一次:0 0 23 L * ?
每周星期天凌晨1点实行一次:0 0 1 ? * L
在26分、29分、33分执行一次:0 26,29,33 * * * ?
每天的0点、13点、18点、21点都执行一次:0 0 0,13,18,21 * * ?
验证一:启动项目
启动项目后可以在elastic-job-lite-console
运维平台我们可以看到我们注册的 SimpleProducerJob
在控制台我们可以看定时任务的日志打印信息
验证二:采用主动的方式去触发任务
一般情况下,设置节点jobName/instance/实例ID
的值为TRIGGER
时,可以触发实例立即执行任务,但任务正在执行中除外。事件通知后,会把该节点的值重置为空("")。
@Slf4j
public class TriggerJobUtils {
public static boolean triggerElasticNodesJob(String jobName){
try{
JobNodePath jobNodePath = new JobNodePath(jobName);
Iterator iterator = JobRegistry.getInstance().getRegCenter(jobName).getChildrenKeys(jobNodePath.getInstancesNodePath()).iterator();
while(iterator.hasNext()) {
String each = (String)iterator.next();
log.info("触发的elastic-job 的节点是:{}",each);
JobRegistry.getInstance().getRegCenter(jobName).persist(jobNodePath.getInstanceNodePath(each), "TRIGGER");
}
}catch (Exception e){
log.error("触发elastic-job:{}失败",jobName);
return false;
}
return true;
}
}
UserTest.class:测试类
@RunWith(SpringRunner.class)
@SpringBootTest(classes = SpringDubboProducerAPP.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class UserTest {
@Test
public void seshi(){
System.out.println("start 主动主动触发定时任务");
TriggerJobUtils.triggerElasticNodesJob("simpleProducerJob");
System.out.println("end 主动主动触发定时任务");
}
}