xxl-job分布式框架源码路由/负载均衡策略算法

【xxl-job源码篇】xxl-job的10种路由策略源码解读 为了保证任务稳定执行,xxl-job支持注册多个executor到注册中心,以保证任务能够稳定的执行,那么这些executor会以怎样的策略去执行呢,本章将从源码层面去解析xxl-job的策略的执行原理。 xxl-job为我们提供了如下策略 * 第一个 * 最后一个 * 轮询 * 随机 * 一致性HASH * 最不经常使用 * 最久未使用 * 故障转移 * 忙碌转移 * 分片执行 以上策略可以帮助我们在各种场景下都能应对自如 阅读详情

概述

xxl-job就是因为内涵丰富的调度策略,使得框架的多样性,灵活性更高。现在就开始讲解xxl-job的核心路由策略算法,总共有10种路由策略,对于以后想从事分布式微服务开发,任务调度的学习是很有必要的。

个人认为这个路由算法可以作为标准样例应用在其他分布式路由策略中,后面有时间的话我会整理为工具类
牧竹子注:2023/6/4

路由策略种类

  • 第一个
  • 最后一个
  • 随机选取
  • 轮询选取
  • 一致性hash
  • 最不经常使用 (LFU)
  • 最近最久未使用(LRU)
  • 故障转移
  • 忙碌转移
  • 分片广播

以上就是xxl-job内部封装的路由策略算法,也是很常见的路由算法,学习掌握之后对自己设计分布式架构很有帮助。
在这里插入图片描述
路由策略枚举类型所有的分片

拓展其他负载均衡算法

我们来看看一些常见中间件使用的负载均衡算法

Nginx

RoundRobin:轮询。
WeightedRoundRobin:加权轮询。
IPHash:按访问IP的Hash选择服务器。
URLHash:按请求URL的Hash选择服务器。
Fair:根据后端服务器的响应时间判断负载情况,从中选出负载最轻的机器进行分流。

Dubbo

RandomLoadBalance:加权随机。
RoundRobinLoadBalance:加权轮询。
LeastActionLoadBalance:最少链接数。
ShortestResponseLoadBalance:最短响应时间。
ConsistentHashLoadBalance:一致性Hash。

Ribbon

RoundRobinRule:轮询。
RandomRule:随机。
WeightedResponseTimeRule:根据响应时间来分配权重的方式,响应的越快,分配的值越大。
BestAvailableRule:会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务。
RetryRule:先按照轮询策略获取服务,如果获取服务失败则在指定时间内进行重试,获取可用的服务。
ZoneAvoidanceRule:根据性能和可用性选择服务。
AvailabilityFilteringRule:会先过滤掉由于多次访问故障而处于断路器状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问。

本节原文链接:https://blog.youkuaiyun.com/Anenan/article/details/116203518

路由策略讲解

第一个

获取注册地址集合的第一个

package com.xxl.job.admin.core.route.strategy;
 
import com.xxl.job.admin.core.route.ExecutorRouter;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
 
import java.util.List;
 
/**
 * Created by xuxueli on 17/3/10.
 */
public class ExecutorRouteFirst extends ExecutorRouter {
 
    @Override
    public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList){
        return new ReturnT<String>(addressList.get(0));
    }
 
}

最后一个

获取注册地址集合的最后一个

package com.xxl.job.admin.core.route.strategy;
 
import com.xxl.job.admin.core.route.ExecutorRouter;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
 
import java.util.List;
 
/**
 * Created by xuxueli on 17/3/10.
 */
public class ExecutorRouteLast extends ExecutorRouter {
 
    @Override
    public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
        return new ReturnT<String>(addressList.get(addressList.size()-1));
    }
 
}

随机选取

通过一个Random对象的nextInt方法在求出[0,addressList.size())区间内的任意一个index

package com.xxl.job.admin.core.route.strategy;
 
import com.xxl.job.admin.core.route.ExecutorRouter;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
 
import java.util.List;
import java.util.Random;
 
/**
 * Created by xuxueli on 17/3/10.
 */
public class ExecutorRouteRandom extends ExecutorRouter {
 
    private static Random localRandom = new Random();
 
    @Override
    public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
        String address = addressList.get(localRandom.nextInt(addressList.size()));
        return new ReturnT<String>(address);
    }
 
}

轮询选取

一个静态的ConcurrentMap对象,这个routeCountEachJob就是用来存放路由任务的,而且还设置了缓存时间,有效期为24小时,当超过24小时的时候,延迟调用时清空当前的缓存。

其中ConcurrentMap的key为jobId,value为当前jobId所对应的计数器,每访问一次就自增一,最大增到100000,然后又从[0,100)的随机数开始重新自增。这里100以内的次数随机获取,因此100以内特别是50前的数字可能要比100以后的数字选中机会少很多

