elastic-job源码分析之分片算法应用

一、前言

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  任务名称例表:[]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值