任务调度之Elastic-Job2

运维平台

下载解压运行

git 下载源码https://github.com/elasticjob/elastic-job-lite

对elastic-job-lite-console 打包得到安装包

解压缩elastic-job-lite-console-${version}.tar.gz 并执行bin\start.sh(Windows运行.bat)。打开浏览器访问http://localhost:8899/即可访问控制台。

8899 为默认端口号,可通过启动脚本输入-p 自定义端口号。

默认管理员用户名和密码是root/root。右上角可以切换语言。

添加ZK 注册中心

第一步,添加注册中心,输入ZK 地址和命名空间,并连接。

运维平台和elastic-job-lite 并无直接关系,是通过读取作业注册中心数据展现作业状态,或更新注册中心数据修改全局配置。

控制台只能控制作业本身是否运行,但不能控制作业进程的启动,因为控制台和作业本身服务器是完全分离的,控制台并不能控制作业服务器。

可以对作业进行操作。

事件追踪

http://elasticjob.io/docs/elastic-job-lite/02-guide/event-trace/

Elastic-Job 提供了事件追踪功能,可通过事件订阅的方式处理调度过程的重要事件,用于查询、统计和监控。

Elastic-Job-Lite 在配置中提供了JobEventConfiguration,目前支持数据库方式配置。

ejob-standalone:simple.SimpleJobTest

BasicDataSource dataSource = new BasicDataSource();
	dataSource.setDriverClassName("com.mysql.jdbc.Driver");
	dataSource.setUrl("jdbc:mysql://localhost:3306/elastic_job_log");
	dataSource.setUsername("root");
	dataSource.setPassword("123456");
	JobEventConfiguration jobEventConfig = new JobEventRdbConfiguration(dataSource);
…………
new JobScheduler(regCenter, simpleJobRootConfig, jobEventConfig).init();
package simple;

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.api.strategy.impl.AverageAllocationJobShardingStrategy;
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 SimpleJobTest {
    // TODO 如果修改了代码,跑之前清空ZK
    public static void main(String[] args) {
        // ZK注册中心
        CoordinatorRegistryCenter regCenter = new ZookeeperRegistryCenter(new ZookeeperConfiguration("localhost:2181", "ejob-standalone"));
        regCenter.init();

        // 数据源,使用DBCP
/*        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/elastic_job_log");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        JobEventConfiguration jobEventConfig = new JobEventRdbConfiguration(dataSource);*/

        // 定义作业核心配置
        // TODO 如果修改了代码,跑之前清空ZK
        JobCoreConfiguration coreConfig = JobCoreConfiguration.newBuilder("MySimpleJob", "0/2 * * * * ?", 4).shardingItemParameters("0=RDP, 1=CORE, 2=SIMS, 3=ECIF").failover(true).build();
        // 定义SIMPLE类型配置
        SimpleJobConfiguration simpleJobConfig = new SimpleJobConfiguration(coreConfig, MySimpleJob.class.getCanonicalName());

        // 作业分片策略
        // 基于平均分配算法的分片策略
        String jobShardingStrategyClass = AverageAllocationJobShardingStrategy.class.getCanonicalName();

        // 定义Lite作业根配置
        // LiteJobConfiguration simpleJobRootConfig = LiteJobConfiguration.newBuilder(simpleJobConfig).jobShardingStrategyClass(jobShardingStrategyClass).build();
        LiteJobConfiguration simpleJobRootConfig = LiteJobConfiguration.newBuilder(simpleJobConfig).build();

        // 构建Job
        new JobScheduler(regCenter, simpleJobRootConfig).init();
        // new JobScheduler(regCenter, simpleJobRootConfig, jobEventConfig).init();
    }

}

事件追踪的event_trace_rdb_url 属性对应库自动创建JOB_EXECUTION_LOG 和JOB_STATUS_TRACE_LOG 两张表以及若干索引。

需要在运维平台中添加数据源信息,并且连接:

