Elastic-Job
文章目录
1. 什么是 ElasticJob
官网地址
https://shardingsphere.apache.org/elasticjob/current/cn/overview/
https://github.com/apache/shardingsphere-elasticjob
Elastic-Job是当当网基于Zookepper,Quartz开发并且开源的Java分布式定时任务,解决Quartz不支持分布式的弊端。
ElasticJob 已于2020年5月28日成为 Apache ShardingSphere 的子项目。
- 分片概念:任务分布式的执行,需要将一个任务拆分成多个独立的任务项,然后由分布式的服务器分别执行某一个或几个分片项。
- 个性化参数:shardingItemParameter,可以和分片项匹配对应关系。比如:将商品的状态分成上架,下架。那么配置0=上架,1=下架,代码中直接使用上架下架的枚举值即可完成分片项与业务逻辑的对应关系。
- 作用高可用:将分片总数设置成1,多台服务器执行作业将采用1主n从的方式执行。
- 弹性扩容:将任务拆分为n个任务项后,各个服务器分别执行各自分配到的任务项。一旦有新的服器加入集群或有服务器宕机。Elastic-Job将保留本次任务不变,下次任务开始前重新分片。
- 并行调度:采用任务分片方式实现。将一个任务拆分为n个独立的任务项,由分布式的服务器并行执行各自分配到的分片项。
- 集中管理:采用基于zookepper的注册中心,集中管理和协调分布式作业的状态,分配和监听。外部系统可直接根据Zookeeper的数据管理和监控elastic-job。
- 定制化流程任务:作业可分为简单和数据流处理两种模式,数据流又分为高吞吐处理模式和顺序性处理模式,其中高吞吐处理模式可以开启足够多的线程快速的处理数据,而顺序性处理模式将每个分片项分配到一个独立线程,用于保证同一分片的顺序性,这点类似于kafka的分区顺序性。
Elastic-Job 是一个开源的分布式调度中间件,由两个相互独立的子项目 Elastic-Job-Lite 和 Elastic-Job-Cloud 组成。Elastic-Job-Lite 为轻量级无中心化解决方案,使用 jar 包提供分布式任务的调度和治理。 Elastic-Job-Cloud 是一个 Mesos Framework,依托于Mesos额外提供资源治理、应用分发以及进程隔离等服务。
ElasticJob-Lite
ElasticJob-Cloud
注:ElasticJob-Cloud支持资源分配,ElasticJob-Lite不支持资源分配
注:ElasticJob-Cloud支持常驻,ElasticJob-Lite支持常驻+瞬时
常驻作业:不论运行时还是等待运行,一直占用资源
瞬时作业:在每一次作业执行完毕后立刻释放资源
特性
-
弹性调度:将一个任务分片执行达到扩展
-
资源分配(elastic-cloud):将资源根据任务进行分配,可以在ElasticJob-Cloud看到这里的资源特指的是k8s容器,将任务分配给容器进行执行,这个资源直接对任务进行定时任务的配置。
-
作业治理:失效转移,错过作业重新执行,自诊断修复
-
开放生态:spring整合,作业类型:数据流、脚本、文件、HTTP(V3.0新特性beta)
-
可视化管控端:docker pull apache/shardingsphere-elasticjob-lite-ui
2. Elastic-Job的具体模块
在 ElasticJob 3.0.0,作业执行器经过重新设计,分为 Classed 与 Typed 两类。需要编写作业逻辑的 Simple、Dataflow 作业属于 Classed 类型作业,通过配置执行的 Script、HTTP 作业属于 Typed 类型作业。
Elastic-Job采用去中心化设计,主要分为注册中心、数据分片、分布式协调、定时任务处理和定制化流程型任务等模块。
-
去中心化:指Elastic-Job没有调度中心这一概念。每个运行在集群中的作业服务器都是对等的,节点之间通过注册中心进行分布式协调。但elastic-job有主节点的概念,主节点用于处理一些集中式任务,如分片,清理运行时信息等,并无调度功能,定时调度都是由作业服务器自行触发。
-
注册中心:注册中心模块目前直接使用zookeeper,用于记录作业的配置,服务器信息以及作业运行状态。Zookeeper虽然很成熟,但原理复杂,使用较难,在海量数据支持的情况下也会有性能和网络问题。
-
数据分片:数据分片是elastic-job中实现分布式的重要概念,将真实数据和逻辑分片对应,用于解耦作业框架和数据的关系。作业框架只负责将分片合理的分配给相关的作业服务器,而作业服务器需要根据所分配的分片匹配数据进行处理。服务器分片目前都存储在注册中心中,各个服务器根据自己的IP地址拉取分片。
-
分布式协调:分布式协调模块用于处理作业服务器的动态扩容缩容。一旦集群中有服务器发生变化,分布式协调将自动监测并将变化结果通知仍存活的作业服务器。协调时将会涉及主节点选举,重分片等操作。目前使用的Zookeeper的临时节点和监听器实现主动检查和通知功能。
-
定时任务处理:定时任务处理根据cron表达式定时触发任务,目前有防止任务同时触发,错过任务重出发等功能。主要还是使用Quartz本身的定时调度功能,为了便于控制,每个任务都使用独立的线程池。
-
定制化流程型任务:定制化流程型任务将定时任务分为多种流程,有不经任何修饰的简单任务;有用于处理数据的fetchData/processData的数据流任务;以后还将增加消息流任务,文件任务,工作流任务等。用户能以插件的形式扩展并贡献代码。
3. 作业类型
Elastic-Job提供Simple、Dataflow和Script 3种作业类型。方法参数shardingContext包含作业配置、片和运行时信息。可通过getShardingTotalCount(), getShardingItem()等方法分别获取分片总数,运行在本作业服务器的分片序列号等。
- Simple类型的作业:该类型意为简单实现,只需实现SimpleJob接口,重写它的execute方法即可
- Dataflow类型作业:用于处理数据流,实现DataflowJob接口,并重写两个方法——用于抓取(fetchData方法)和处理(processData方法)数据。比如在fetchData方法里面查询没有上架的商品,在processData方法修改该商品的状态。
- 注意:可通过DataflowJobConfiguration配置是否流式处理。当配置成流式处理,fetchData方法返回值(返回值是集合)是null或长度是0,作业才停止抓取,否则将一直运行。非流式的则每次作业只执行一次这两个方法就结束该作业。
- Script类型作业:意为脚本类型作业,支持shell、python、perl等类型脚本。只需通过控制台或代码配置scriptCommandLine即可,无需编码。
4. 作业配置
引入Maven依赖
<!-- 引入elastic-job-lite核心模块 -->
<dependency>
<groupId>io.elasticjob</groupId>
<artifactId>elastic-job-lite-core</artifactId>
<version>${latest.release.version}</version>
</dependency>
<!-- 使用springframework自定义命名空间时引入 -->
<dependency>
<groupId>io.elasticjob</groupId>
<artifactId>elastic-job-lite-spring</artifactId>
<version>${latest.release.version}</version>
</dependency>
Elasti-Job配置分成3个层级,Core, Type和Root。
- Core对应JobCoreConfiguration,用于提供作业核心配置信息,如:作业名称、分片总数、CRON表达式等。
- Type对应JobTypeConfiguration,有三个子类分别对应SIMPLE,DATAFLOW和SCRIPT类型作业,供3种作业需要的不同配置,如:DATAFLOW类型是否流式处理或SCRIPT类型的命令行等。
- Root对应JobRootConfiguration,有两个子类分别对应Lite和Cloud部署类型,提供不同部署类型所需的配置,如:Lite类型的是否需要覆盖本地配置或Cloud占用CPU或Memory数量等。
5. 与Spring结合
新版Elastic-job 需要 maven 版本3.5.0 以上 ;java8 以上;zookeeper 版本3.6.0 以上(java 代码里接入的zk和平台里的zk都要3.6.0 版本以上)
5.1 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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example.elasticjob</groupId>
<artifactId>elasticjobdemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>elasticjobdemo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--elasticJob-->
<dependency>
<groupId>org.apache.shardingsphere.elasticjob</groupId>
<artifactId>elasticjob-lite-core</artifactId>
<version>3.0.0-RC1</version>
</dependency>
<!--springboot整合elasticJob(官方文档中没有)-->
<dependency>
<groupId>org.apache.shardingsphere.elasticjob</groupId>
<artifactId>elasticjob-lite-spring-boot-starter</artifactId>
<version>3.0.0-RC1</version>
</dependency>
<!--elasticJob- 对接邮件、企业微信、钉钉-->
<dependency>
<groupId>org.apache.shardingsphere.elasticjob</groupId>
<artifactId>elasticjob-error-handler-email</artifactId>
<version>3.0.0-RC1</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.15</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.1.5.RELEASE</version>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
5.2 核心配置+任务调度
zookeeper配置类(创建zk客户端,且调用init方法)
package com.example.elasticjob.quickStart.config;
import org.apache.shardingsphere.elasticjob.reg.base.CoordinatorRegistryCenter;
import org.apache.shardingsphere.elasticjob.reg.zookeeper.ZookeeperConfiguration;
import org.apache.shardingsphere.elasticjob.reg.zookeeper.ZookeeperRegistryCenter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ZookepeerConfig {
//zookeeper链接字符串 localhost:2181
private String ZOOKEEPER_CONNECTION_STRING = "localhost:2181" ;
//定时任务命名空间
private String JOB_NAMESPACE = "elastic-job-boot-java";
//zk的配置及创建注册中心
@Bean(initMethod = "init")
public CoordinatorRegistryCenter setUpRegistryCenter(){
//zk的配置
ZookeeperConfiguration zookeeperConfiguration = new ZookeeperConfiguration(ZOOKEEPER_CONNECTION_STRING, "elastic-job-boot-java");
//设置zk超时时间
zookeeperConfiguration.setSessionTimeoutMilliseconds(1000);
//创建注册中心
CoordinatorRegistryCenter zookeeperRegistryCenter = new ZookeeperRegistryCenter(zookeeperConfiguration);
return zookeeperRegistryCenter;
}
}
配置esjob+任务调度
package com.example.elasticjob.quickStart.config;
import com.example.elasticjob.quickStart.job.FileBackupJobDb;
import com.example.elasticjob.quickStart.service.FileService;
import org.apache.shardingsphere.elasticjob.api.JobConfiguration;
import org.apache.shardingsphere.elasticjob.lite.api.bootstrap.impl.ScheduleJobBootstrap;
import org.apache.shardingsphere.elasticjob.reg.base.CoordinatorRegistryCenter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* elasticJob
*/
@Configuration
public class ElasticJobConfig {
@Autowired
CoordinatorRegistryCenter registryCenter;
@Autowired
FileService fileService;
@Bean
public JobConfiguration createJobConfiguration() {
// 定义作业核心配置
JobConfiguration jobConfig = JobConfiguration.newBuilder("myJob", 3)
.cron("0/5 * * * * ?")
.shardingItemParameters("0=text,1=image,2=radio")
.failover(true)
.overwrite(true)
.monitorExecution(true)
.misfire(true).build();
//启动分布式定时任务
new ScheduleJobBootstrap(registryCenter, new FileBackupJobDb(fileService), jobConfig).schedule();
return jobConfig;
}
}
FileService类
package com.example.elasticjob.quickStart.service;
import com.example.elasticjob.quickStart.pojo.FileCustom;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import java.util.List;
@Component
public class FileService {
@Autowired
JdbcTemplate jdbcTemplate;
/**
* 获取某文件类型未备份的文件
* @param fileType 文件类型
* @param count 获取条数
* @return
*/
public List<FileCustom> fetchUnBackupFiles(String fileType, Integer count){
String sql="select * from t_file where type = ? and backedUp = 0 limit 0,?";
List<FileCustom> files = jdbcTemplate.query(sql, new Object[]{fileType, count}, new BeanPropertyRowMapper(FileCustom.class));
return files;
}
/**
* 备份文件
* @param files 要备份的文件
*/
public void backupFiles(List<FileCustom> files){
for(FileCustom file:files){
String sql="update t_file set backedUp = 1 where id = ?";
jdbcTemplate.update(sql,new Object[]{file.getId()});
System.out.println(String.format("线程 %d | 已备份文件:%s 文件类型:%s"
,Thread.currentThread().getId()
,file.getName()
,file.getType()));
}
}
}
FileCustom实体
package com.example.elasticjob.quickStart.pojo;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class FileCustom {
/**
* 标识
*/
private String id;
/**
* 文件名
*/
private String name;
/**
* 文件类型,如text、image、radio、vedio
*/
private String type;
/**
* 文件内容
*/
private String content;
/**
* 是否已备份
*/
private Boolean backedUp = false;
public FileCustom(String id, String name, String type, String content){
this.id = id;
this.name = name;
this.type = type;
this.content = content;
}
}
数据源
package com.example.elasticjob.quickStart.config;
import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class DataSourceConfig {
@Value("${spring.datasource.driver}")
private String driverClassName;
@Value("${spring.datasource.url}")
private String url;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Bean(name = "myDataSource")
public DataSource getMyDataSource() {
BasicDataSource result = new BasicDataSource();
result.setDriverClassName(driverClassName);
result.setUrl(url);
result.setUsername(username);
result.setPassword(password);
return result;
}
}
yml
server:
port: ${port:8081}
esjob:
zkServerlists: localhost:2181
zkNamespace: es-job-cupid
startedTimeoutMilliseconds: 500
completedTimeoutMilliseconds: 500
spring:
datasource:
url: jdbc:mysql://localhost:3306/elastic_job_demo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
username: root
password: 1234
driver: com.mysql.jdbc.Driver