这个算法的思想就是取余数,每次先计算出当前jobId所对应的计数器的值,然后 计数器的值 % addressList.size() 求得这一次轮询的数组下标index地址。

根据上面轮询的算法原理,前50的被选中概率要远小于100以后的值,因此可以把随机算法改成从0开始,那么基本都会轮询到

package com.xxl.job.admin.core.route.strategy;
 
import com.xxl.job.admin.core.route.ExecutorRouter;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
 
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;
 
/**
 * Created by xuxueli on 17/3/10.
 */
public class ExecutorRouteRound extends ExecutorRouter {


	//缓存jobId和对应被轮的序号 
    private static ConcurrentMap<Integer, AtomicInteger> routeCountEachJob = new ConcurrentHashMap<>();
    private static long CACHE_VALID_TIME = 0;//缓存到期实际
 
	//轮询算法的具体实现
    private static int count(int jobId) {
        // 缓存到期了则清空缓存
        if (System.currentTimeMillis() > CACHE_VALID_TIME) {
            routeCountEachJob.clear();
            CACHE_VALID_TIME = System.currentTimeMillis() + 1000*60*60*24;
        }
 
        AtomicInteger count = routeCountEachJob.get(jobId);
        if (count == null || count.get() > 1000000) {//初次和到达1000000了就从新开始初始化
            // 初始化时主动Random一次,缓解首次压力
            count = new AtomicInteger(new Random().nextInt(100));
        } else {
            // count++
            count.addAndGet(1);
        }
        routeCountEachJob.put(jobId, count);//放入jobId和对应轮询的序号
        return count.get();
    }
 
	//路由方法入口
    @Override
    public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
	//调用count(JobId)获取轮询到的次数,根据计数器值获取数组下标=count%list.size()
        String address = addressList.get(count(triggerParam.getJobId())%addressList.size());
        return new ReturnT<String>(address);
    }
}

一致性hash

一致哈希 是一种特殊的哈希算法。在使用一致哈希算法后,哈希表槽位数2^32是固定的,只需要对 K/n 个关键字重新映射,其中K是关键字的数量, n是槽位数量。然而在传统的哈希表中,添加或删除一个槽位的几乎需要对所有关键字进行重新映射。引入了一致性hash算法,即服务节点的变更不会造成大量的哈希重定位。

一致性hash引入之后,若服务器节点数量过少,有几率出现数据倾斜的情况,既大量的数据映射到某一区间,其它区间没有数据映射,造成了资源分配不均匀,为了解决这个问题,xxl-job源码引入了虚拟节点,既将每台服务器的节点都生成所对应的100个虚拟节点,这应少量的服务器节点通过引入虚拟节点,就会加大节点的数量,这样大量的节点分配到hash环上是比较均匀的,从而很容易的解决数据分配不均匀问题。

package com.xxl.job.admin.core.route.strategy;
 
import com.xxl.job.admin.core.route.ExecutorRouter;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
 
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
 
/**
 * 分组下机器地址相同,不同JOB均匀散列在不同机器上,保证分组下机器分配JOB平均;且每个JOB固定调度其中一台机器;
 *      a、virtual node:解决不均衡问题
 *      b、hash method replace hashCode:String的hashCode可能重复,需要进一步扩大hashCode的取值范围
 * Created by xuxueli on 17/3/10.
 */
public class ExecutorRouteConsistentHash extends ExecutorRouter {
 
    private static int VIRTUAL_NODE_NUM = 100;
 
    /**
     * 哈希槽定位
     * get hash code on 2^32 ring (md5散列的方式计算hash值)
     * @param key
     * @return
     */
    private static long hash(String key) {
 
        // md5 byte
        MessageDigest md5;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("MD5 not supported", e);
        }
        md5.reset();
        byte[] keyBytes = null;
        try {
            keyBytes = key.getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("Unknown string :" + key, e);
        }
 
        md5.update(keyBytes);
        byte[] digest = md5.digest();
 
        // hash code, Truncate to 32-bits
        long hashCode = ((long) (digest[3] & 0xFF) << 24)
                | ((long) (digest[2] & 0xFF) << 16)
                | ((long) (digest[1] & 0xFF) << 8)
                | (digest[0] & 0xFF);
 