在作业历史中查询:

Spring 集成与分片详解

ejob-springboot 工程

pom 依赖

<properties>
	<elastic-job.version>2.1.5</elastic-job.version>
</properties>
<dependency>
	<groupId>com.dangdang</groupId>
	<artifactId>elastic-job-lite-core</artifactId>
	<version>${elastic-job.version}</version>
</dependency>
<!-- elastic-job-lite-spring -->
<dependency>
	<groupId>com.dangdang</groupId>
	<artifactId>elastic-job-lite-spring</artifactId>
	<version>${elastic-job.version}</version>
</dependency>

application.properties

定义配置类和任务类中要用到的参数

server.port=${random.int[10000,19999]}
regCenter.serverList = localhost:2181
regCenter.namespace = ejob-springboot
leonJob.cron = 0/3 * * * * ?
leonJob.shardingTotalCount = 2
leonJob.shardingItemParameters = 0=0,1=1

创建任务

创建任务类,加上@Component 注解

@Component
public class SimpleJobDemo implements SimpleJob {
	public void execute(ShardingContext shardingContext) {
		System.out.println(String.format("------Thread ID: %s, %s,任务总片数: %s, " +
		"当前分片项: %s.当前参数: %s," +
		"当前任务名称: %s.当前任务参数%s",
		Thread.currentThread().getId(),
		new SimpleDateFormat("HH:mm:ss").format(new Date()),
		shardingContext.getShardingTotalCount(),
		shardingContext.getShardingItem(),
		shardingContext.getShardingParameter(),
		shardingContext.getJobName(),
		shardingContext.getJobParameter()
		));
	}
}

注册中心配置

Bean 的initMethod 属性用来指定Bean 初始化完成之后要执行的方法,用来替代继承InitializingBean 接口,以便在容器启动的时候创建注册中心。

@Configuration
public class ElasticRegCenterConfig {
	@Bean(initMethod = "init")
	public ZookeeperRegistryCenter regCenter(
	@Value("${regCenter.serverList}") final String serverList,
	@Value("${regCenter.namespace}") final String namespace) {
		return new ZookeeperRegistryCenter(new ZookeeperConfiguration(serverList, namespace));
	}
}

作业三级配置

Core——Type——Lite

