Elastic-Job:Elastic-Job作业分片是什么、Elastic-Job作业分片策略及源码分析、Elastic-Job自定义分片策略、Elastic-Job作业分片配置方式
- Elastic-Job作业分片是什么
- Elastic-Job作业分片策略及源码分析
- JobShardingStrategyFactory:分片策略工厂创建作业分片策略实例
- 分片策略JobShardingStrategy接口
- Elastic-Job-Lite提供三种自带的作业分片策略
- AverageAllocationJobShardingStrategy:平均分配
- AverageAllocationJobShardingStrategy 源码分析
- OdevitySortByNameJobShardingStrategy:根据作业名的哈希值分配
- OdevitySortByNameJobShardingStrategy源码分析
- RotateServerByNameJobShardingStrategy:根据作业名的哈希值对作业节点列表进行轮转的分片策略
- RotateServerByNameJobShardingStrategy源码分析
- Elastic-Job自定义分片策略
- Elastic-Job作业分片配置方式
Elastic-Job作业分片是什么
作业分片是指任务的分布式执行,需要将一个任务拆分为多个独立的任务项,然后由分布式的应用实例分别执行某一个或几个分片项。
如两台服务器,每台服务器跑一个应用实例,为了快速执行作业,可以将作业分成四片,每个应用实例各执行两片。
通过任务合理的分片化,从而达到任务并行处理的效果,最大限度的提高执行作业的吞吐量。
分布式调度一定要避免数据重复处理,因此要保证拿到的数据是不一样的
合理的分片化
分片是逻辑拆分,比如你要根据什么去拆分。
比如用性别分片,让A进程去处理男性的分片任务,让B进程去处理女性的分片任务。
真实场景分片到底怎么拆分逻辑是由应用开发者决定的。
分片项与业务处理解耦
Elastic-Job并不直接提供数据处理的功能,框架只会将分片项分配至各个运行中的作业服务器,开发者需要自行处理分片项与真实数据的对应关系。
就是说你的定时任务执行的业务逻辑,在方法体execute(ShardingContext shardingContext),Elastic-Job提供了shardingContext.getShardingItem()来让你可以拿到分片参数,要由你自己 根据当前分片参数判断执行对应的业务逻辑方法 ,Elastic-Job只会将分片项分配至各个运行中的作业服务器。
最大限度利用资源
将分片设置为大于服务器的数量,最好是大于服务器倍数的数量,这样有利于作业将合理利用分布式资源,动态的分配分片项。
例如: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作业分片策略及源码分析
JobShardingStrategyFactory:分片策略工厂创建作业分片策略实例
作业进行分片计算时,作业分片策略工厂( JobShardingStrategyFactory ) 会创建作业分片策略实例:
public final class JobShardingStrategyFactory {
/**
* 获取作业分片策略实例.
*
* @param jobShardingStrategyClassName 作业分片策略类名
* @return 作业分片策略实例
*/
public static JobShardingStrategy getStrategy(final String jobShardingStrategyClassName) {
if (Strings.isNullOrEmpty(jobShardingStrategyClassName)) {
return new AverageAllocationJobShardingStrategy();
}
try {
Class<?> jobShardingStrategyClass = Class.forName(jobShardingStrategyClassName);
if (!JobShardingStrategy.class.isAssignableFrom(jobShardingStrategyClass)) {
throw new JobConfigurationException("Class '%s' is not job strategy class", jobShardingStrategyClassName);
}
return (JobShardingStrategy) jobShardingStrategyClass.newInstance();
} catch (final ClassNotFoundException | InstantiationException | IllegalAccessException ex) {
throw new JobConfigurationException("Sharding strategy class '%s' config error, message details are '%s'", jobShardingStrategyClassName, ex.getMessage());
}
}
}
分片策略JobShardingStrategy接口
com.dangdang.ddframe.job.lite.api.strategy.JobShardingStrategyFactory,作业分片策略接口。分片策略通过实现JobShardingStrategy接口的 sharding(…) 方法提供作业分片的计算。
public interface JobShardingStrategy {
/**
* 作业分片.
*
* @param jobInstances 所有参与分片的单元列表
* @param jobName 作业名称
* @param shardingTotalCount 分片总数
* @return 分片结果
*/
Map<JobInstance, List<Integer>> sharding(List<JobInstance> jobInstances, String jobName, int shardingTotalCount);
}
Elastic-Job-Lite提供三种自带的作业分片策略
Elastic-Job-Lite 提供三种自带的作业分片策略(JobShardingStrategy的3个实现类):
- AverageAllocationJobShardingStrategy:基于平均分配算法的分片策略。
- OdevitySortByNameJobShardingStrategy:根据作业名的哈希值奇偶数决定IP升降序算法的分片策略。
- RotateServerByNameJobShardingStrategy:根据作业名的哈希值对作业节点列表进行轮转的分片策略。
AverageAllocationJobShardingStrategy:平均分配
全路径:
com.dangdang.ddframe.job.lite.api.strategy.impl.AverageAllocationJobShardingStrategy
策略说明:
基于平均分配算法的分片策略,也是默认的分片策略
如果分片不能整除,则不能整除的多余分片将依次追加到序号小的服务器。策略举例:
假设有3台服务器,
分成9片,则每台服务器分到的分片是:1【0,1,2】,2【3,4,5】,3【6,7,8】
分成8片,则每台服务器分到的分片是:1【0,1,6】,2【2,3,7】,3【4,5】
分成10片,则每台服务器分到的分片是:1【0,1,2】,2【3,4,5】,3【6,7,8】
AverageAllocationJobShardingStrategy 源码分析
public final class AverageAllocationJobShardingStrategy implements JobShardingStrategy {
@Override
public Map<JobInstance, List<Integer>> sharding(final List<JobInstance> jobInstances, final String jobName, final int shardingTotalCount) {
// 不存在 作业运行实例
if (jobInstances.isEmpty()) {
return Collections.emptyMap();
}
// 分配能被整除的部分
Map<JobInstance, List<Integer>> result = shardingAliquot(jobInstances, shardingTotalCount);
// 分配不能被整除的部分
addAliquant(jobInstances, shardingTotalCount, result);
return result;
}
}
shardingAliquot(…) 方法分配能被整除的部分
private Map<JobInstance, List<Integer>> shardingAliquot(final List<JobInstance> shardingUnits, final int shardingTotalCount) {
Map<JobInstance, List<Integer>> result = new LinkedHashMap<>(shardingTotalCount, 1);
int itemCountPerSharding = shardingTotalCount / shardingUnits.size(); // 每个作业运行实例分配的平均分片数
int count = 0;
for (JobInstance each : shardingUnits) {
List<Integer> shardingItems = new ArrayList<>(itemCountPerSharding + 1);
// 顺序向下分配
for (int i = count * itemCountPerSharding; i < (count + 1) * itemCountPerSharding; i++) {
shardingItems.add(i);
}
result.put(each, shardingItems);
count++;
}
return result;
}
addAliquant(…) 方法分配能不被整除的部分:如果分片不能整除,则不能整除的多余分片将依次追加到序号小的服务器
private void addAliquant(final List<JobInstance> shardingUnits, final int shardingTotalCount, final Map<JobInstance, List<Integer>> shardingResults) {
int aliquant = shardingTotalCount % shardingUnits.size(); // 余数
int count = 0;
for (Map.Entry<JobInstance, List<Integer>> entry : shardingResults.entrySet()) {
if (count < aliquant) {
entry.getValue().add(shardingTotalCount / shardingUnits.size() * shardingUnits.size() + count);
}
count++;
}
}
OdevitySortByNameJobShardingStrategy:根据作业名的哈希值分配
全路径:
com.dangdang.ddframe.job.lite.api.strategy.impl.OdevitySortByNameJobShardingStrategy
策略说明:
根据作业名的哈希值的奇偶数决定IP升降序算法的分片策略,常用于不同作业平均分配在不同的服务器上。
- 作业名的哈希值为奇数则IP升序
- 作业名的哈希值为偶数则IP降序
AverageAllocationJobShardingStrategy的缺点是,一旦分片数小于作业服务器数,作业将永远分配至IP地址靠前的服务器,导致IP地址靠后的服务器空闲
OdevitySortByNameJobShardingStrategy可以根据作业名重新分配服务器。
策略举例:
假设有3台服务器,分成2片,
作业名称的哈希值为奇数,则每台服务器分到的分片是:1【0】,2【1】,3【】
作业名称的哈希值为偶数,则每台服务器分到的分片是:3【0】,2【1】,1【】
OdevitySortByNameJobShardingStrategy源码分析
public Map<JobInstance, List<Integer>> sharding(final List<JobInstance> jobInstances, final String jobName, final int shardingTotalCount) {
long jobNameHash = jobName.hashCode();
if (0 == jobNameHash % 2) {
//判断到作业名的哈希值为偶数时,进行数组反转
Collections.reverse(jobInstances);
}
return averageAllocationJobShardingStrategy.sharding(jobInstances, jobName, shardingTotalCount);
}
jobInstances 已经是按照 IP 进行降序的数组。所以当判断到作业名的哈希值为偶数时,进行数组反转( Collections#reverse(…) )实现按照 IP 升序。下jobInstances 按照 IP 进行降序在这里处理:
public List<String> getChildrenKeys(final String key) {
try {
List<String> result = client.getChildren().forPath(key);
Collections.sort(result, new Comparator<String>() {
@Override
public int compare(final String o1, final String o2) {
return o2.compareTo(o1);
}
});
return result;
} catch (final Exception ex) {
RegExceptionHandler.handleException(ex);
return Collections.emptyList();
}
}
RotateServerByNameJobShardingStrategy:根据作业名的哈希值对作业节点列表进行轮转的分片策略
全路径:
com.dangdang.ddframe.job.lite.api.strategy.impl.OdevitySortByNameJobShardingStrategy
策略说明:
根据作业名的哈希值对作业节点列表进行轮转的分片策略
策略举例:
假设有3台服务器,顺序为 【0, 1, 2】,如果作业名的哈希值根据作业分片总数取模为 1, 作业节点顺序变为 【1, 2, 0】。
RotateServerByNameJobShardingStrategy源码分析
public final class RotateServerByNameJobShardingStrategy implements JobShardingStrategy {
private AverageAllocationJobShardingStrategy averageAllocationJobShardingStrategy = new AverageAllocationJobShardingStrategy();
@Override
public Map<JobInstance, List<Integer>> sharding(final List<JobInstance> jobInstances, final String jobName, final int shardingTotalCount) {
return averageAllocationJobShardingStrategy.sharding(rotateServerList(jobInstances, jobName), jobName, shardingTotalCount);
}
private List<JobInstance> rotateServerList(final List<JobInstance> shardingUnits, final String jobName) {
int shardingUnitsSize = shardingUnits.size();
int offset = Math.abs(jobName.hashCode()) % shardingUnitsSize; // 轮转开始位置
if (0 == offset) {
return shardingUnits;
}
List<JobInstance> result = new ArrayList<>(shardingUnitsSize);
for (int i = 0; i < shardingUnitsSize; i++) {
int index = (i + offset) % shardingUnitsSize;
result.add(shardingUnits.get(index));
}
return result;
}
}
调用 rotateServerList(…) 实现作业节点数组轮转。
调用 AverageAllocationJobShardingStrategy.sharding(…) 方法完成最终作业分片计算。
Elastic-Job自定义分片策略
某些场景需要实现自定义的作业分片策略。通过定义类实现 JobShardingStrategy 接口即可:
public final class OOXXShardingStrategy implements JobShardingStrategy {
@Override
public Map<JobInstance, List<Integer>> sharding(final List<JobInstance> jobInstances, final String jobName, final int shardingTotalCount) {
// 实现逻辑
}
}
实现后,配置实现类的全路径到 Lite作业配置( LiteJobConfiguration )的 jobShardingStrategyClass 属性。
Elastic-Job作业分片配置方式
与配置通常的作业属性相同,在spring命名空间或者JobConfiguration中配置jobShardingStrategyClass,属性值是作业分片策略类的全路径。
分片策略配置xml方式
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:reg="http://www.dangdang.com/schema/ddframe/reg"
xmlns:job="http://www.dangdang.com/schema/ddframe/job"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.dangdang.com/schema/ddframe/reg
http://www.dangdang.com/schema/ddframe/reg/reg.xsd
http://www.dangdang.com/schema/ddframe/job
http://www.dangdang.com/schema/ddframe/job/job.xsd
">
<!--configure registry center -->
<reg:zookeeper id="regCenter" server-lists="localhost:2181" namespace="dd-job" base-sleep-time-milliseconds="1000" max-sleep-time-milliseconds="3000" max-retries="3" />
<!--configure job -->
<job:simple id="mySimpleJob" class="com.javacore.job.MySimpleJob" registry-center-ref="regCenter" cron="0/10 * * * * ?" sharding-total-count="3" sharding-item-parameters="0=A,1=B,2=C" job-sharding-strategy-class="com.dangdang.ddframe.job.lite.api.strategy.impl.AverageAllocationJobShardingStrategy"/>
</beans>
分片策略配置java方式
// 定义Lite作业根配置
LiteJobConfiguration simpleJobRootConfig = LiteJobConfiguration.newBuilder(simpleJobConfig).jobShardingStrategyClass("com.dangdang.ddframe.job.lite.api.strategy.impl.AverageAllocationJobShardingStrategy").overwrite(true).build();