08 xxl-job快、慢线程池

914 阅读4分钟

我们知道在调度中心轮询任务的工作是由一个专门的线程来负责的(调度线程scheduleThread),

调度线程scheduleThread发现有任务要触发时候,会通知我们的执行器去执行这个任务,通知这件事交给了另外的线程去执行的,这个线程就是由我们的快慢线程池(二选一)创建的

那么,为什么要使用线程池异步呢?

主要是因为触发任务,需要通过Http接口调用具体的执行器实例去触发任务

这一过程必然会耗费时间,如果调度线程去做,就会耽误调度的效率

所以就通过异步线程去做,调度线程只负责判断任务是否需要执行

并且,Xxl-Job为了进一步优化任务的触发,将这个触发任务执行的线程池划分成快线程池慢线程池两个线程池

下面我们将详细探索这快、慢线程池二者的区别已经应用

何时被初始化

在调度中心启动的时候,会按顺序调用以下3个方法来初始化快慢线程池

//调度中心的核心入口

XxlJobAdminConfig.afterPropertiesSet();

xxlJobScheduler.init();

// 初始化快慢线程池

JobTriggerPoolHelper.toStart();

初始化快慢线程池的代码如下:

// fast/slow thread pool  
private ThreadPoolExecutor fastTriggerPool = null;  
private ThreadPoolExecutor slowTriggerPool = null;  
  
public void start() {  
    fastTriggerPool = new ThreadPoolExecutor(  
        10,  
        XxlJobAdminConfig.getAdminConfig().getTriggerPoolFastMax(),  
        60L,  
        TimeUnit.SECONDS,  
        new LinkedBlockingQueue<Runnable>(1000),  
        new ThreadFactory() {  
            @Override  
            public Thread newThread(Runnable r) {  
                return new Thread(r, "xxl-job, admin JobTriggerPoolHelper-fastTriggerPool-" + r.hashCode());  
            }  
    });  

    slowTriggerPool = new ThreadPoolExecutor(  
        10,  
        XxlJobAdminConfig.getAdminConfig().getTriggerPoolSlowMax(),  
        60L,  
        TimeUnit.SECONDS,  
        new LinkedBlockingQueue<Runnable>(2000),  
        new ThreadFactory() {  
            @Override  
            public Thread newThread(Runnable r) {  
                return new Thread(r, "xxl-job, admin JobTriggerPoolHelper-slowTriggerPool-" + r.hashCode());  
            }  
    });  
}

什么叫快线程池、什么叫慢线程池?二者的区别是什么?

从上面的代码从可以看出,二者的区别如下:

最大线程数队列的容量
快线程池2001000
慢线程池1002000

快线程池:主要处理耗时较短的通知。

慢线程池:主要处理耗时较长的通知。

这里面提到的通知其实是调度中心发送http请求给我们的服务去执行job,至于job具体是如何执行(异步线程)的就跟我们的调度中心无关了。

所以通知的这个时间不包括任务具体执行时间

在哪里被使用到

JobTriggerPoolHelper.trigger();


private volatile long minTim = System.currentTimeMillis() / 60000; // ms > min  
private volatile ConcurrentMap<Integer, AtomicInteger> jobTimeoutCountMap = new ConcurrentHashMap<>();

// 到点了,准备通知我们的服务区执行job了
public void addTrigger(final int jobId,  
                        final TriggerTypeEnum triggerType,  
                        final int failRetryCount,  
                        final String executorShardingParam,  
                        final String executorParam,  
                        final String addressList) {  
    // choose thread pool  
    ThreadPoolExecutor triggerPool_ = fastTriggerPool;  
    AtomicInteger jobTimeoutCount = jobTimeoutCountMap.get(jobId);  
    // 该job在最近1分钟内trigger执行耗时超过500ms的次数超过10次,我们就任务该job的trigger是一个耗时任务,换成慢线程池处理  
    // 这里所说的耗时任务是指http通知我们的服务去执行job就OK了,至于job具体是如何执行的就跟我们的调度中心无关了  
    if (jobTimeoutCount != null && jobTimeoutCount.get() > 10) { // job-timeout 10 times in 1 min  
        triggerPool_ = slowTriggerPool;  
    }  
    
    // trigger  
    triggerPool_.execute(new Runnable() {  
        @Override  
        public void run() {  
            long start = System.currentTimeMillis();  
            try {  
                // 只是通过http给我们自己的服务发送执行job的请求就立刻返回了(这个过程一般是很快的,也有可能网络不好导致很慢),  
                // 至于我们的job具体是如何执行的就跟我们的调度中心无关了  
                XxlJobTrigger.trigger(jobId, triggerType, failRetryCount, executorShardingParam, executorParam, addressList);  
            } catch (Exception e) {  
                logger.error(e.getMessage(), e);  
            } finally {  
                long minTim_now = System.currentTimeMillis() / 60000; // 多少分钟  
                if (minTim != minTim_now) { // 时间过去了至少1分钟了  
                    minTim = minTim_now;  
                    // 超过1分钟了我就要把jobTimeoutCountMap清掉,  
                    // 所以jobTimeoutCountMap只保留最近1分钟的数据  
                    jobTimeoutCountMap.clear();  
                }  
            
                long cost = System.currentTimeMillis() - start;  
                if (cost > 500) { // ob-timeout threshold 500ms  
                    // jobTimeoutCountMap保留最近1分钟的数据该jobId的trigger耗时超过500ms的次数  
                    AtomicInteger timeoutCount = jobTimeoutCountMap.putIfAbsent(jobId, new AtomicInteger(1));  
                    if (timeoutCount != null) {  
                        timeoutCount.incrementAndGet();  
                    }  
                }  
            }  
        }  
    });  
}

1 计算本次通知的耗时cost,如果超过了500ms,这个任务的慢次数就会加1

2 如果这个任务在最近1分钟之内累计慢次数超过10次,就选择慢线程池触发

3 每过1分钟,任务的慢次数就清零