我们知道在调度中心轮询任务的工作是由一个专门的线程来负责的(调度线程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());
}
});
}
什么叫快线程池、什么叫慢线程池?二者的区别是什么?
从上面的代码从可以看出,二者的区别如下:
| 最大线程数 | 队列的容量 | |
|---|---|---|
| 快线程池 | 200 | 1000 |
| 慢线程池 | 100 | 2000 |
快线程池:主要处理耗时较短的通知。
慢线程池:主要处理耗时较长的通知。
这里面提到的通知其实是调度中心发送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分钟,任务的慢次数就清零