一.前期准备
1.1环境要求
JDK要求1.7及以上版本
Maven要求3.0.4及以上版本
zookeeper要求采用3.4.6及以上版本,执行程序时确保zk已经启动。
1.2.代码逻辑
此任务在每次执行时获取一定数目的文件,进行备份处理,由File实体类的backedUp属性来标识该文件是否已备份。
1.案例逻辑:每个线程独立处理40个文件,假设1个线程,则1个线程处理40个文件;2个线程,则2个线程分别处理40个文件。
2.案例逻辑:这里修改将多个线程处理共有的40个文件,编写执行备份任务类:这里防止多线程情况下,出现消费错乱情况,将读取文件,和修改文件封装到一个方法后,进行加锁。n个线程处理这40个共有的文件。n个线程共同处理这40个文件。
1.3 zk的搭建启动
1.解压zk
2.修改conf/zoo_sample.cfg 改为zoo.cfg
3.启动 :zkServer.cmd 点击执行。
二.案例展示
2.1 项目编写
2.1.1 配置pom文件
新建工程: elastic-file-dispatch,配置pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>elastic-job-demo</artifactId>
<groupId>com.ljf.elastic.job</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.ljf.elastic.job.file.dispatch</groupId>
<artifactId>elastic-file-dispatch</artifactId>
<version>1.0-SNAPSHOT</version>
<name>elastic-file-dispatch</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.dangdang</groupId>
<artifactId>elastic-job-lite-core</artifactId>
<version>2.1.5</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
2.1.2 配置job执行类
编写执行备份任务类:这里防止多线程情况下,出现消费错乱情况,将读取文件,和修改文件封装到一个方法后,进行加锁。
package com.itheima.scheduler.elasticjob.quickstart.job;
import com.dangdang.ddframe.job.api.ShardingContext;
import com.dangdang.ddframe.job.api.simple.SimpleJob;
import com.itheima.scheduler.elasticjob.quickstart.model.FileCustom;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
/**
* 文件备份任务
* @author Administrator
* @version 1.0
**/
public class FileBackupJob implements SimpleJob {
//每次任务执行要备份文件的数量
private final int FETCH_SIZE = 1;
//文件列表(模拟)
public static List<FileCustom> files = new ArrayList<>();
//任务执行代码逻辑
@Override
public void execute(ShardingContext shardingContext) {
System.out.println("作业分片:"+shardingContext.getShardingItem());
// //获取未备份的文件
// List<FileCustom> fileCustoms = fetchUnBackupFiles(FETCH_SIZE);
// //进行文件备份
// backupFiles(fileCustoms);
fengzhuang();
System.out.println("本次任务执行完成.....");
}
public synchronized void fengzhuang(){
//获取未备份的文件
List<FileCustom> fileCustoms = fetchUnBackupFiles(FETCH_SIZE);
//进行文件备份
backupFiles(fileCustoms);
}
/**
* 获取未备份的文件
* @param count 文件数量
* @return
*/
public List<FileCustom> fetchUnBackupFiles(int count){
//获取的文件列表
List<FileCustom> fileCustoms = new ArrayList<>();
int num=0;
for(FileCustom fileCustom:files){
if(num >=count){
break;
}
if(!fileCustom.getBackedUp()){
fileCustoms.add(fileCustom);
num ++;
}
}
System.out.printf("线程:"+Thread.currentThread().getId()+"time:%s,获取文件%d个\n", LocalDateTime.now(),num);
return fileCustoms;
}
/**
* 文件备份
* @param files
*/
public void backupFiles(List<FileCustom> files){
for(FileCustom fileCustom:files){
fileCustom.setBackedUp(true);
System.out.printf("线程:"+Thread.currentThread().getId()+"time:%s,备份文件,名称:%s,类型:%s\n", LocalDateTime.now(),fileCustom.getName(),fileCustom.getType());
}
}
}
2.1.3 编写实体类
编写模型bean:
package com.ljf.elastic.job.file.dispatch.model;
import lombok.Data;
@Data
public class FileData {
/**
* 标识
*/
private String id;
/**
* 文件名
*/
private String name;
/**
* 文件类型,如text、image、radio、vedio
*/
private String type;
/**
* 文件内容
*/
private String content;
/**
* 是否已备份
*/
private Boolean backedUp = false;
public FileData(String id,String name,String type,String content){
this.id = id;
this.name = name;
this.type = type;
this.content = content;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Boolean getBackedUp() {
return backedUp;
}
public void setBackedUp(Boolean backedUp) {
this.backedUp = backedUp;
}
}
2.1.4.启动类
1.编写启动类
package com.ljf.elastic.job.file.dispatch;
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;
import com.ljf.elastic.job.file.dispatch.job.FileBackUpJob;
import com.ljf.elastic.job.file.dispatch.model.FileData;
/**
* Hello world!
*
*/
public class App
{
//zookeeper端口
private static final int ZOOKEEPER_PORT = 2181;
//zookeeper链接字符串 localhost:2181
private static final String ZOOKEEPER_CONNECTION_STRING = "127.0.0.1:" + ZOOKEEPER_PORT;
//定时任务命名空间
private static final String JOB_NAMESPACE = "elastic-job-example-01";
//执行启动任务
public static void main( String[] args )
{
//制造一些测试数据
generateTestFiles();
//配置注册中心
CoordinatorRegistryCenter registryCenter = setUpRegistryCenter();
//启动任务
startJob(registryCenter);
System.out.println( "Hello World!" );
}
//任务的配置和启动
private static void startJob(CoordinatorRegistryCenter registryCenter){
//String jobName 任务名称, String cron 调度表达式, int shardingTotalCount 作业分片数量
JobCoreConfiguration jobCoreConfiguration = JobCoreConfiguration.newBuilder("fiels-job-01", "0/10 * * * * ?", 1).build();
//创建SimpleJobConfiguration
SimpleJobConfiguration simpleJobConfiguration = new SimpleJobConfiguration(jobCoreConfiguration, FileBackUpJob.class.getCanonicalName());
//创建new JobScheduler
new JobScheduler(registryCenter, LiteJobConfiguration.newBuilder(simpleJobConfiguration).overwrite(true).build()).init();
}
//zk的配置及创建注册中心
private static CoordinatorRegistryCenter setUpRegistryCenter(){
//zk的配置
ZookeeperConfiguration zookeeperConfiguration = new ZookeeperConfiguration(ZOOKEEPER_CONNECTION_STRING, JOB_NAMESPACE);
//减少zk超时时间
zookeeperConfiguration.setSessionTimeoutMilliseconds(100);
//创建注册中心
CoordinatorRegistryCenter zookeeperRegistryCenter = new ZookeeperRegistryCenter(zookeeperConfiguration);
zookeeperRegistryCenter.init();
return zookeeperRegistryCenter;
}
//制造一些测试数据
//生成测试文件
private static void generateTestFiles(){
for(int i=1;i<5;i++){
FileBackUpJob.filesList.add(new FileData(String.valueOf(i+10),"文件"+(i+10),"text","content"+ (i+10)));
FileBackUpJob.filesList.add(new FileData(String.valueOf(i+20),"文件"+(i+20),"image","content"+ (i+20)));
FileBackUpJob.filesList.add(new FileData(String.valueOf(i+30),"文件"+(i+30),"radio","content"+ (i+30)));
FileBackUpJob.filesList.add(new FileData(String.valueOf(i+40),"文件"+(i+40),"video","content"+ (i+40)));
}
System.out.println("生产测试数据完成,大小为:"+FileBackUpJob.filesList.size());
}
}
2.2 案例测试1(1个分片1个执行机)
情况1: 1个分片,1个执行器(实例案例,1个jvm,1个进程):1个分片时候:启动程序,欢快的执行着。相当于在一台机器会创建1个线程进行串行执行。
2.3 案例测试2(3个分片,1个执行机)
情况2: 3个分片,1个执行器(实例案例,1个jvm,1个进程):相当于在一台机器会创建3个线程进行执行。
具体log日志:
分片:0 线程:34 time:2023-11-12T10:46:33.072,获取文件1个
分片:0 线程:34 time:2023-11-12T10:46:33.072,备份文件,名称:文件11,类型:text
本次任务执行完成.....
分片:2 线程:36 time:2023-11-12T10:46:33.073,获取文件1个
分片:2 线程:36 time:2023-11-12T10:46:33.073,备份文件,名称:文件21,类型:image
本次任务执行完成.....
分片:1 线程:35 time:2023-11-12T10:46:33.073,获取文件1个
分片:1 线程:35 time:2023-11-12T10:46:33.073,备份文件,名称:文件31,类型:radio
本次任务执行完成.....
作业分片:0
分片:0 线程:37 time:2023-11-12T10:46:36.019,获取文件1个
分片:0 线程:37 time:2023-11-12T10:46:36.019,备份文件,名称:文件41,类型:video
本次任务执行完成.....
作业分片:1
分片:1 线程:38 time:2023-11-12T10:46:36.020,获取文件1个
分片:1 线程:38 time:2023-11-12T10:46:36.020,备份文件,名称:文件12,类型:text
本次任务执行完成.....
作业分片:2
分片:2 线程:39 time:2023-11-12T10:46:36.021,获取文件1个
分片:2 线程:39 time:2023-11-12T10:46:36.021,备份文件,名称:文件22,类型:image
本次任务执行完成.....
作业分片:0
分片:0 线程:40 time:2023-11-12T10:46:39.018,获取文件1个
作业分片:1
分片:0 线程:40 time:2023-11-12T10:46:39.018,备份文件,名称:文件32,类型:radio
本次任务执行完成.....
分片:1 线程:41 time:2023-11-12T10:46:39.025,获取文件1个
作业分片:2
分片:1 线程:41 time:2023-11-12T10:46:39.025,备份文件,名称:文件42,类型:video
本次任务执行完成.....
分片:2 线程:42 time:2023-11-12T10:46:39.025,获取文件1个
分片:2 线程:42 time:2023-11-12T10:46:39.025,备份文件,名称:文件13,类型:text
本次任务执行完成.....
作业分片:0
分片:0 线程:43 time:2023-11-12T10:46:42.016,获取文件1个
作业分片:1
分片:0 线程:43 time:2023-11-12T10:46:42.016,备份文件,名称:文件23,类型:image
本次任务执行完成.....
分片:1 线程:44 time:2023-11-12T10:46:42.017,获取文件1个
作业分片:2
分片:1 线程:44 time:2023-11-12T10:46:42.017,备份文件,名称:文件33,类型:radio
本次任务执行完成.....
分片:2 线程:45 time:2023-11-12T10:46:42.017,获取文件1个
分片:2 线程:45 time:2023-11-12T10:46:42.017,备份文件,名称:文件43,类型:video
本次任务执行完成.....
作业分片:0
分片:0 线程:46 time:2023-11-12T10:46:45.016,获取文件1个
分片:0 线程:46 time:2023-11-12T10:46:45.016,备份文件,名称:文件14,类型:text
本次任务执行完成.....
作业分片:1
分片:1 线程:47 time:2023-11-12T10:46:45.016,获取文件1个
分片:1 线程:47 time:2023-11-12T10:46:45.016,备份文件,名称:文件24,类型:image
本次任务执行完成.....
作业分片:2
分片:2 线程:48 time:2023-11-12T10:46:45.016,获取文件1个
分片:2 线程:48 time:2023-11-12T10:46:45.017,备份文件,名称:文件34,类型:radio
本次任务执行完成.....
作业分片:0
分片:0 线程:49 time:2023-11-12T10:46:48.019,获取文件1个
分片:0 线程:49 time:2023-11-12T10:46:48.020,备份文件,名称:文件44,类型:video
本次任务执行完成.....
作业分片:1
分片:1 线程:50 time:2023-11-12T10:46:48.020,获取文件1个
分片:1 线程:50 time:2023-11-12T10:46:48.020,备份文件,名称:文件15,类型:text
本次任务执行完成.....
作业分片:2
分片:2 线程:51 time:2023-11-12T10:46:48.020,获取文件1个
分片:2 线程:51 time:2023-11-12T10:46:48.020,备份文件,名称:文件25,类型:image
本次任务执行完成.....
作业分片:0
分片:0 线程:52 time:2023-11-12T10:46:51.017,获取文件1个
分片:0 线程:52 time:2023-11-12T10:46:51.017,备份文件,名称:文件35,类型:radio
本次任务执行完成.....
作业分片:1
分片:1 线程:53 time:2023-11-12T10:46:51.018,获取文件1个
分片:1 线程:53 time:2023-11-12T10:46:51.018,备份文件,名称:文件45,类型:video
本次任务执行完成.....
作业分片:2
分片:2 线程:54 time:2023-11-12T10:46:51.018,获取文件0个
本次任务执行完成.....
作业分片:0
分片:0 线程:55 time:2023-11-12T10:46:54.019,获取文件0个
本次任务执行完成.....
作业分片:1
分片:1 线程:56 time:2023-11-12T10:46:54.019,获取文件0个
本次任务执行完成.....
作业分片:2
分片:2 线程:57 time:2023-11-12T10:46:54.020,获取文件0个
本次任务执行完成.....
作业分片:0
分片:0 线程:58 time:2023-11-12T10:46:57.019,获取文件0个
本次任务执行完成.....
作业分片:1
分片:1 线程:59 time:2023-11-12T10:46:57.019,获取文件0个
本次任务执行完成.....
作业分片:2
分片:2 线程:60 time:2023-11-12T10:46:57.019,获取文件0个
本次任务执行完成.....
作业分片:0
分片:0 线程:61 time:2023-11-12T10:47:00.024,获取文件0个
本次任务执行完成.....
作业分片:1
分片:1 线程:62 time:2023-11-12T10:47:00.024,获取文件0个
本次任务执行完成.....
作业分片:2
分片:2 线程:63 time:2023-11-12T10:47:00.025,获取文件0个
本次任务执行完成.....
作业分片:0
分片:0 线程:64 time:2023-11-12T10:47:03.020,获取文件0个
本次任务执行完成.....
作业分片:1
作业分片:2
分片:1 线程:65 time:2023-11-12T10:47:03.020,获取文件0个
本次任务执行完成.....
分片:2 线程:34 time:2023-11-12T10:47:03.020,获取文件0个
本次任务执行完成.....
作业分片:0
分片:0 线程:36 time:2023-11-12T10:47:06.020,获取文件0个
本次任务执行完成.....
作业分片:1
分片:1 线程:35 time:2023-11-12T10:47:06.020,获取文件0个
本次任务执行完成.....
作业分片:2
分片:2 线程:37 time:2023-11-12T10:47:06.020,获取文件0个
本次任务执行完成.....
作业分片:0
分片:0 线程:38 time:2023-11-12T10:47:09.018,获取文件0个
本次任务执行完成.....
作业分片:1
分片:1 线程:39 time:2023-11-12T10:47:09.018,获取文件0个
本次任务执行完成.....
作业分片:2
分片:2 线程:40 time:2023-11-12T10:47:09.019,获取文件0个
本次任务执行完成.....
作业分片:0
分片:0 线程:41 time:2023-11-12T10:47:12.023,获取文件0个
本次任务执行完成.....
作业分片:1
分片:1 线程:42 time:2023-11-12T10:47:12.023,获取文件0个
本次任务执行完成.....
作业分片:2
分片:2 线程:43 time:2023-11-12T10:47:12.024,获取文件0个
本次任务执行完成.....
2.4 案例测试3(2个分片,3个执行机)
情况3: 2个分片,3个执行器(实例案例,1个jvm,1个进程):相当于在2台机器会创建3个线程进行执行,其中有两台机器分别获取到一个分片,剩余1台机器处于空闲状态,当执行任务的一台机器宕机,空闲机器接管宕机机器上的任务进行执行。
1.A窗口实例:先开始执行0分片,1分片
2.新增窗口B实例,则分片1调度到实例窗口B上执行
3.新增窗口C实例,则分片1调度到实例窗口c上执行,实例B不再有任务执行。
C窗口:
B窗口
4.将C实例窗口宕机,关闭,则看到分片1又转移到窗口B执行
B窗口执行记录
四.执行流程总结
4.1 总结介绍
Elastic-Job帮我们解决了分布式调度的以下三个问题:
1)多实例部署时避免任务重复执行,在任务执行时间到来时,从所有实例中选举出来一个,让它来执行任务,从而避免多个实例同时执行任务。
2)高可用,若某一个实例宕机,不影响其他实例来执行任务。
3)弹性扩容,当集群中增加某一个实例,它应当也能够被选举并执行任务,如果进行作业分片将参与执行某个分片作业。