什么是执行器
- 执行器就是指跑任务逻辑的节点
- 官方提供了执行器的samples,位于xxl-job\xxl-job-executor-samples
简析启动过程
- 以springboot版本的执行器为例子来解析
- 首先看com.xxl.job.executor.core.config.XxlJobConfig
@Bean
public XxlJobSpringExecutor xxlJobExecutor() {
logger.info(">>>>>>>>>>> xxl-job config init.");
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
xxlJobSpringExecutor.setAppname(appname);
xxlJobSpringExecutor.setAddress(address);
xxlJobSpringExecutor.setIp(ip);
xxlJobSpringExecutor.setPort(port);
xxlJobSpringExecutor.setAccessToken(accessToken);
xxlJobSpringExecutor.setLogPath(logPath);
xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
return xxlJobSpringExecutor;
}
- XxlJobSpringExecutor
- XxlJobSpringExecutor是一个典型的spring管理的bean,需要分解一下它的继承实现结构以及它的生命周期方法
public class XxlJobSpringExecutor extends XxlJobExecutor implements ApplicationContextAware, SmartInitializingSingleton, DisposableBean
- XxlJobExecutor这是执行的关键类,我的理解XxlJobSpringExecutor只是用来适配spring的生命周期来达到XxlJobExecutor生命周期方法的调用
- 初始化方法
@Override
public void afterSingletonsInstantiated() {
initJobHandlerMethodRepository(applicationContext);
GlueFactory.refreshInstance(1);
try {
super.start();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void initJobHandlerMethodRepository(ApplicationContext applicationContext) {
}
- XxlJobExecutor
public void start() throws Exception {
XxlJobFileAppender.initLogPath(logPath);
initAdminBizList(adminAddresses, accessToken);
JobLogFileCleanThread.getInstance().start(logRetentionDays);
TriggerCallbackThread.getInstance().start();
initEmbedServer(address, ip, port, appname, accessToken);
}
public void start() {
while(!toStop){
try {
HandleCallbackParam callback = getInstance().callBackQueue.take();
if (callback != null) {
if (callbackParamList!=null && callbackParamList.size()>0) {
doCallback(callbackParamList);
}
}
} catch (Exception e) {
if (!toStop) {
logger.error(e.getMessage(), e);
}
}
}
}
private void doCallback(List<HandleCallbackParam> callbackParamList){
}
- 启动的最后一步,EmbedServer start!!!
public void start(final String address, final int port, final String appname, final String accessToken) {
executorBiz = new ExecutorBizImpl();
addLast(new IdleStateHandler(0, 0, 30 * 3, TimeUnit.SECONDS))
addLast(new HttpServerCodec())
addLast(new HttpObjectAggregator(5 * 1024 * 1024))
addLast(new EmbedHttpServerHandler(executorBiz, accessToken, bizThreadPool));
@Override
protected void channelRead0(final ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
bizThreadPool.execute(new Runnable() {
@Override
public void run() {
Object responseObj = process(httpMethod, uri, requestData, accessTokenReq);
}
});
}
private Object process(HttpMethod httpMethod, String uri, String requestData, String accessTokenReq) {
try {
if ("/beat".equals(uri)) {
return executorBiz.beat();
} else if ("/idleBeat".equals(uri)) {
IdleBeatParam idleBeatParam = GsonTool.fromJson(requestData, IdleBeatParam.class);
return executorBiz.idleBeat(idleBeatParam);
} else if ("/run".equals(uri)) {
TriggerParam triggerParam = GsonTool.fromJson(requestData, TriggerParam.class);
return executorBiz.run(triggerParam);
} else if ("/kill".equals(uri)) {
KillParam killParam = GsonTool.fromJson(requestData, KillParam.class);
return executorBiz.kill(killParam);
} else if ("/log".equals(uri)) {
LogParam logParam = GsonTool.fromJson(requestData, LogParam.class);
return executorBiz.log(logParam);
} else {
return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, uri-mapping("+ uri +") not found.");
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
return new ReturnT<String>(ReturnT.FAIL_CODE, "request error:" + ThrowableUtil.toString(e));
}
}
- com.xxl.job.core.biz.impl.ExecutorBizImpl#run
// 这段代码的分析以第一次执行来备注
@Override
public ReturnT<String> run(TriggerParam triggerParam) {
// 获取管理这个jobid的job线程
// job线程持有一个LinkedBlockingQueue,和一个id集合(集合是用来避免admin重复投递相同的任务的)
JobThread jobThread = XxlJobExecutor.loadJobThread(triggerParam.getJobId())
// 第一次获取不到job线程,所以绑定的job处理器也是空
// 这个处理器就是最开始初始化的时候,扫描xxljob注解的方法组装的类
IJobHandler jobHandler = jobThread!=null?jobThread.getHandler():null
String removeOldReason = null
GlueTypeEnum glueTypeEnum = GlueTypeEnum.match(triggerParam.getGlueType())
if (GlueTypeEnum.BEAN == glueTypeEnum) {
// 这里就根据name直接去jobHandlerRepository获取一个处理器
IJobHandler newJobHandler = XxlJobExecutor.loadJobHandler(triggerParam.getExecutorHandler())
// 校验
if (jobThread!=null && jobHandler != newJobHandler) {
// change handler, need kill old thread
removeOldReason = "change jobhandler or glue type, and terminate the old job thread."
jobThread = null
jobHandler = null
}
// 变量替换
if (jobHandler == null) {
jobHandler = newJobHandler
if (jobHandler == null) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "job handler [" + triggerParam.getExecutorHandler() + "] not found.")
}
}
}
// 省略非Bean模式的代码执行,后面讲glue的时候再回过头来看
// 第一次进来,所以job线程为null,所以这段逻辑先绕过
// 后面会回过头来讲这一段东西
if (jobThread != null) {
ExecutorBlockStrategyEnum blockStrategy = ExecutorBlockStrategyEnum.match(triggerParam.getExecutorBlockStrategy(), null)
if (ExecutorBlockStrategyEnum.DISCARD_LATER == blockStrategy) {
// discard when running
if (jobThread.isRunningOrHasQueue()) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "block strategy effect:"+ExecutorBlockStrategyEnum.DISCARD_LATER.getTitle())
}
} else if (ExecutorBlockStrategyEnum.COVER_EARLY == blockStrategy) {
// kill running jobThread
if (jobThread.isRunningOrHasQueue()) {
removeOldReason = "block strategy effect:" + ExecutorBlockStrategyEnum.COVER_EARLY.getTitle()
jobThread = null
}
} else {
// just queue trigger
}
}
// 注册一个job线程
// 注册线程的代码主要是传入jobid和处理器,然后启动(启动代码下面分析,目前猜测它不是马上执行任务的)
// 同时会把旧的关联jobid的线程拿出来停止和中断
if (jobThread == null) {
jobThread = XxlJobExecutor.registJobThread(triggerParam.getJobId(), jobHandler, removeOldReason)
}
// 把任务推到job线程的队列和集合
// jobthread启动是不断拉取队列的元素进行消费的
ReturnT<String> pushResult = jobThread.pushTriggerQueue(triggerParam)
return pushResult
}
- 接下来看JobThread启动后干了什么(com.xxl.job.core.thread.JobThread#run)
@Override
public void run() {
handler.init();
while(!toStop){
if (triggerParam.getExecutorTimeout() > 0) {
Thread futureThread = null;
try {
FutureTask<Boolean> futureTask = new FutureTask<Boolean>(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
XxlJobContext.setXxlJobContext(xxlJobContext);
handler.execute();
return true;
}
});
futureThread = new Thread(futureTask);
futureThread.start();
Boolean tempResult = futureTask.get(triggerParam.getExecutorTimeout(), TimeUnit.SECONDS);
} catch (TimeoutException e) {
} finally {
futureThread.interrupt();
}
} else {
handler.execute();
}
if (idleTimes > 30) {
if(triggerQueue.size() == 0) {
XxlJobExecutor.removeJobThread(jobId, "excutor idel times over limit.");
}
}
TriggerCallbackThread.pushCallBack
}
}
结尾
- 上面留了一些问题,后面的文章会继续解析
- callback回调后admin做了什么?
- glue代码是怎么执行的?
- 上面简单分析了一整个启动过程,总结一下
- 我们可以把xxl-job这种分布式任务调度框架简单理解为:每个执行器其实就是一个http服务器,然后依然是请求应答模式,只是现在的请求是admin发出的,然后执行器就执行任务
- 以往的单点任务调度把任务和调度2件事放在了一起,所以不方便做集群,分布式处理。现在就是调度抽离出去到admin,执行器就做任务处理,分而治之