什么时候用到分布式任务调度
任务调度就是定时任务。分布式任务调度区别于单节点任务调度,分布式任务调度可以让集群环境下的各个节点执行不同的任务,以此提高效率。
什么场景用到任务调度呢?
例如1.借贷App,逾期问题,自动发送短信,自动计算更新逾期金额;
2.电商给满足条件的用户自动发送优惠券;
3.互联网项目缓存预热,在用户查看之前提前准备好缓存;
基本概念
- 分片概念
将一个大任务拆分成多个小任务,在各个节点上执行。
- 分片项与业务处理解耦
elastic-job不提供数据处理的功能,框架只会将分片项分配至各个运行中的作业服务器,开发者需要自行处理分片项与真实数据的对应关系。
- 个性化参数的适用场景
个性化参数即shardingItemParameter,可以和分片项匹配对应关系,用于将分片项的数字转化为更加可读的业务代码。
例如:按照水平拆分数据库,数据库A是北京的数据;数据库B是上海的数据;数据库C是广州的数据。按照分片项配置,开发者需要了解0表示北京;1表示上海;2表示广州。合理适用个性化参数可以让代码更可读,如果配置0=北京,1=上海,2=广州,那么代码中直接适用北京、上海、广州的枚举即可完成分片项和业务逻辑的对应关系。
- 分布式调度
Elastic-Job-Lite 并无作业调度中心节点,而是基于部署做业框架的程序到达相应时间点时各自触发调度。注册中心仅用于作业注册和监控信息存储。而主作业节点仅用于处理分片和清理等功能
- 作业高可用
Elastic-Job-Lite 提供最安全的方式执行作业。将分片总数设置为1,并使用多余1台的服务器执行作业,作业将会以1主n从的方式执行。一旦执行作业的服务器崩溃,等待执行的服务器将会在下次作业启动时替补执行。开启失效转移功能效果更好,可以保证在本次作业执行时崩溃,备机立即启动替补执行。
- 最大限度利用资源
Elastic-Job-Lite 也提供最灵活的方式,最大限度的提高执行作业的吞吐量。将分片项设置为大于服务器的数量,最好是大于服务器倍数的数量,作业将会合理的利用分布式资源,动态的分配分片项。例如:3台服务器,分成10片,则分片项分配结果为服务器A=0,1,2;服务器B=3,4,5;服务器C=6,7,8,9.如果服务器C崩溃,则分片项分配结果为服务器A=0,1,2,3,4;服务器B=5,6,7,8,9.在不丢失分片项的情况下,最大限度的利用现有资源提高吞吐量。
认识 Elastic-Job
Elastic-Job是一个分布式任务调度框架,由当当网基于Quartz开发,于2020 年 5 月 28 日开源给apache。使用 Zookeeper 作为注册中心,支持弹性扩容和数据分片。
Elastic-Job有Elastic-Job-Lite 和 Elastic-Job-Cloud 两个子项目。Elastic-Job-Lite 定位为轻量级无中心化解决方案, 使用jar包的形式提供分布式任务的协调服务 。而Elastic-Job-Cloud 使用 Mesos + Docker 的解决方案,额外提供资源治理、应用分发以及进程隔离等服务(跟 Lite 的区别只是部署方式不同,他们使用相同的 API,只要开发一次)。本文只介绍Elastic-Job-Lite的demo。Elastic-Job支持3种作业类型,分别是SimpleJob、DataFlowJob、ScriptJob,此篇使用的是SimpleJob。
实例
注意elastic-job依赖zookeeper,首先要安装好zookeeper。
接下来我们模仿现实中的一个场景:电商收到订单后,需要按地址分发物流。假设有北京、上海、深圳、广州四个地址,为了提高吞吐量,采用4台机器4个节点进行分片处理。
application.properties:
zk.serverList=118.25.53.252:2181
zk.namespace=elasticjob-test
pom.xml
<?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.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>pers.czl</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.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.dangdang</groupId>
<artifactId>elastic-job-lite-core</artifactId>
<version>2.1.5</version>
</dependency>
<!-- elastic-job-lite-spring -->
<dependency>
<groupId>com.dangdang</groupId>
<artifactId>elastic-job-lite-spring</artifactId>
<version>2.1.5</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<target>1.8</target>
<source>1.8</source>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
注册中心配置类
package pers.czl.elasticjob.config;
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;
/**
* @Title: 注册中心配置类
* @Description:
* @Author:
* @CreateTime: 2020-12-17 16:53
* @Version:1.0
**/
@Configuration
public class RegCenterConfig {
/**
* 配置zookeeper
* @param serverList
* @param namespace
* @return
*/
@Bean(initMethod = "init")
public ZookeeperRegistryCenter zookeeperRegistryCenter(
@Value("${zk.serverList}") final String serverList,
@Value("${zk.namespace}") final String namespace) {
return new ZookeeperRegistryCenter(new ZookeeperConfiguration(serverList, namespace));
}
}
任务配置类
"0/5 * * * * ?"是cron表达式,代表每5秒钟执行一次
package pers.czl.elasticjob.config;
import com.dangdang.ddframe.job.api.simple.SimpleJob;
import com.dangdang.ddframe.job.config.JobCoreConfiguration;
import com.dangdang.ddframe.job.config.JobRootConfiguration;
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.lite.spring.api.SpringJobScheduler;
import com.dangdang.ddframe.job.reg.zookeeper.ZookeeperRegistryCenter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import pers.czl.elasticjob.job.MySimpleJob;
/**
* @Title: 作业配置类
* @Description:
* @Author: wb-ccl670938
* @CreateTime: 2020-12-17 17:18
* @Version:1.0
**/
@Configuration
public class MySimpleJobConfig {
@Autowired
private ZookeeperRegistryCenter zookeeperRegistryCenter;
@Bean
public SimpleJob stockJob() {
return new MySimpleJob();
}
@Bean(initMethod = "init")
public JobScheduler simpleJobScheduler(final SimpleJob simpleJob) {
return new SpringJobScheduler(simpleJob, zookeeperRegistryCenter, createSimpleJobConfiguration());
}
private static LiteJobConfiguration createSimpleJobConfiguration() {
// 定义作业核心配置
JobCoreConfiguration simpleCoreConfig = JobCoreConfiguration.newBuilder("SimpleJobDemo", "0/5 * * * * ?", 4)
.shardingItemParameters("0=北京,1=上海,2=广州,3=深圳").build();
// 定义SIMPLE类型配置
SimpleJobConfiguration simpleJobConfig = new SimpleJobConfiguration(simpleCoreConfig, MySimpleJob.class.getCanonicalName());
// 定义Lite作业根配置
JobRootConfiguration simpleJobRootConfig = LiteJobConfiguration.newBuilder(simpleJobConfig).build();
return (LiteJobConfiguration) simpleJobRootConfig;
}
}
作业类
package pers.czl.elasticjob.job;
import com.dangdang.ddframe.job.api.ShardingContext;
import com.dangdang.ddframe.job.api.simple.SimpleJob;
/**
* @Title: 作业类
* @Description:
* @Author: wb-ccl670938
* @CreateTime: 2020-12-17 16:44
* @Version:1.0
**/
public class MySimpleJob implements SimpleJob {
@Override
public void execute(ShardingContext shardingContext) {
//获取分片总数
int shardingTotalCount = shardingContext.getShardingTotalCount();
//获取分片项
int shardingItem = shardingContext.getShardingItem();
//获取分片项参数
String shardingParameter = shardingContext.getShardingParameter();
System.out.println("分片总数:" + shardingTotalCount);
System.out.println("分片项:" + shardingItem);
System.out.println("分片项参数:" + shardingParameter);
System.out.println("作业名称:" + shardingContext.getJobName());
//不同分片项进行不同处理
switch (shardingItem) {
case 0:
System.out.println("物流分发至:" + shardingParameter);
break;
case 1:
System.out.println("物流分发至:" + shardingParameter);
break;
case 2:
System.out.println("物流分发至:" + shardingParameter);
break;
case 3:
System.out.println("物流分发至:" + shardingParameter);
break;
}
}
}
启动类
package pers.czl.elasticjob;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ElasticJobDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ElasticJobDemoApplication.class, args);
}
}
代码完成,首先启动一个实例,看输出结果
可以看到所有分片项都被分发到这一个实例。那么继续启动3个实例。
可以看到4个分片项被均匀的分配给了4个节点。
现在停掉一个实例
停掉分片项3的实例后,分片项3和0被分配给了第二个实例。这样就实现了故障的转移。
注册中心节点简介
项目启动后会在zookeeper注册中心生成节点。
config:job配置信息,持久化节点
{
"jobName":"SimpleJobDemo",
"jobClass":"pers.czl.elasticjob.job.MySimpleJob",
"jobType":"SIMPLE",
"cron":"0/5 * * * * ?",
"shardingTotalCount":4,
"shardingItemParameters":"0=鍖椾含,1=涓婃捣,2=骞垮窞,3=娣卞湷",
"jobParameter":"",
"failover":false,
"misfire":true,
"description":"",
"jobProperties":{
"job_exception_handler":"com.dangdang.ddframe.job.executor.handler.impl.DefaultJobExceptionHandler",
"executor_service_handler":"com.dangdang.ddframe.job.executor.handler.impl.DefaultExecutorServiceHandler"
},
"monitorExecution":true,
"maxTimeDiffSeconds":-1,
"monitorPort":-1,
"jobShardingStrategyClass":"",
"reconcileIntervalMinutes":10,
"disabled":false,
"overwrite":false
}
instances:启动的实例。本例先启动了4个,停止了一个剩下3个。由ip+@-@+pid组成。
sharding:分片信息,子节点是分片序号。记录的数据是当前分片分配给的实例。
servers:任务实例的ip信息。
leader:实例的主节点,通过zookeeper的主节点选举选选出来的。主节点负责控制分片,把分片分配给不同的实例。有三个子节点:
1.election:主节点选举 。instance记录的是选举出来的主节点,此例的主节点pid是7032。election 下面的 latch 节点也是一个永久节点用于选举时候的实现分布式锁
2.sharding:分片
3.failover:失效转移,没有发生失效转移所以没显示