        //通过md5算出的hashcode % 2^32 余数,将hash值散列在一致性hash环上  这个环分了2^32个位置
        long truncateHashCode = hashCode & 0xffffffffL;
        return truncateHashCode;
    }
 
    /**
     * 哈希槽定位
     * @return
     */
    public String hashJob(int jobId, List<String> addressList) {
 
        // ------A1------A2-------A3------
        // -----------J1------------------
        TreeMap<Long, String> addressRing = new TreeMap<Long, String>();
        for (String address: addressList) {
            for (int i = 0; i < VIRTUAL_NODE_NUM; i++) {
                //为每一个注册的节点分配100个虚拟节点,并算出这些节点的一致性hash值,存放到TreeMap中
                long addressHash = hash("SHARD-" + address + "-NODE-" + i);
                addressRing.put(addressHash, address);
            }
        }
        //第二步求出job的hash值 通过jobId计算
        long jobHash = hash(String.valueOf(jobId));
        //通过treeMap性质,所有的key都按照从小到大的排序,即按照hash值从小到大排序,通过tailMap 求出>=hash(jobId)的剩余一部分map,
        SortedMap<Long, String> lastRing = addressRing.tailMap(jobHash);
        if (!lastRing.isEmpty()) {
            //若找到则取第一个key,为带路由的地址
            return lastRing.get(lastRing.firstKey());
        }
        //若本身hash(jobId)为treeMap的最后一个key,则找当前treeMap的第一个key
        return addressRing.firstEntry().getValue();
    }
 
    @Override
    public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
        String address = hashJob(triggerParam.getJobId(), addressList);
        return new ReturnT<String>(address);
    }
 
}

最不经常使用 (LFU)

Least Recently Used:最近最近少使用频次,最少使用的作为首选

使用ConcurrentMap维护一个jobId为key的cachMap,vue为Map<String,int>对应url,使用次数的lfuItemMap

  • 第一次url地址依次放入lfuItemMap ,随机生成它的次数
  • 遍历url的list,把新加入的url和次数到达1000000 的url重置次数
  • 淘汰不存在的url:遍历lfuItemMap 的key,如果urlList中不存在则说明该地址已经无效需要移除
  • 对lfuItemMap进行升序排序,选第一个作为最少使用的值返回
package com.xxl.job.admin.core.route.strategy;
 
import com.xxl.job.admin.core.route.ExecutorRouter;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
 
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
 
/**
 * 单个JOB对应的每个执行器,使用频率最低的优先被选举
 *      a(*)、LFU(Least Frequently Used):最不经常使用,频率/次数
 *      b、LRU(Least Recently Used):最近最久未使用,时间
 *
 *      算法思想:
 *      构建一个作业和地址map    jobid -> addressList
 *      第一次随机的将任务所对应的执行器的注册地址编一个序列号
 *      然后将执行器的注册地址按照从小到大进行排序
 *      筛选过程找第一个序列号最小的作为下一次的路由地址
 *      随后将当前选中的地址编号值+1
 *      这样最终我们都会挑选编号最小的注册器地址作为下一个路由地址,既最不常使用的
 *
 * Created by xuxueli on 17/3/10.
 */
public class ExecutorRouteLFU extends ExecutorRouter {
 
    private static ConcurrentMap<Integer, HashMap<String, Integer>> jobLfuMap = new ConcurrentHashMap<Integer, HashMap<String, Integer>>();
    private static long CACHE_VALID_TIME = 0;
 
    public String route(int jobId, List<String> addressList) {
 
        // cache clear
        if (System.currentTimeMillis() > CACHE_VALID_TIME) {
            jobLfuMap.clear();
            //有效缓存时间为一天
            CACHE_VALID_TIME = System.currentTimeMillis() + 1000*60*60*24;
        }
 
        // lfu item init
        HashMap<String, Integer> lfuItemMap = jobLfuMap.get(jobId);     // Key排序可以用TreeMap+构造入参Compare;Value排序暂时只能通过ArrayList;
        if (lfuItemMap == null) {
            lfuItemMap = new HashMap<String, Integer>();
            jobLfuMap.putIfAbsent(jobId, lfuItemMap);   // 避免重复覆盖
        }
 
        // put new
        for (String address: addressList) {
            if (!lfuItemMap.containsKey(address) || lfuItemMap.get(address) >1000000 ) {
                lfuItemMap.put(address, new Random().nextInt(addressList.size()));  // 初始化时主动Random一次,缓解首次压力
            }
        }
        // remove old
        List<String> delKeys = new ArrayList<>();
        for (String existKey: lfuItemMap.keySet()) {
            if (!addressList.contains(existKey)) {
                delKeys.add(existKey);
            }
        }
        if (delKeys.size() > 0) {
            for (String delKey: delKeys) {
                lfuItemMap.remove(delKey);
            }
        }
 
        // load least userd count address
        List<Map.Entry<String, Integer>> lfuItemList = new ArrayList<Map.Entry<String, Integer>>(lfuItemMap.entrySet());
        Collections.sort(lfuItemList, new Comparator<Map.Entry<String, Integer>>() {
            @Override
            public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
                return o1.getValue().compareTo(o2.getValue());
            }
        });
 
        Map.Entry<String, Integer> addressItem = lfuItemList.get(0);
        String minAddress = addressItem.getKey();
        addressItem.setValue(addressItem.getValue() + 1);
 
        return addressItem.getKey();
    }
 
    @Override
    public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
        String address = route(triggerParam.getJobId(), addressList);
        return new ReturnT<String>(address);
    }
 
}

最近最久未使用(LRU)

