xxl-job调度中心与执行器通讯过程源码理解
管理端(调度器)
-
基于一个定时任务任务的执行开始分析,执行调用/trigger接口
-
xxl-job-admin中源码跟踪
- /trigger–>helper.addTrigger–>XxlJobTrigger.trigger–>processTrigger–>runExecutor
- 注意: 调用 executorBiz.run(triggerParam),其实会调用代理类XxlRpcReferenceBean.getObject()的方法, 该代理方法代理后并没有执行原来的run方法,所以不会执行executorBiz的run()方法。代理方法会使用netty发送http请求到执行器。并把ExecutorBiz类和该类的run方法作为参数传递过去。还会把执行器的任务Handler示例相关的类名和方法传递过去,在执行器中通过反射调用Java相关的方法
-
注意: 调用 executorBiz.run(triggerParam),其实会调用代理类XxlRpcReferenceBean.getObject()的方法, 该代理方法代理后并没有执行原来的run方法,所以不会执行executorBiz的run()方法。代理方法会使用netty发送http请求到执行器。并把ExecutorBiz类和该类的run方法作为参数传递过去。还会把执行器的任务Handler示例相关的类名和方法传递过去,在执行器中通过反射调用Java相关的方法 public static ReturnT<String> runExecutor(TriggerParam triggerParam, String address){ ReturnT<String> runResult = null; try { // 做了动态代理 ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address); // 调用executorBiz.run其实会调用XxlRpcReferenceBean.getObject() runResult = executorBiz.run(triggerParam); } catch (Exception e) { logger.error(">>>>>>>>>>> xxl-job trigger error, please check if the executor[{}] is running.", address, e); runResult = new ReturnT<String>(ReturnT.FAIL_CODE, ThrowableUtil.toString(e)); } StringBuffer runResultSB = new StringBuffer(I18nUtil.getString("jobconf_trigger_run") + ":"); runResultSB.append("<br>address:").append(address); runResultSB.append("<br>code:").append(runResult.getCode()); runResultSB.append("<br>msg:").append(runResult.getMsg()); runResult.setMsg(runResultSB.toString()); return runResult; }
客户端(执行器)
客户端使用流程
-
引入依赖
<dependency> <groupId>com.xuxueli</groupId> <artifactId>xxl-job-core</artifactId> <version>2.0.2</version> </dependency>
-
创建xxljob配置
package com.xxl.job.executor.core.config; import com.xxl.job.core.executor.impl.XxlJobSpringExecutor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * xxl-job config * * @author xuxueli 2017-04-28 */ @Configuration public class XxlJobConfig { private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class); @Value("${xxl.job.admin.addresses}") private String adminAddresses; @Value("${xxl.job.executor.appname}") private String appName; @Value("${xxl.job.executor.ip}") private String ip; @Value("${xxl.job.executor.port}") private int port; @Value("${xxl.job.accessToken}") private String accessToken; @Value("${xxl.job.executor.logpath}") private String logPath; @Value("${xxl.job.executor.logretentiondays}") private int logRetentionDays; @Bean(initMethod = "start", destroyMethod = "destroy") public XxlJobSpringExecutor xxlJobExecutor() { logger.info(">>>>>>>>>>> xxl-job config init."); XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor(); xxlJobSpringExecutor.setAdminAddresses(adminAddresses); xxlJobSpringExecutor.setAppName(appName); xxlJobSpringExecutor.setIp(ip); xxlJobSpringExecutor.setPort(port); xxlJobSpringExecutor.setAccessToken(accessToken); xxlJobSpringExecutor.setLogPath(logPath); xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays); return xxlJobSpringExecutor; } }
-
创建任务Handler
package com.xxl.job.executor.service.jobhandler; import com.xxl.job.core.biz.model.ReturnT; import com.xxl.job.core.handler.IJobHandler; import com.xxl.job.core.handler.annotation.JobHandler; import com.xxl.job.core.log.XxlJobLogger; import org.springframework.stereotype.Component; import java.util.concurrent.TimeUnit; /** * 任务Handler示例(Bean模式) * * 开发步骤: * 1、继承"IJobHandler":“com.xxl.job.core.handler.IJobHandler”; * 2、注册到Spring容器:添加“@Component”注解,被Spring容器扫描为Bean实例; * 3、注册到执行器工厂:添加“@JobHandler(value="自定义jobhandler名称")”注解,注解value值对应的是调度中心新建任务的JobHandler属性的值。 * 4、执行日志:需要通过 "XxlJobLogger.log" 打印执行日志; * * @author xuxueli 2015-12-19 19:43:36 */ @JobHandler(value="demoJobHandler") @Component public class DemoJobHandler extends IJobHandler { @Override public ReturnT<String> execute(String param) throws Exception { XxlJobLogger.log("XXL-JOB, Hello World."); for (int i = 0; i < 5; i++) { System.out.println(i); XxlJobLogger.log("beat at:" + i); } return SUCCESS; } }
源码分析
- XxlJobConfig的start方法会开启一个netty服务
package com.xxl.job.executor.core.config;
@Configuration
public class XxlJobConfig {
private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);
@Value("${xxl.job.admin.addresses}")
private String adminAddresses;
@Value("${xxl.job.executor.appname}")
private String appName;
@Value("${xxl.job.executor.ip}")
private String ip;
@Value("${xxl.job.executor.port}")
private int port;
@Value("${xxl.job.accessToken}")
private String accessToken;
@Value("${xxl.job.executor.logpath}")
private String logPath;
@Value("${xxl.job.executor.logretentiondays}")
private int logRetentionDays;
// start方法会开启一个netty服务监听http请求
@Bean(initMethod = "start", destroyMethod = "destroy")
public XxlJobSpringExecutor xxlJobExecutor() {
logger.info(">>>>>>>>>>> xxl-job config init.");
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
xxlJobSpringExecutor.setAppName(appName);
xxlJobSpringExecutor.setIp(ip);
xxlJobSpringExecutor.setPort(port);
xxlJobSpringExecutor.setAccessToken(accessToken);
xxlJobSpringExecutor.setLogPath(logPath);
xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
return xxlJobSpringExecutor;
}
}
- start–>super.start();–>initRpcProvider(ip, port, appName, accessToken);–>xxlRpcProviderFactory.start();–>this.server.start(this);–>NettyHttpServer.start()
- NettyHttpServerHandler会接收到调度器的请求,因为调度器是使用的executorBiz.run(triggerParam)进行代理过来的,并且把类和方法名传递过来了。所以执行下图的75行时实际会反射调用executorBiz.run()方法
- 调度器中通过反射执行executorBiz.run()方法,该方法主要做了以下两个步骤
- 创建了一个线程
- 将这个线程放到阻塞队列
- 基于上一步的队列继续追踪。有一个线程,一直监听着该阻塞队列,下图中的handler就是我们定义的DemoJobHandler(上一步创建线程的时候已经获取到了要调用DemoJobHandler)。执行DemoJobHandler的execute方法。至此,调度中心已经完成了到客户端的请求
xxl-job执行器与调度中心通讯过程源码理解
基于callback回调的理解
-
上一步中handler.execute(triggerParamTmp.getExecutorParams());执行之后,会把执行结果放到一个回调队列里面
-
基于这个队列做研究,该队列是一个后台线程在监听着,该线程会调用doCallBack()
-
继续跟踪doCallBack()方法。同样Adminbiz使用了代理模式,调用adminBiz.callback(callbackParamList),实际上是访问的XxlRpcReferenceBean.getObject()方法
-
XxlRpcReferenceBean.getObject()方法会把AdminBiz类和callback方法作为参数发送到管理端。统一请求是管理端的/api接口。
-
管理端的api接口会反射访问adminBiz.callback()方法。至此完成了执行器到调度中心的通讯