05 xx-job 的10种路由策略(附带分片广播job实现)

562 阅读5分钟

路由策略

每一个任务都可以配置一个路由策略 image.png 除了分片广播策略,每一种路由策略对用一个java类 image.png

什么是路由?

在我们的服务集群部署的情况下,某job到点触发后,调度中心根据某种策略去通知哪些执行器跑该job

image.png

从图上可以看出xxljob支持多种路由策略

这里简单讲一讲各种算法的原理,有兴趣的小伙伴可以去看看内部的实现细节

第一个、最后一个、轮询、随机都很简单,没什么好说的

一致性Hash讲起来比较复杂,你可以先看看这篇文章,再去查看Xxl-Job的代码实现

zhuanlan.zhihu.com/p/470368641

最不经常使用(LFU:Least Frequently Used):Xxl-Job内部会有一个缓存,统计每个任务每个地址的使用次数,每次都选择使用次数最少的地址,这个缓存每隔24小时重置一次

最近最久未使用(LRU:Least Recently Used):将地址存到LinkedHashMap中,它利用LinkedHashMap可以根据元素访问(get/put)顺序来给元素排序的特性,快速找到最近最久未使用(未访问)的节点

故障转移:调度中心都会去请求每个执行器,只要能接收到响应,说明执行器正常,那么任务就会交给这个执行器去执行

忙碌转移:调度中心也会去请求每个执行器,判断执行器是不是正在执行当前需要执行的任务(任务执行时间过长,导致上一次任务还没执行完,下一次又触发了),如果在执行,说明忙碌,不能用,否则就可以用

分片广播:XxlJob给每个执行器分配一个编号,从0开始递增,然后向所有执行器触发任务,告诉每个执行器自己的编号和总共执行器的数据

我们可以通过XxlJobHelper#getShardIndex获取到编号,XxlJobHelper#getShardTotal获取到执行器的总数据量

分片广播就是将任务量分散到各个执行器,每个执行器只执行一部分任务,加快任务的处理

举个例子,比如你现在需要处理30w条数据,有3个执行器,此时使用分片广播,那么此时可将任务分成3分,每份10w条数据,执行器根据自己的编号选择对应的那份10w数据处理

当选择好了具体的执行器实例之后,调用中心就会携带一些触发的参数,发送Http请求,触发任务

路由策略相关源码见下

入口

XxlJobTrigger.trigger()

// 路由策略  
ExecutorRouteStrategyEnum routeStrategyEnum = ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null);  
if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST == routeStrategyEnum  
        && group.getRegistryList() != null && !group.getRegistryList().isEmpty()  
        && shardingParam == null) {  
    for (int i = 0; i < group.getRegistryList().size(); i++) {  
        // 分片路由:通知每一个执行器,传给每个执行器的shardIndex不同,shardTotal相同  
        processTrigger(group, jobInfo, finalFailRetryCount, triggerType, i, group.getRegistryList().size());  
    }  
} else {  
    if (shardingParam == null) {  
        shardingParam = new int[]{0, 1};  
    }  
    processTrigger(group, jobInfo, finalFailRetryCount, triggerType, shardingParam[0], shardingParam[1]);  
}
String address = null;  
ReturnT<String> routeAddressResult = null;  
if (group.getRegistryList() != null && !group.getRegistryList().isEmpty()) { // 至少有一个执行器可供选择  
    if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST == executorRouteStrategyEnum) {  
        // 分片广播  
        if (index < group.getRegistryList().size()) {  
            address = group.getRegistryList().get(index);  
        } else {  
            address = group.getRegistryList().get(0);  
        }  
    } else {  
        ////////////////根据对应的路由策略去选择对应的执行器//////////////////////////////////
        routeAddressResult = executorRouteStrategyEnum.getRouter().route(triggerParam, group.getRegistryList());  
        if (routeAddressResult.getCode() == ReturnT.SUCCESS_CODE) {  
            address = routeAddressResult.getContent();  
        }  
    }  
} else {  
    routeAddressResult = new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("jobconf_trigger_address_empty"));  
}

分片广播job实现

package com.shuncom.rulr.config;  
  