LRU(Least Recently Used):最近最久未使用
这里关注的是最久未使用,不关注次数

因此LinkedHashMap的accessOrder可以直接满足这个要求,accessOrder默认值是false

  • false:按插入顺序排序,谁最先插入put就放入头部,最后进入放入尾部,FIFO特性

  • true:按访问顺序排序(get/put时排序)排序,谁被访问就放到头部,没有的则会排到尾部

  • 依次遍历urlList,把对应的url放入LinkedHashMap

  • 淘汰不存在的url:遍历LinkedHashMap的key,如果服务urlList中不存在该url则直接移除

  • 获取LinkedHashMap的第一个url作为服务地址返回

package com.xxl.job.admin.core.route.strategy;
 
import com.xxl.job.admin.core.route.ExecutorRouter;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
 
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
 
/**
 * 单个JOB对应的每个执行器,最久为使用的优先被选举
 *      a、LFU(Least Frequently Used):最不经常使用,频率/次数
 *      b(*)、LRU(Least Recently Used):最近最久未使用,时间
 *
 * Created by xuxueli on 17/3/10.
 */
public class ExecutorRouteLRU extends ExecutorRouter {
 
    private static ConcurrentMap<Integer, LinkedHashMap<String, String>> jobLRUMap = new ConcurrentHashMap<Integer, LinkedHashMap<String, String>>();
    private static long CACHE_VALID_TIME = 0;
 
    public String route(int jobId, List<String> addressList) {
 
        // cache clear
        if (System.currentTimeMillis() > CACHE_VALID_TIME) {
            jobLRUMap.clear();
            CACHE_VALID_TIME = System.currentTimeMillis() + 1000*60*60*24;
        }
 
        // init lru
        LinkedHashMap<String, String> lruItem = jobLRUMap.get(jobId);
        if (lruItem == null) {
            /**
             * LinkedHashMap
             *      a、accessOrder:true=访问顺序排序(get/put时排序);false=插入顺序排期;
             *      b、removeEldestEntry:新增元素时将会调用,返回true时会删除最老元素;可封装LinkedHashMap并重写该方法,比如定义最大容量,超出是返回true即可实现固定长度的LRU算法;
             */
            lruItem = new LinkedHashMap<String, String>(16, 0.75f, true);
            jobLRUMap.putIfAbsent(jobId, lruItem);
        }
 
        // put new
        for (String address: addressList) {
            if (!lruItem.containsKey(address)) {
                lruItem.put(address, address);
            }
        }
        // remove old
        List<String> delKeys = new ArrayList<>();
        for (String existKey: lruItem.keySet()) {
            if (!addressList.contains(existKey)) {
                delKeys.add(existKey);
            }
        }
        if (delKeys.size() > 0) {
            for (String delKey: delKeys) {
                lruItem.remove(delKey);
            }
        }
 
        // load
        String eldestKey = lruItem.entrySet().iterator().next().getKey();
        String eldestValue = lruItem.get(eldestKey);
        return eldestValue;
    }
 
    @Override
    public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
        String address = route(triggerParam.getJobId(), addressList);
        return new ReturnT<String>(address);
    }
 
}

故障转移

遍历所有的该组下的所有注册节点地址集合,然后分别进行心跳处理,直到找到一个发送心跳成功的节点作为下一次路由的节点。每次调用当前路由时都会促发一次执行寻找下一个路由节点提前备用

package com.xxl.job.admin.core.route.strategy;
 
import com.xxl.job.admin.core.scheduler.XxlJobScheduler;
import com.xxl.job.admin.core.route.ExecutorRouter;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
 
import java.util.List;
 
/**
 * Created by xuxueli on 17/3/10.
 * 故障转移路由策略
 * 思想:遍历所有的该组下的所有注册节点地址集合,然后分别进行心跳处理,直到找到一个发送心跳成功的节点作为下一次路由的节点
 */
public class ExecutorRouteFailover extends ExecutorRouter {
 
    @Override
    public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
 
        StringBuffer beatResultSB = new StringBuffer();
        for (String address : addressList) {
            // beat
            ReturnT<String> beatResult = null;
            try {
                ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
                beatResult = executorBiz.beat();
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
                beatResult = new ReturnT<String>(ReturnT.FAIL_CODE, ""+e );
            }
            beatResultSB.append( (beatResultSB.length()>0)?"<br><br>":"")
                    .append(I18nUtil.getString("jobconf_beat") + ":")
                    .append("<br>address:").append(address)
                    .append("<br>code:").append(beatResult.getCode())
                    .append("<br>msg:").append(beatResult.getMsg());
 
            // beat success
            if (beatResult.getCode() == ReturnT.SUCCESS_CODE) {
 
                beatResult.setMsg(beatResultSB.toString());
                beatResult.setContent(address);
                return beatResult;
            }
        }
        return new ReturnT<String>(ReturnT.FAIL_CODE, beatResultSB.toString());
 
    }
}

