在工作中我们会遇到定时任务的需求(定时任务形式:每隔⼀定时间/特定某⼀时刻执⾏某个任务/逻辑)
什么是分布式任务调度?有两层含义
1)运⾏在分布式集群环境下的调度任务(同⼀个定时任务程序部署多份,只应该有⼀个定时任务在执⾏)
2)分布式调度—>定时任务的分布式—>定时任务的拆分(即为把⼀个⼤的作业任务拆分为多个⼩的作 业任务,多个实例同时执⾏)
Elastic-Job是当当⽹开源的⼀个分布式调度解决⽅案,基于Quartz⼆次开发的,由两个相互独⽴的⼦项 ⽬Elastic-Job-Lite和Elastic-Job-Cloud组成。我们要学习的是 Elastic-Job-Lite,它定位为轻量级⽆中⼼化解决⽅案,使⽤Jar包的形式提供分布式任务的协调服务,⽽Elastic-Job-Cloud⼦项⽬需要结合Mesos 以及Docker在云环境下使⽤。
Elastic-Job的github地址:https://github.com/elasticjob
1、分布式调度协调
在分布式环境中,任务能够按指定的调度策略执⾏,并且能够避免同⼀任务多实例重复执⾏
2、丰富的调度策略 基于成熟的定时任务作业框架Quartz cron表达式执⾏定时任务
3、弹性扩容缩容 当集群中增加某⼀个实例,它应当也能够被选举并执⾏任务;当集群减少⼀个实例 时,它所执⾏的任务能被转移到别的 实例来执⾏。
4、失效转移 某实例在任务执⾏失败后,会被转移到其他实例执⾏
5、错过执⾏作业重触发 若因某种原因导致作业错过执⾏,⾃动记录错过执⾏的作业,并在上次作业 完成后⾃动触发。
6、⽀持并⾏调度 ⽀持任务分⽚,任务分⽚是指将⼀个任务分为多个⼩任务项在多个实例同时执⾏。
7、作业分⽚⼀致性 当任务被分⽚后,保证同⼀分⽚在分布式环境中仅⼀个执⾏实例。
安装Zookeeper(此处单例配置)
1)我们使⽤3.4.10版本,在linux平台解压下载的zookeeper-3.4.10.tar.gz
2)进⼊conf⽬录,cp zoo_sample.cfg zoo.cfg
3) 进⼊bin⽬录,启动zk服务
启动 ./zkServer.sh start
停⽌ ./zkServer.sh stop
查看状态 ./zkServer.sh status
com.dangdang
elastic-job-lite-core
2.1.5
需求:每隔两秒钟执⾏⼀次定时任务(resume表中未归档的数据归档到resume_bak表中, 每次归档1条记录)
1)resume_bak和resume表结构完全⼀样
2)resume表中数据归档之后不删除,只将state置为"已归档"
数据表结构:
DROP TABLE IF EXISTS `resume`;CREATE TABLE `resume` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`sex` varchar(255) DEFAULT NULL,
`phone` varchar(255) DEFAULT NULL,
`address` varchar(255) DEFAULT NULL,
`education` varchar(255) DEFAULT NULL,
`state` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8;
SET FOREIGN_KEY_CHECKS = 1;
程序开发
定时任务类
import com.dangdang.ddframe.job.api.ShardingContext;
import com.dangdang.ddframe.job.api.simple.SimpleJob;
import javax.swing.*;
import java.sql.Connection;
import java.util.List;
import java.util.Map;
/**
* ElasticJobLite定时任务业务逻辑处理类
* 定义一个任务类实现 SimpleJob接口
*/
public class BackupJob implements SimpleJob {
// 定时任务每执行一次都会执行此方法逻辑
// execute方法 写我们自己的定时任务业务逻辑
@Override
public void execute(ShardingContext shardingContext) {
// 分片数 当前是哪个分片
int shardingItem = shardingContext.getShardingItem();
System.out.println("当前分片:"+shardingItem);// 当前分片:0
// 获取分片参数
String shardingParameter = shardingContext.getShardingParameter();//
System.out.println("分片参数:"+shardingParameter);// 分片参数:doctor
// 1.从resume中查出一条记录
String sql="select * from resume where state='未归档' and education='"+shardingParameter+"' limit 1";
List
import com.dangdang.ddframe.job.config.JobCoreConfiguration;
import com.dangdang.ddframe.job.config.simple.SimpleJobConfiguration;
import com.dangdang.ddframe.job.lite.api.JobScheduler;
import com.dangdang.ddframe.job.lite.config.LiteJobConfiguration;
import com.dangdang.ddframe.job.reg.base.CoordinatorRegistryCenter;
import com.dangdang.ddframe.job.reg.zookeeper.ZookeeperConfiguration;
import com.dangdang.ddframe.job.reg.zookeeper.ZookeeperRegistryCenter;
public class ElasticJobMain {
public static void main(String[] args) {
// 配置分布式协调服务 zookeeper ,不能让任务重复执行
// zookeeper 地址,zookeeper命名空间,空间下可以有多个任务
ZookeeperConfiguration zookeeperConfiguration = new ZookeeperConfiguration("192.168.206.131:2181","data-archive-job");
// 注册中心对象
CoordinatorRegistryCenter coordinatorRegistryCenter = new ZookeeperRegistryCenter(zookeeperConfiguration);
coordinatorRegistryCenter.init();
//配置 任务
// 时间事件
JobCoreConfiguration jobCoreConfiguration = JobCoreConfiguration
// 任务名字 ,任务执行时间 -每两秒执行一次,分片数量
.newBuilder("backupJob", "*/2 * * * * ?", 3)
// 每个分片处理的关键信息 0=doctor,表示给0号分片传递的参数,
.shardingItemParameters("0=doctor,1=bachelor ,2=master")
.build();
// 关联任务逻辑
SimpleJobConfiguration simpleJobConfiguration = new SimpleJobConfiguration(jobCoreConfiguration, BackupJob.class.getName());
// 调度器
JobScheduler jobScheduler = new JobScheduler(coordinatorRegistryCenter, LiteJobConfiguration.newBuilder(simpleJobConfiguration).overwrite(true).build());
jobScheduler.init();
}
}
1)可先启动⼀个进程,然后再启动⼀个进程(两个进程模拟分布式环境下,通⼀个定时任务 部署了两份在⼯作)
2)两个进程逐个启动,观察现象
一个进程启动后,任务执行,打印出日志,第二个进程也启动后,开始执行定时任务,同时第二个进程停止执行定时任务。
( 在分布式环境中,任务能够按指定的调度策略执⾏,并且能够避免同⼀任务多实例重复执⾏)
3)关闭其中执⾏的进程,观察现象
执行的进程关闭后(模拟宕机),另一个进程开始 执行定时任务
(弹性扩容缩容 当集群中增加某⼀个实例,它应当也能够被选举并执⾏任务;当集群减少⼀个实例 时,它所执⾏的任务能被转移到别的 实例来执⾏。)
此时可以用可视化工具查看zookeeper的结构目录
Leader节点选举机制
每个Elastic-Job的任务执⾏实例App作为Zookeeper的客户端来操作ZooKeeper的znode
(1)多个实例同时创建/leader节点
(2)/leader节点只能创建⼀个,后创建的会失败,创建成功的实例会被选为leader节点, 执⾏任务
先说一下什么是中心化:指有一个统一任务分发器,定时给服务1分配任务,定时给服务2分发任务。
elasticjob去中心化:是服务自己在zookeeper上同步数据,自己拿到一些数据,然后去执行,自我驱动,不是被动分发的。
每个服务节点是一样的包括ElasticJob的jar和我们自己写的程序,唯一不同的可能是分片。
(即为把⼀个⼤的作业任务拆分为多个⼩的作业任务,多个实例同时执⾏)
⼀个⼤的⾮常耗时的作业Job,⽐如:⼀次要处理⼀亿的数据,那这⼀亿的数据存储在数据库中,如果 ⽤⼀个作业节点处理⼀亿数据要很久,在互联⽹领域是不太能接受的,互联⽹领域更希望机器的增加去 横向扩展处理能⼒。所以,ElasticJob可以把作业分为多个的task(每⼀个task就是⼀个任务分⽚),每 ⼀个task交给具体的⼀个机器实例去处理(⼀个机器实例是可以处理多个task的),但是具体每个task 执⾏什么逻辑由我们⾃⼰来指定。
一个实例执行结果:一个实例执行3片
增加一个实例执行,一共两个实例执行3片
一个实例执行一片任务,结果如下:
第二个实例执行两个分片,结果如下:
再增加一个实例,共三个实例:
第0个实例执行第2个分片,结果 如下:
第1个实例执行第1个分片,结果如下:
第二个实例执行第0个分片,结果 如下:
新增加⼀个运⾏实例,它会⾃动注册到注册中⼼,注册中⼼发现新的服务上线,注册中⼼会通知 ElasticJob 进⾏重新分⽚,那么总得分⽚项有多少,那么就可以搞多少个实例机器,⽐如完全可以分 1000⽚ ,那么就可以搞1000台机器⼀起执⾏作业
注意:
1)分⽚项也是⼀个JOB配置,修改配置,重新分⽚,在下⼀次定时运⾏之前会重新调⽤分⽚算法,那么 这个分⽚算法的结果就是:哪台机器运⾏哪⼀个⼀⽚,这个结果存储到zookeeper中的,主节点(leader)会把分⽚给分好(比如,第0个机器运行第2个分片,第1个机器运行第0个分片,第2个机器运行第1个分片) 放到注册中⼼去,然后执⾏节点从注册中⼼获取信息(执⾏节点在定时任务开启的时候获取相应的分⽚)。
2)如果所有的节点挂掉值剩下⼀个节点,所有分⽚都会指向剩下的⼀个节点,这也是ElasticJob的⾼可⽤