return LiteJobConfiguration.newBuilder(new SimpleJobConfiguration(JobCoreConfiguration.newBuilder(

@Configuration
public class ElasticJobConfig {

	@Autowired
	private ZookeeperRegistryCenter regCenter;
	
	@Bean(initMethod = "init")
	public JobScheduler simpleJobScheduler(final SimpleJobDemo simpleJob,
	@Value("${leonJob.cron}") final String cron,
	@Value("${leonJob.shardingTotalCount}") final int shardingTotalCount,
	@Value("${leonJob.shardingItemParameters}") final String
	shardingItemParameters) {
		return new SpringJobScheduler(simpleJob, regCenter,
		getLiteJobConfiguration(simpleJob.getClass(), cron, shardingTotalCount, shardingItemParameters));
	}
	
	private LiteJobConfiguration getLiteJobConfiguration(final Class<? extends SimpleJob> jobClass,
	final String cron,
	final int shardingTotalCount,
	final String shardingItemParameters) {
		return LiteJobConfiguration.newBuilder(new SimpleJobConfiguration(
		JobCoreConfiguration.newBuilder(jobClass.getName(), cron, shardingTotalCount)
		.shardingItemParameters(shardingItemParameters).build()
		, jobClass.getCanonicalName())
		).overwrite(true).build();
	}
}

作业运行

先把application.properties 中的分片数全部改成1

启动com.leon.EjobApp 的main 方法

package com.leon;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class EjobApp {
	public static void main(String[] args) {
		SpringApplication.run(EjobApp.class, args);
	}
}

分片策略

分片项与分片参数

任务分片,是为了实现把一个任务拆分成多个子任务,在不同的ejob 示例上执行。例如100W 条数据,在配置文件中指定分成10 个子任务(分片项),这10 个子任务再按照一定的规则分配到5 个实际运行的服务器上执行。除了直接用分片项ShardingItem获取分片任务之外,还可以用item 对应的parameter 获取任务。

standalone 工程:simple.SimpleJobTest

JobCoreConfiguration coreConfig = JobCoreConfiguration.newBuilder("MySimpleJob", "0/2 * * * * ?",
4).shardingItemParameters("0=RDP, 1=CORE, 2=SIMS, 3=ECIF").build();
package simple;

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.api.strategy.impl.AverageAllocationJobShardingStrategy;
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 SimpleJobTest {
    // TODO 如果修改了代码,跑之前清空ZK
    public static void main(String[] args) {
        // ZK注册中心
        CoordinatorRegistryCenter regCenter = new ZookeeperRegistryCenter(new ZookeeperConfiguration("localhost:2181", "leon-ejob-standalone"));
        regCenter.init();

        // 数据源,使用DBCP
/*        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/elastic_job_log");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        JobEventConfiguration jobEventConfig = new JobEventRdbConfiguration(dataSource);*/

        // 定义作业核心配置
        // TODO 如果修改了代码,跑之前清空ZK
        JobCoreConfiguration coreConfig = JobCoreConfiguration.newBuilder("MySimpleJob", "0/2 * * * * ?", 4).shardingItemParameters("0=RDP, 1=CORE, 2=SIMS, 3=ECIF").failover(true).build();
        // 定义SIMPLE类型配置
        SimpleJobConfiguration simpleJobConfig = new SimpleJobConfiguration(coreConfig, MySimpleJob.class.getCanonicalName());

        // 作业分片策略
        // 基于平均分配算法的分片策略
        String jobShardingStrategyClass = AverageAllocationJobShardingStrategy.class.getCanonicalName();

        // 定义Lite作业根配置
        // LiteJobConfiguration simpleJobRootConfig = LiteJobConfiguration.newBuilder(simpleJobConfig).jobShardingStrategyClass(jobShardingStrategyClass).build();
        LiteJobConfiguration simpleJobRootConfig = LiteJobConfiguration.newBuilder(simpleJobConfig).build();

        // 构建Job
        new JobScheduler(regCenter, simpleJobRootConfig).init();
        // new JobScheduler(regCenter, simpleJobRootConfig, jobEventConfig).init();
    }

}

springboot 工程,在application.properties 中定义。

定义几个分片项,一个任务就会有几个线程去运行它。

注意:分片个数和分片参数要一一对应。通常把分片项设置得比E-Job 服务器个数大一些,比如3 台服务器,分成9 片,这样如果有服务器宕机,分片还可以相对均匀。4.7.2 分片验证

为避免运行的任务太多看不清楚运行结果,可以注释在ElasticJobConfig 中注释DataFlowJob 和ScriptJob。SimpleJob 的分片项改成2。

直接运行com.leon.EjobApp。

或者打成jar 包:mvn package -Dmaven.test.skip=true

Jar 包路径:ejob-springboot\target\ejob-springboot-0.0.1-SNAPSHOT.jar

修改名称为ejob.jar 放到D 盘下。

多实例运行(单机):

java –jar ejob.jar

1、多运行一个点,任务不会重跑(两个节点各获得一个分片项)

2、关闭一个节点,任务不会漏跑

分片策略

http://elasticjob.io/docs/elastic-job-lite/02-guide/job-sharding-strategy/

分片项如何分配到服务器?这个跟分片策略有关。

策略类描述具体规则
AverageAllocationJobShardin
gStrategy
基于平均分配算法的分片策
略,也是默认的分片策略。
如果分片不能整除,则不能整除的多余分片将依
次追加到序号小的服务器。如:
 如果有3 台服务器,分成9 片,则每台服务
器分到的分片是: 1=[0,1,2], 2=[3,4,5],
3=[6,7,8]
 如果有3 台服务器,分成8 片,则每台服务
器分到的分片是:1=[0,1,6], 2=[2,3,7], 3=[4,5]
 如果有3 台服务器,分成10 片,则每台服务
器分到的分片是: 1=[0,1,2,9], 2=[3,4,5],
3=[6,7,8]
OdevitySortByNameJobShar
dingStrategy
根据作业名的哈希值奇偶数
决定IP 升降序算法的分片策
略。
根据作业名的哈希值奇偶数决定IP 升降序算法的
分片策略。
 作业名的哈希值为奇数则IP 升序。
 作业名的哈希值为偶数则IP 降序。
用于不同的作业平均分配负载至不同的服务器。
RotateServerByNameJobSha
rdingStrategy
根据作业名的哈希值对服务
器列表进行轮转的分片策
略。
 
自定义分片策略 实现JobShardingStrategy 接口并实现sharding 方
法,接口方法参数为作业服务器IP 列表和分片策
略选项,分片策略选项包括作业名称,分片总数
以及分片序列号和个性化参数对照表,可以根据
需求定制化自己的分片策略。

AverageAllocationJobShardingStrategy 的缺点是,一旦分片数小于作业服务器数,作业将永远分配至IP 地址靠前的服务
器,导致IP 地址靠后的服务器空闲。而OdevitySortByNameJobShardingStrategy 则可以根据作业名称重新分配服务器负
载。如:

如果有3 台服务器,分成2 片,作业名称的哈希值为奇数,则每台服务器分到的分片是:1=[0], 2=[1], 3=[]

如果有3 台服务器,分成2 片,作业名称的哈希值为偶数,则每台服务器分到的分片是:3=[0], 2=[1], 1=[]

在Lite 配置中指定分片策略:

String jobShardingStrategyClass = AverageAllocationJobShardingStrategy.class.getCanonicalName();
LiteJobConfiguration simpleJobRootConfig =
LiteJobConfiguration.newBuilder(simpleJobConfig).jobShardingStrategyClass(jobShardingStrategyClass).build();

分片方案

获取到分片项shardingItem 之后,怎么对数据进行分片嗯?

1、对业务主键进行取模,获取余数等于分片项的数据

举例:获取到的sharding item 是0,1

在SQL 中加入过滤条件:where mod(id, 4) in (1, 2)。

这种方式的缺点:会导致索引失效,查询数据时会全表扫描。

解决方案:在查询条件中在增加一个索引条件进行过滤。

2、在表中增加一个字段,根据分片数生成一个mod 值。取模的基数要大于机器数。否则在增加机器后,会导致机器空闲。例如取模基数是2,而服务器有5 台,那么有三台服务器永远空闲。而取模基数是10,生成10 个shardingItem,可以分配到5 台服务器。当然,取模基数也可以调整。

3、如果从业务层面,可以用ShardingParamter 进行分片。

例如0=RDP, 1=CORE, 2=SIMS, 3=ECIF

List<users> = SELECT * FROM user WHERE status = 0 AND SYSTEM_ID = 'RDP' limit 0, 100。

在Spring Boot 中要Elastic-Job 要配置的内容太多了,有没有更简单的添加任务的方法呢?比如在类上添加一个注解?这个时候我们就要用到starter 了。

e-job starter

Git 上有一个现成的实现

https://github.com/TFdream/elasticjob-spring-boot-starter

工程:elasticjob-spring-boot-starter

需求(一个starter 应该有什么样子):

需求实现作用
可以在启动类上使用@Enable 功
能开启E-Job 任务调度
注解@EnableElasticJob在自动配置类上用@ConditionalOnBean
决定是否自动配置
可以在properties 或yml 中识别配
置内容
配置类RegCenterProperties.java支持在properties 文件中使用
elasticjob.regCenter 前缀,配置注册中心
参数
在类上加上注解,直接创建任务注解@JobScheduled配置任务参数,包括定分片项、分片参
数等等
不用创建ZK注册中心自动配置类
RegCentreAutoConfiguration.java
注入从RegCenterProperties.java 读取到
的参数,自动创ZookeeperConfiguration
不用创建三级(Core、Type、Lite)
配置
自动配置类
JobAutoConfiguration.java
读取注解的参数, 创建
JobCoreConfiguration 、
JobTypeConfiguration 、
LiteJobConfiguration
在注册中心创建之后再创建
Spring Boot 启动时自动配置创建
Resource/META-INF/spring.factori
es
指定两个自动配置类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
io.dreamstudio.elasticjob.autoconfigure.RegCentreAutoConfiguration,\
io.dreamstudio.elasticjob.autoconfigure.JobAutoConfiguration

打包starter 的工程,引入starter 的依赖,即可在项目中使用注解开启任务调度功能。

E-Job 原理

启动

standalone 工程

new JobScheduler(regCenter, simpleJobRootConfig).init();

init 方法

public void init() {
	LiteJobConfiguration liteJobConfigFromRegCenter = schedulerFacade.updateJobConfiguration(liteJobConfig);
	// 设置分片数
	JobRegistry.getInstance().setCurrentShardingTotalCount(liteJobConfigFromRegCenter.getJobName(),
	liteJobConfigFromRegCenter.getTypeConfig().getCoreConfig().getShardingTotalCount());
	// 构建任务,创建调度器
	JobScheduleController jobScheduleController = new JobScheduleController(
	createScheduler(),createJobDetail(liteJobConfigFromRegCenter.getTypeConfig().getJobClass()),
	liteJobConfigFromRegCenter.getJobName());
	// 在ZK 上注册任务
	JobRegistry.getInstance().registerJob(liteJobConfigFromRegCenter.getJobName(), jobScheduleController, regCenter);
	// 添加任务信息并进行节点选举
	schedulerFacade.registerStartUpInfo(!liteJobConfigFromRegCenter.isDisabled());
	// 启动调度器
	jobScheduleController.scheduleJob(liteJobConfigFromRegCenter.getTypeConfig().getCoreConfig().getCron());
}

registerStartUpInfo 方法

public void registerStartUpInfo(final boolean enabled) {
	// 启动所有的监听器
	listenerManager.startAllListeners();
	// 节点选举
	leaderService.electLeader();
	// 服务信息持久化(写到ZK)
	serverService.persistOnline(enabled);
	// 实例信息持久化(写到ZK)
	instanceService.persistOnline();
	// 重新分片
	shardingService.setReshardingFlag();
	// 监控信息监听器
	monitorService.listen();
	// 自诊断修复,使本地节点与ZK 数据一致
	if (!reconcileService.isRunning()) {
		reconcileService.startAsync();
	}
}

监听器用于监听ZK 节点信息的变化。

启动的时候进行主节点选举

/**
* 选举主节点.
*/
public void electLeader() {
	log.debug("Elect a new leader now.");
	jobNodeStorage.executeInLeader(LeaderNode.LATCH, new LeaderElectionExecutionCallback());
	log.debug("Leader election completed.");
}

Latch 是一个分布式锁,选举成功后在instance 写入服务器信息。

// 服务信息持久化(写到ZK servers 节点)

serverService.persistOnline(enabled);

以下是单机运行多个实例:

// 实例信息持久化(写到ZK instances 节点)

instanceService.persistOnline();

运行了两个实例:

任务执行与分片原理

关注两个问题:

1、LiteJob 是怎么被执行的?

2、分片项是怎么分配给不同的服务实例的?

在创建Job 的时候(createJobDetail),创建的是实现了Quartz 的Job 接口的LiteJob 类,LiteJob 类实现了Quartz 的Job 接口。

在LiteJob 的execute 方法中获取对应类型的执行器,调用execute()方法。

public static AbstractElasticJobExecutor getJobExecutor(final ElasticJob elasticJob, final JobFacade jobFacade) {
	if (null == elasticJob) {
		return new ScriptJobExecutor(jobFacade);
	}
	if (elasticJob instanceof SimpleJob) {
		return new SimpleJobExecutor((SimpleJob) elasticJob, jobFacade);
	}
	if (elasticJob instanceof DataflowJob) {
		return new DataflowJobExecutor((DataflowJob) elasticJob, jobFacade);
	}
	throw new JobConfigurationException("Cannot support job type '%s'", elasticJob.getClass().getCanonicalName());
}

EJOB 提供管理任务执行器的抽象类AbstractElasticJobExecutor,核心动作在execute()方法中执行。

public final void execute() {

调用了另一个execute()方法,122 行:

execute(shardingContexts, JobExecutionEvent.ExecutionSource.NORMAL_TRIGGER);
private void execute(final ShardingContexts shardingContexts, final JobExecutionEvent.ExecutionSource
executionSource) {

在这个execute 方法中又调用了process()方法,150 行

private void process(final ShardingContexts shardingContexts, final JobExecutionEvent.ExecutionSource
executionSource) {
	Collection<Integer> items = shardingContexts.getShardingItemParameters().keySet();
	// 只有一个分片项时,直接执行
	if (1 == items.size()) {
		int item = shardingContexts.getShardingItemParameters().keySet().iterator().next();
		JobExecutionEvent jobExecutionEvent = new JobExecutionEvent(shardingContexts.getTaskId(), jobName,
		executionSource, item);
		process(shardingContexts, item, jobExecutionEvent);
		return;
	}
	final CountDownLatch latch = new CountDownLatch(items.size());
	// 本节点遍历执行相应的分片信息
	for (final int each : items) {
		final JobExecutionEvent jobExecutionEvent = new JobExecutionEvent(shardingContexts.getTaskId(), jobName,
		executionSource, each);
		if (executorService.isShutdown()) {
			return;
		}
		executorService.submit(new Runnable() {
			@Override
			public void run() {
				try {
					process(shardingContexts, each, jobExecutionEvent);
				} finally {
					latch.countDown();
				}
			}
		});
	}
	try {
		// 等待所有的分片项任务执行完毕
		latch.await();
	} catch (final InterruptedException ex) {
		Thread.currentThread().interrupt();
	}
}

又调用了另一个process()方法,206 行

protected abstract void process(ShardingContext shardingContext);

交给具体的实现类(SimpleJobExecutor、DataflowJobExecutor、ScriptJobExecutor)去处理。

最终调用到任务类

@Override
protected void process(final ShardingContext shardingContext) {
	simpleJob.execute(shardingContext);
}

失效转移

所谓失效转移,就是在执行任务的过程中发生异常时,这个分片任务可以在其他节点再次执行。

simple.SimpleJobTest,failover 方法:

// 设置失效转移
JobCoreConfiguration coreConfig = JobCoreConfiguration.newBuilder("MySimpleJob", "0/2 * * * * ?",
4).shardingItemParameters("0=RDP, 1=CORE, 2=SIMS, 3=ECIF").failover(true).build();

FailoverListenerManager 监听的是zk 的instance 节点删除事件。如果任务配置了failover 等于true,其中某个instance 与zk 失去联系或被删除,并且失效的节点又不是本身,就会触发失效转移逻辑。

Job 的失效转移监听来源于FailoverListenerManager 中内部类JobCrashedJobListener 的dataChanged 方法。

当节点任务失效时会调用JobCrashedJobListener 监听器,此监听器会根据实例id获取所有的分片,然后调用FailoverService 的setCrashedFailoverFlag 方法,将每个分片id 写到/jobName/leader/failover/items 下,例如原来的实例负责1、2 分片项,
那么items 节点就会写入1、2,代表这两个分片项需要失效转移。

protected void dataChanged(final String path, final Type eventType, final String data) {
	if (isFailoverEnabled() && Type.NODE_REMOVED == eventType && instanceNode.isInstancePath(path)) {
		String jobInstanceId = path.substring(instanceNode.getInstanceFullPath().length() + 1);
			if (jobInstanceId.equals(JobRegistry.getInstance().getJobInstance(jobName).getJobInstanceId())) {
			return;
		}
		List<Integer> failoverItems = failoverService.getFailoverItems(jobInstanceId);
		if (!failoverItems.isEmpty()) {
			for (int each : failoverItems) {
				// 设置失效的分片项标记
				failoverService.setCrashedFailoverFlag(each);
				failoverService.failoverIfNecessary();
			}
		} else {
			for (int each : shardingService.getShardingItems(jobInstanceId)) {
				failoverService.setCrashedFailoverFlag(each);
				failoverService.failoverIfNecessary();
			}
		}
	}
}

然后接下来调用FailoverService 的failoverIfNessary 方法,首先判断是否需要失败转移,如果可以需要则只需作业失败转移。

public void failoverIfNecessary() {
	if (needFailover()) {
		jobNodeStorage.executeInLeader(FailoverNode.LATCH, new FailoverLeaderExecutionCallback());
	}
}

条件一:${JOB_NAME}/leader/failover/items/${ITEM_ID} 有失效转移的作业分片项。

条件二:当前作业不在运行中。

private boolean needFailover() {
	return jobNodeStorage.isJobNodeExisted(FailoverNode.ITEMS_ROOT)
	&& !jobNodeStorage.getJobNodeChildrenKeys(FailoverNode.ITEMS_ROOT).isEmpty()
	&& !JobRegistry.getInstance().isJobRunning(jobName);
}

在主节点执行操作

public void executeInLeader(final String latchNode, final LeaderExecutionCallback callback) {
	try (LeaderLatch latch = new LeaderLatch(getClient(), jobNodePath.getFullPath(latchNode))) {
		latch.start();
		latch.await();
		callback.execute();
		//CHECKSTYLE:OFF
	} catch (final Exception ex) {
		//CHECKSTYLE:ON
		handleException(ex);
	}
}

1、再次判断是否需要失效转移;

2、从注册中心获得一个`${JOB_NAME}/leader/failover/items/${ITEM_ID}` 作业分片项;

3、在注册中心节点`${JOB_NAME}/sharding/${ITEM_ID}/failover` 注册作业分片项为当前作业节点;

4、然后移除任务转移分片项;

5、最后调用执行,提交任务。

class FailoverLeaderExecutionCallback implements LeaderExecutionCallback {
	@Override
	public void execute() {
		// 判断是否需要失效转移
		if (JobRegistry.getInstance().isShutdown(jobName) || !needFailover()) {
			return;
		}
		// 从${JOB_NAME}/leader/failover/items/${ITEM_ID}获得一个分片项
		int crashedItem =
		Integer.parseInt(jobNodeStorage.getJobNodeChildrenKeys(FailoverNode.ITEMS_ROOT).get(0));
		log.debug("Failover job '{}' begin, crashed item '{}'", jobName, crashedItem);
		// 在注册中心节点`${JOB_NAME}/sharding/${ITEM_ID}/failover`注册作业分片项为当前作业节点
		jobNodeStorage.fillEphemeralJobNode(FailoverNode.getExecutionFailoverNode(crashedItem),
		JobRegistry.getInstance().getJobInstance(jobName).getJobInstanceId());
		// 移除任务转移分片项
		jobNodeStorage.removeJobNodeIfExisted(FailoverNode.getItemsNode(crashedItem));
		JobScheduleController jobScheduleController = JobRegistry.getInstance().getJobScheduleController(jobName);
		if (null != jobScheduleController) {
			// 提交任务
			jobScheduleController.triggerJob();
		}
	}
}

这里仅仅是触发作业,而不是立即执行。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值