忙碌转移

忙碌转移也很容易理解,原理遍历所有的执行器,对所有执行器发送空闲心跳数据包
收集所有的返回信息,若当前机器繁忙则响应getCode500 否则空闲则getCode200
直到找到空闲的机器则返回该空闲机器的地址返回作为下次服务调用的节点

package com.xxl.job.admin.core.route.strategy;
 
import com.xxl.job.admin.core.scheduler.XxlJobScheduler;
import com.xxl.job.admin.core.route.ExecutorRouter;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.IdleBeatParam;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
 
import java.util.List;
 
/**
 * Created by xuxueli on 17/3/10.
 * 忙碌转移
 * 原理遍历所有的执行器,对所有执行器发送空闲心跳数据包
 * 收集所有的返回信息,若当前机器繁忙则响应getCode==500 否则空闲则getCode==200
 * 找到空闲的机器则返回该空闲机器的地址
 */
public class ExecutorRouteBusyover extends ExecutorRouter {
 
    @Override
    public ReturnT<String> route(TriggerParam triggerParam, List<String> addressList) {
        StringBuffer idleBeatResultSB = new StringBuffer();
        for (String address : addressList) {
            // beat
            ReturnT<String> idleBeatResult = null;
            try {
                ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
                idleBeatResult = executorBiz.idleBeat(new IdleBeatParam(triggerParam.getJobId()));
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
                idleBeatResult = new ReturnT<String>(ReturnT.FAIL_CODE, ""+e );
            }
            idleBeatResultSB.append( (idleBeatResultSB.length()>0)?"<br><br>":"")
                    .append(I18nUtil.getString("jobconf_idleBeat") + ":")
                    .append("<br>address:").append(address)
                    .append("<br>code:").append(idleBeatResult.getCode())
                    .append("<br>msg:").append(idleBeatResult.getMsg());
 
            // beat success
            if (idleBeatResult.getCode() == ReturnT.SUCCESS_CODE) {
                idleBeatResult.setMsg(idleBeatResultSB.toString());
                idleBeatResult.setContent(address);
                return idleBeatResult;
            }
        }
 
        return new ReturnT<String>(ReturnT.FAIL_CODE, idleBeatResultSB.toString());
    }
 
}

EmbedServer来处理这个请求,判断当前执行器节点是否执行当前任务或者当前执行器节点的任务队列是否为空,若既不是执行当前任务的节点或者任务队列为空则返回SUCCESS,以下代码就是上述所说。

else if ("/idleBeat".equals(uri)){
 IdleBeatParam idleBeatParam = GsonTool.fromJson(reguestData, IdleBeatParam.class);
return executorBiz.idleBeat(idleBeatParam);
}

//空闲心跳处理 查询当前处理器是否为执行状态
@Override
public ReturnT<string> idleBeat(IdleBeatParam idleBeatParam) {
	isRunningOrHasOueue
	boolean isRunningOrHasOueue = false:
	JobThread jobthread = XxlJobExecutor.loadJobThread(idleBeatParamgetJobid());
	if (jobThread != null && jobThread.isRunningorHasOueue()) {
		isRunningOrHasOueue = true;
	}
	if (isRunningOrHasQueue) {
		return new ReturnT<String>(ReturnT.FAIL CODE, msg: "job thread is running or has trigger gueue.");
	return ReturnT.sUCCEss;
	}

分片路由

这个实现还是有点意外的,没想到它就只是把注册地址遍历出来,依次给对应的路由地址发送参数(index,RegistryList().size()),这样每个任务执行器收到的是对应的index和分片总数size。是不是很意外,它比你想象中实现的要简单许多

 如果是分片路由,走的是这段逻辑
if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST == ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null)
                && group.getRegistryList() != null && !group.getRegistryList().isEmpty()
                && shardingParam == null) {
            for (int i = 0; i < group.getRegistryList().size(); i++) {
	            // 最后两个参数,i是当前机器在执行器集群当中的index,group.getRegistryList().size()为执行器总数=分片总数
                processTrigger(group, jobInfo, finalFailRetryCount, triggerType, i, group.getRegistryList().size());
            }
        } 

以上便是xxl-job所有的路由策略,除了分片广播,其他都是单例模式运行执行器,本章节完更。

更新时间2023/06/09

原文链接

https://blog.youkuaiyun.com/s6056826a/article/details/113447358

XXL-JOB Admin端架构与调度原理深度解析 分布式任务调度是微服务架构中的关键技术,其核心在于高效的任务分配与执行监控。XXL-JOB作为轻量级分布式任务调度平台,通过时间轮算法实现毫秒级任务触发,采用多线程池架构保证高并发处理能力。在技术实现上,Admin端基于Spring Boot+MyBatis技术栈,通过双服务架构(Netty+Tomcat)实现高性能通信,支持FIRST/LAST/ROUND/RANDOM四种路由策略。典型应用场景包括定时报表生成、分布式批处理等,其中改良版时间轮调度机制和动态线程池设计尤为值得关注。通过分析调度延迟监控指标 阅读详情

