文章目录
一、前言
elastic-job版本2.1.5
如果大部分应用分片数都是1,如果用默认的分片算法,那么所有任务都会分配到 Ip 最小的那个节点上,这样会导致严重的负载不均衡。好在elastic-job提供了3种分片算法
1、AverageAllocationJobShardingStrategy平均分配分片算法
全路径:
com.dangdang.ddframe.job.lite.api.strategy.impl.AverageAllocationJobShardingStrategy
策略说明:
基于平均分配算法的分片策略,也是默认的分片策略。
如果分片不能整除,则不能整除的多余分片将依次追加到序号小的服务器。如:
如果有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]
2、OdevitySortByNameJobShardingStrategy 作业名的哈希值奇偶数决定IP升降序算法
3、RotateServerByNameJobShardingStrategy 作业名的哈希值对服务器列表进行轮转的分片策略
这里先不展开这3种分片算法的源码了,源码解析可查看另一篇:
elastic-job源码分析
二、修改Job默认的分片算法
通过上面三种分片的分析,可以看出,如果用OdevitySortByNameJobShardingStrategy
那么任务会根据名字的hash 分配置 最大ip节点和最小ip节点上,那么靠中间位置的节点 也得不到利用。而RotateServerByNameJobShardingStrategy
作业名的哈希值对服务器列表进行轮转的分片策略。
那么下面修改job的分片策略:
如果注册job时不指定分片算法,就是默认的分片算法AverageAllocationJobShardingStrategy
这样所有任务在一个节点上运行。
1、通过xml配置job时修改分片算法
<job:simple id="MyShardingJob1" class="nick.test.elasticjob.MyShardingJob1" registry-center-ref="regCenter" cron="0/10 * * * * ?" sharding-total-count="5" sharding-item-parameters="0=A,1=B,2=C,3=D,4=E" job-sharding-strategy-class="com.dangdang.ddframe.job.lite.api.strategy.impl.OdevitySortByNameJobShardingStrategy"/>
2、如果通过bean注入Job的,那么添加如下
在定义LiteJobConfiguration时指定分片参数
LiteJobConfiguration getLiteJobConfiguration(TaskJobEnum taskJobEnum, Class<? extends SimpleJob> jobClass) {
// 定义作业核心配置
JobCoreConfiguration simpleCoreConfig = JobCoreConfiguration.newBuilder(taskJobEnum.getJobName(),
taskJobEnum.getJobCron(), taskJobEnum.getShardingTotalCount()).
shardingItemParameters(taskJobEnum.getShardingItemParameters()).build();
// 定义SIMPLE类型配置
SimpleJobConfiguration simpleJobConfig =
new SimpleJobConfiguration(simpleCoreConfig, jobClass.getCanonicalName());
// 定义Lite作业根配置
return LiteJobConfiguration.newBuilder(simpleJobConfig).
//jobShardingStrategyClass("com.dangdang.ddframe.job.lite.api.strategy.impl.RotateServerByNameJobShardingStrategy").
jobShardingStrategyClass("com.dangdang.ddframe.job.lite.api.strategy.impl.OdevitySortByNameJobShardingStrategy").
overwrite(true).build();
}
3、修改效果
1、不指定默认分片策略如下:
部署了4个实例,但所以任务都分片到一个节点下去了。
2、指定OdevitySortByNameJobShardingStrategy
因为通过elastic-job的控台不是很好观察 任务所在节点。可以直接通过浏览器查看 elastic-job-console 查看任务列表和查看任务详情时所调用的接口,如下
获取Job详情
http://elastic-job-console控台ip:8899/api/jobs/任务名字/sharding?order=asc&offset=0&limit=10&_=2342342423
// 获取jobs
http://elastic-job-console控台ip/api/jobs?order=asc&offset=0&limit=100&_=2342342342342
//带条件搜索
http://elastic-job-console控台ip:8899/api/jobs?search=搜索关键字&order=asc&offset=0&limit=10&_=2342342342342
可以用java程序调用上面接统计即可。
@Test
public void hhtp_test(){
List<JobEEEEEE> jobEEEEEES = new ArrayList<>();
CloseableHttpClient HTTP_CLIENT = HttpClients.createDefault();
//TaskJobEnum 这个枚举类定义了所有任务,所以直接遍历这个枚举可得到 这个应用注册到elastic-job上的任务名
for (TaskJobEnum value : TaskJobEnum.values()) {
JobEEEEEE each = new JobEEEEEE();
jobEEEEEES.add(each);
each.setJobName(value.getJobName());
String jobName = value.getJobName();
//elastic-job控台 查看Job详情的地址
String api = "elastic-job-console控台ip:8899/api/jobs/"+jobName+"/sharding?order=asc&offset=0&limit=10&_=34234234234";
HttpGet get = new HttpGet(api);
try {
HttpResponse response = HTTP_CLIENT.execute(get);
String res = EntityUtils.toString(response.getEntity());
List<HashMap> list = JSON.parseArray(res, HashMap.class);
//只设置了一个分片所以,这个就直接取的list.get(0)。如果任务设置了多个分片,那么得到 这个任务所执行的所有Ip
System.out.print(" "+ list.get(0).get("serverIp").toString());
System.out.println(jobName+" "+res);
//获取运行的ip
each.setServiceIP(list.get(0).get("serverIp").toString());
} catch (IOException e) {
e.printStackTrace();
}
}
Map<String,List<String>> map = new HashMap<>();
//将ip相同的 任务,加到同一个 列表中,最后 打印ip 上所有运行的作务名。
for (JobEEEEEE jobEEEEEE : jobEEEEEES) {
if(map.get(jobEEEEEE.getServiceIP())!=null){
map.get(jobEEEEEE.getServiceIP()).add(jobEEEEEE.getJobName());
}else{
List<String> objects = new ArrayList<>();
objects.add(jobEEEEEE.getJobName());
map.put(jobEEEEEE.getServiceIP(),objects);
}
}
for (Map.Entry<String, List<String>> entry : map.entrySet()) {
System.out.print(entry.getKey()+" "+entry.getValue().size()+" ");
System.out.println(entry.getValue());
}
}
得到统计数据:已分片的任务被分配到4个节点中的2个上去了。
节点Ip:172.23.7.170 运行任务数:4 任务名列表:[。。。。。。。]
节点Ip:172.23.133.167 运行任务数:10 任务名列表:[。。。。。。。]
3、指定RotateServerByNameJobShardingStrategy
4、如果不想调用http进行统计,还可以直接从zk获取任务节点进行统计
可以直接登录到zk 去查看elastic-job任务注册节点的路径结构。
用ZooInspector工具,登录zk。
可以直接连zk进行操作,也可以直接用ZookeeperRegistryCenter
操作,它封装了获取路径下数据的方法
@Autowired
ZookeeperRegistryCenter regCenter;
@Test
public void get_test1111(){
List<String> jobNames = regCenter.getChildrenKeys("/");
List<JobEEEEEE> jobEEEEEES = new ArrayList<>();
for (String jobName : jobNames) {
//过滤不想要的job
List<TaskJobEnum> joblocal = Arrays.asList(TaskJobEnum.values());
Optional<TaskJobEnum> first = joblocal.stream().filter(e -> e.getJobName().equals(jobName)).findFirst();
if(!first.isPresent()){
continue;
}
JobEEEEEE eacheeee = new JobEEEEEE();
jobEEEEEES.add(eacheeee);
eacheeee.setJobName(jobName);
//拼接路径 /jobName/sharding
String shardingRootPath = String.format("/%s/%s", jobName, "sharding");
//得到 /dd-job/jobName/sharing路径下的数据,也就是这个任所有分片项 0,1,2,3
List<String> items = regCenter.getChildrenKeys(shardingRootPath);
for (String each : items) {
String p = String.format("%s/%s/%s", shardingRootPath, each, "instance");
//得到 /dd-job/任务名/sharing/分片项/instance 下的实例id
String instanceId = regCenter.get(p);
instanceId = instanceId.substring(0,instanceId.indexOf("@"));
eacheeee.setServiceIP(instanceId);
}
}
//统计
Map<String,List<String>> map = new HashMap<>();
for (JobEEEEEE jobEEEEEE : jobEEEEEES) {
if(map.get(jobEEEEEE.getServiceIP())!=null){
map.get(jobEEEEEE.getServiceIP()).add(jobEEEEEE.getJobName());
}else{
List<String> objects = new ArrayList<>();
objects.add(jobEEEEEE.getJobName());
map.put(jobEEEEEE.getServiceIP(),objects);
}
}
//只要有效ip上的统计,因为有些作务 未执行,状态是 sharding状态 显示还是旧的运行ip
List<String> include = Arrays.asList("172.23.14.1","172.23.7.1","172.23.62.1","172.23.146.1");
for (Map.Entry<String, List<String>> entry : map.entrySet()) {
if(!include.contains(entry.getKey())){
continue;
}
System.out.print(entry.getKey()+" "+entry.getValue().size()+" ");
System.out.println(entry.getValue());
}
}
统计结果:
运行节点:172.23.146.1 任务数:8 任务名称例表:[]
运行节点:172.23.14.1 任务数:3 任务名称例表:[]
运行节点:172.23.7.1 任务数:4 任务名称例表:[]
运行节点:172.23.62.1 任务数:5 任务名称例表:[]