import com.xxl.job.core.context.XxlJobContext;  
import com.xxl.job.core.handler.annotation.XxlJob;  
import lombok.extern.slf4j.Slf4j;  
import org.springframework.stereotype.Component;  
  
import java.util.concurrent.TimeoutException;  
  
@Component  
@Slf4j  
public class TestJob2 {  
    // value对应调度中心任务里的JobHandler  
    @XxlJob(value = "jobHandlerName2", init = "init", destroy = "destroy")  
    public void x() {  
        log.info("xxl-job2 is running");  

        // 本次job执行的一个上下文,具体原理见JobThread.run()即可,很简单  
        XxlJobContext xxlJobContext = XxlJobContext.getXxlJobContext();  

        // xxl-job也提供了一个工具类XxlJobHelper去获取XxlJobContext中的数据,原理不变  

        // 这是我们在调度中心创建任务时填的任务参数,调度中心在执行job的时候会传给我们  
        String jobParam = xxlJobContext.getJobParam();  

        // 调度中心都把jobId传给我们了,我们可以通过db去查询这个job的任何信息了  
        long jobId = xxlJobContext.getJobId();  

        // 以下两个参数用于本次job的执行完成后的情况(成功、失败、超时),需要我们手动设置,用于调度中心xxl-job-admin记录日志  
        // int handleCode = xxlJobContext.getHandleCode();  
        // String handleMsg = xxlJobContext.getHandleMsg();  

        // 当我们这个job所属任务的路由策略是分片广播的时候,以下两个参数才有意义,其他路由策略下这两个参数直接忽略  
        // 举例说明:当任务在路由策略下,如何处理我们db中的1000个超时的订单  
        // 当我们的服务集群部署的情况下,假设有3个节点,我们希望这3个节点都可以同时处理我们数据库中的1000个超时订单  
        // 节点1:shardIndex=0,shardTotal=3 处理第0-334个订单  
        // 节点2:shardIndex=1,shardTotal=3 处理第334-668个订单  
        // 节点3:shardIndex=2,shardTotal=3 处理第668-1000个订单  
        int shardIndex = xxlJobContext.getShardIndex(); // 当前分片  
        int shardTotal = xxlJobContext.getShardTotal(); // 总的分片  

        if (shardTotal > 1) { // 路由策略是分片广播的时候才有可能大于1  
            // 查询数据库中所有未处理的订单数,假设有1000个  
            int unProcessedOrderCount = selectUnProcessedOrderCount();  
            // 计算每个执行器处理的订单数  
            int eachCount = (int) (Math.ceil(((double) unProcessedOrderCount) / ((double) shardTotal)))); // 334  
            int fromIndex = eachCount * shardIndex;  
            int pageSize = fromIndex + eachCount;  
            // 当前执行器根据fromIndex和pageSize去查询自己要处理的订单,这样集群环境下就各个执行器处理的订单不会冲突了  
            run(jobParam, fromIndex, pageSize);  
        } else {  
            run(jobParam, null, null);  
        }  
    }  

    private void run(String jobParam, Integer fromIndex, Integer pageSize) {  
        XxlJobContext xxlJobContext = XxlJobContext.getXxlJobContext();  
        try {  
            // 执行我们的业务代码  
            // todo  

            // 200 : success  
            //500 : fail  
            //502 : timeout  
            // 代表本次业务执行成功  
            xxlJobContext.setHandleCode(XxlJobContext.HANDLE_CODE_SUCCESS);  
            xxlJobContext.setHandleMsg(null);  
        } catch (Exception e) {  
            log.error("Failed to runInternal", e);  
            if (e instanceof TimeoutException) {  
                xxlJobContext.setHandleCode(XxlJobContext.HANDLE_CODE_TIMEOUT);  
            } else {  
                xxlJobContext.setHandleCode(XxlJobContext.HANDLE_CODE_FAIL);  
            }  
            xxlJobContext.setHandleMsg(e.getMessage());  
        }  
    }  

    private int selectUnProcessedOrderCount() {  
        // 数据库中未处理订单的数量
        return 1000;  
    }  

    public void init() {  
        log.info("init2");  
    }  

    public void destroy() {  
        log.info("destroy2");  
    }  
  
}

部分内容转载自:# 新来个架构师,把xxl-job原理讲的炉火纯青~~