相关推荐

【项目实战】分布式定时任务框架XXL-JOB核心源码分析 - 作业调度和执行 (路由策略介绍)

路由策略决定了组中哪个作业实例将执行该作业

奶爸的编程之路,也就一周冷个三天 1237

分布式任务调度平台xxl-job的10种路由策略

路由策略种类: 1.第一个:当选择该策略时,会选择执行器注册地址的第一台机器执行,如果第一台机器出现故障,则调度任务失败。 2.最后一个:当选择该策略时,会选择执行器注册地址的最后一台机器执行,如果最后一台机器出现故障,则调度任务失败。 3.轮询:当选择该策略时,会按照执行器注册地址轮询分配任务,如果其中一台机器出现故障,调度任务失败,任务不会转移。 4.随机:当选择该策略时,会按照执行器注册地址随机分配任务,如果其中一台机器出现故障,调度任务失败,任务不会转移。 5.一致性HASH:当选择该策略

灰太狼 5806

XXL-Job实战指南:一篇文章让你成为任务调度高手!

XXL-Job 是一个开源的分布式任务调度框架,由国内知名技术团队进行维护和开发。它旨在解决分布式系统中的定时任务问题,提供了一整套完善的解决方案,使得任务调度变得更加简单、高效、可靠。自2015年开源以来,XXL-Job 已经在众多企业和项目中得到了广泛的应用,成为分布式任务调度领域的佼佼者。**轻量级:**XXL-Job 采用轻量级设计,核心代码不足千行,易于集成和部署。**功能丰富:**支持定时任务、周期任务、一次性任务等多种任务类型,满足不同业务场景的需求。

邓邓子的博客 4930

xxl-job(四)路由策略

接上一篇: 一、任务路由策略:总共有 1、第一个:始终执行执行器的第一个机器,如我配置了两个节点,则只执行第一个即141节点的服务

w_t_y_y的博客 1万+

XXL-JOB 从本地快速上手到核心架构深度解析

XXL-JOB分布式任务调度框架实战指南 摘要:XXL-JOB作为轻量级分布式任务调度框架,通过调度中心与执行器分离的设计实现高效任务调度。本文从本地部署入手,详细介绍了环境准备、数据库初始化和管理平台配置等基础操作。深入解析了框架的核心机制,包括自动注册与心跳、端口分工、8张核心数据表的作用等关键技术点。重点剖析了调度中心与执行器的交互流程,以及分片广播、故障转移等高级特性。文章还提供了任务开发示例代码,帮助开发者快速掌握自定义任务开发方法。最后总结了XXL-JOB的高可用设计思想及其在企业级应用中的优势

长河的博客 860

Win本地部署DeepSeek

Ollama 提供了简单的接口,用于创建、运行和管理这些模型,同时还维护了一个丰富的预构建模型库,可以轻松集成到各种应用程序中。重启ollama,具体操作为打开搜索ollama进程,将其结束进程,之后再执行相关模型下载命令即可,或重启电脑!Ollama支持多种DeepSeek模型版本,用户可以根据硬件配置选择合适的模型。配置添加系统环境变量OLLAMA_MODELS,设置其值为新的路径,点击确定应用。安装完成后,在终端输入以下命令,检查Ollama版本。:14B、32B或70B版本,适合高端GPU。

m0_67297673的博客 2385

分布式定时任务系列10:XXL-job源码分析之路由策略

** FIRST(第一个):固定选择第一个机器;*//** (最后一个):固定选择最后一个机器;*//** (轮询):;*//** (随机):随机选择在线的机器;*//** (一致性HASH):每个任务按照Hash算法固定选择某一台机器,且所有任务均匀散列在不同机器上。*//** (最不经常使用):使用频率最低的机器优先被选举;*//** (最近最久未使用):最久未使用的机器优先被选举;*//** (故障转移):按照顺序依次进行心跳检测,第一个心跳检测成功的机器选定为目标执行器并发起调度;

weigeshikebi的博客 2122

《21天学通C++》阶段性成果测试题——hstring(手写string类)

总体考察了类的使用,缓冲区考察了指针和数组的使用、增删改查、考察编码思维、考察了类的重载。1.初始化子字符串的起始位置为-1,表示未找到,若buffer或findHstring为空,则返回-1。右边的字符串不为空,则分别获取母串和子串的长度(用循环),然后创建一个固定大小的数组。3.然后循环遍历寻找子串在母串中的起始位置,循环时可以通过两个长度相减,避免循环越界。2.如都不为空,则分别获取双方的字符串长度(用循环),然后。右边的字符串为空,则输出原来的字符串。是否为空,有一方为空则返回另一方。

立志成为炼丹师? 1263

分布式定时任务系列9:XXL-job路由策略

执行器集群部署时提供丰富的路由策略,包括:第一个、最后一个、轮询、随机、一致性HASH、最不经常使用、最近最久未使用、故障转移、忙碌转移等。

weigeshikebi的博客 1439

从零到一:手把手教你用Python+LangChain搭建企业级RAG知识库(附避坑指南)

本文为开发者提供了一份详尽的实战指南,手把手教你使用Python和LangChain框架从零构建企业级RAG知识库。内容涵盖环境搭建、文档处理、向量化检索、提示工程及生产部署等核心环节,并重点分享了文档分割、检索优化等关键避坑经验,旨在帮助读者打造稳定、高效且易于维护的智能知识中枢。

weixin_29058331的博客 398

【Clickhouse从入门到精通】第51篇:ClickHouse权限管理体系详解

摘要 本文是ClickHouse权限管理系列文章的第51篇,重点解析ClickHouse RBAC权限体系中的GRANT/REVOKE机制。文章详细介绍了GRANT语句的基础语法与权限类型,包括SELECT、INSERT等常规权限以及WITH GRANT OPTION转授权功能。特别深入讲解了列级权限的实现方式,通过SELECT(col1,col2)语法实现字段粒度的访问控制,并展示了权限查询与验证方法。本文还对比了ClickHouse 20.4版本前后权限管理体系的差异,为构建安全的多租户数据平台提供了关

xyghehehehe的专栏 440

【开发经验】分布式任务调度——xxl-job

​ 之前了解了本地任务的调度方式,但是随着定时任务的越来越多,越来越复杂,单机压力压力越来越大,所以任务服务也需要做高可用。如果部署多台服务,就会存在任务谁来执行或者一个重复执行的问题。 ​ 现在可以实现分布式任务调度的框架有很多,xxl-job、quartz等等。 ​ quartz分布式任务调度是通过数据库锁实现的,实现思路简单,但是功能也简单,好多东西都需要自己二次开发。重点是需要简单的二次开发,而且没有可视化。 ​ xxl-job上手很容器,有非常友好的可视化页面,功能强大,1分钟即可上手。

叁滴水 的博客 2641

【即插即用模块】Transformer篇 | CVPR 2025 | D2T:双域特征融合,完美适配缺陷检测/小目标检测和分割等频域细节敏感的任务!

本文提出了一种基于小波和原型增强的双域Transformer(WPFormer)用于像素级表面缺陷检测。该模型通过Dual-domain Transformer (D2T)模块融合频域和空间域特征:小波增强交叉注意力(WCA)分解高低频特征并加权平衡,增强缺陷细节捕捉;原型引导注意力(PCA)生成全局原型向量强化关键信息聚合。实验表明,该模块在缺陷检测任务中性能优于现有方法,通过频域细节增强和全局依赖建模有效提升了复杂场景下的检测效果。代码已开源,适用于对频域敏感的目标检测和分割任务。

主要更新底层视觉(去噪、超分等)相关的科研内容,形式为【论文精读】+【论文复现】 1374

揪出XXL-JOB中的细节

废话少说,直接进入正题。 相信大家对XXL-JOB都很了解,故本文对源码不进行过多介绍,侧重的是看源码过程中想到的几个知识点,不一定都对,请大神们批评指正。 XXL-JOB简介 XXL-JOB是一个轻量级分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用。 XXL-JOB分为调度中心、执行器、数据中心,调度中心负责任务管理及调度、执行器管理、日志管理等,执行器负责任务执行及执行结果回调。 任务调度 - “类时间轮”的实现 时间轮

dayformyjob 1016

骑砍Ⅱ霸主MOD开发(16)-FaceKey&BannerKey

摘要:本文介绍了FaceKey人物模型和BannerKey旗帜模型的生成流程。FaceKey通过序列化参数生成人物模型,包括头部(脸部、嘴、睫毛、眼睛)、眉毛、头发、身体和躯干等组件,每个部分通过特定标签和骨骼绑定实现。BannerKey则通过解析参数生成旗帜模型,包括模型、贴图和颜色等元素。两种模型都采用参数化设计和XML配置文件实现模型生成与定制。

骑砍战团&霸主MOD开发 2284

AIGC算法工程师-面试八股文-2025年版本-算法八股文/工程八股文/开放式问题

​两年前整理了《AIGC算法工程师 面试八股文》,全网一共有100w读者看过,收到了很多小伙伴的催更。Diffusion的技术发展非常快,博主特意整理了最近两年最新的八股文,内容非常全面,涵盖算法八股文/工程八股文/开放式问题三个大类,希望能为求职的读者们带来帮助。

沉迷单车的追风少年 4179

XXL-Job实现原理

XXL-Job 是一个分布式任务调度平台,其设计旨在解决分布式环境下定时任务的调度与管理问题。

m0_71845127的博客 1513

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

XXL-JOB原理

前言XXL-JOB简介任务调度 - “类时间轮”的实现时间轮XXL-JOB中的“时间轮”一致性Hash路由中的Hash算法分片任务的实现 - 维护线程上下文看完源码后的思考前言废话少说,直接进入正题。相信大家对XXL-JOB都很了解,故本文对源码不进行过多介绍,侧重的是看源码过程中想到的几个知识点,不一定都对,请大神们批...

weixin_41037829的博客 268

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

XXL-JOB Admin端架构与调度核心源码解析

分布式任务调度系统是现代微服务架构的关键组件,通过解耦任务触发与执行实现系统弹性。其核心原理基于时间轮算法和线程池技术,XXL-JOB作为轻量级实现方案,采用三层架构设计(表现层/服务层/数据层)保障调度可靠性。在工程实践中,2.3.0版本引入的RingBuffer结构和动态线程池调优显著提升性能,支持秒级任务触发与多种负载均衡策略。典型应用场景包括定时报表生成、分布式批处理等,本文通过剖析Admin端启动流程、调度算法实现及性能优化关键点,为开发者提供深度技术参考。

weixin_30458043的博客 394

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

XXL-JOB源码深度解析:从调度触发到执行回调的全链路剖析

分布式任务调度是微服务架构中实现定时任务统一管理的核心技术,其核心原理在于解耦调度决策与任务执行,通过中心化的调度器与分布式的执行器协同工作。该技术通过预读、时间轮等算法优化调度性能,并利用注册发现、心跳机制保障集群高可用,其技术价值在于提升系统可维护性、资源利用率和任务可靠性。典型应用场景包括数据同步、报表生成、消息推送等周期性或延迟性业务处理。本文聚焦于轻量级分布式任务调度平台XXL-JOB,深入其调度触发、路由选择、任务执行与结果回调的全链路源码实现,为开发者解决任务执行异常、调度延迟等线上问题,以及

weixin_34391445的博客 441

XXL-JOB内部机制大揭秘,任务飞起来!

????这是一个或许对你有用的社群????一对一交流/面试小册/简历优化/求职解惑,欢迎加入「芋道快速开发平台」知识星球。下面是星球提供的部分资料:《项目实战(视频)》:从书中学,往事中“练”《互联网高频面试题》:面朝简历学习,春暖花开《架构 x 系统设计》:摧枯拉朽,掌控面试高频场景题《精进 Java 学习指南》:系统学习,互联网主流技术栈《必读 Java 源码专栏》:知其然,知其所以然????这是一个或许...

weixin_44421461的博客 224

XXL-JOB分布式任务调度中心:从核心原理到生产实践

在微服务架构中,定时任务和延时任务的可靠执行是常见的业务需求。其核心原理在于将任务触发与业务执行解耦,通过中心化的调度器统一管理任务生命周期,实现分布式环境下的任务协调与容错。这一技术价值在于提供了统一的任务管理、监控和运维能力,有效解决了任务分散、重复执行和故障排查困难等问题。典型的应用场景包括数据统计报表生成、延时业务处理(如订单超时关单)以及大数据量的并行处理。本文以流行的开源框架XXL-JOB为例,深入剖析其调度中心与执行器分离的架构设计,并结合分片广播、故障转移等高级特性,展示如何应对海量数据处理

weixin_34294649的博客 332

XXL-JOB核心调度流程与设计模式深度剖析(一篇掌握精髓)

本文深度剖析了XXL-JOB的核心调度流程与设计模式,详细解析了其架构设计、时间轮调度算法、双线程池模型及九种路由策略的实现。通过源码解析,揭示了XXL-JOB在分布式任务调度中的高性能设计关键点,包括数据库锁优化、任务阻塞处理机制等,帮助开发者掌握其精髓并应用于实际项目。

weixin_33739646的博客 248

XXL-Job 分布式任务调度平台:从核心原理到 Spring Boot 集成实践

分布式任务调度是构建高可用、可扩展后端系统的关键技术,其核心原理在于将任务的触发与执行解耦,通过中心化调度器统一管理任务的生命周期。这种架构解决了传统单机定时任务在分布式环境下重复执行、负载不均和状态监控困难等痛点,为微服务架构提供了可靠的后台作业支撑。XXL-Job 作为一款轻量级开源分布式任务调度平台,通过调度中心与执行器分离的设计,实现了任务配置可视化、调度策略多样化以及执行日志完整化。在技术价值层面,它显著降低了分布式定时任务的开发与运维复杂度,支持分片广播、故障转移等高级特性,适用于大数据处理、定

weixin_34293141的博客 346
上一篇: xxjob代码执行过程
下一篇: windows11安装docker desktop和K8S环境创建镜像和运行dashboard
牧竹子
博客等级 码龄17年 572粉丝 233原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值