xxljob源码最有用的地方就是能够亲身感受NIO网络连接的具体实现EmbedServer。
NIO(Non-blocking I/O) 是 Java 提供的非阻塞 I/O 模型,核心特点是:
(1)非阻塞:线程不会因为等待 I/O 而阻塞
(2)事件驱动:基于事件通知机制
(3)单线程多连接:一个线程可以处理多个连接
BIO(Blocking I/O,阻塞I/O)是一种同步阻塞的网络通信模式。在这种模式下,当线程执行I/O操作时,必须等待操作完成才能继续执行后续代码。核心特点是:
(1)读取数据时:如果没有数据可读,线程会一直阻塞,直到有数据到达
(2)写入数据时:如果缓冲区已满,线程会阻塞直到可以写入数据
(3)接收连接时:如果没有新的连接请求,线程会阻塞等待
java
**传统 BIO(阻塞 I/O)**:
// 伪代码:BIO 模式
while (true) {
Socket socket = serverSocket.accept(); // 阻塞等待连接
new Thread(() -> {
socket.read(); // 阻塞读取数据
process(); // 处理业务
socket.write(); // 阻塞写入数据
}).start();
}
- 问题:每个连接需要一个线程,线程资源消耗大
- 问题:线程阻塞在 I/O 操作上,CPU 利用率低
**NIO(非阻塞 I/O)**:
// 伪代码:NIO 模式
while (true) {
selector.select(); // 非阻塞,检查就绪事件
for (SelectionKey key : selectedKeys) {
if (key.isReadable()) {
channel.read(); // 非阻塞读取
process(); // 处理业务
}
}
}
- 优势:一个线程可以处理多个连接
- 优势:线程不会阻塞,CPU 利用率高
- 优势:支持高并发连接
其次就是调度中心和执行器的关系,调度中心是定时器是Admin中手动触发任务的地方,执行器是我们希望定时到了的时候能执行的动作。二者之间的关系是通过发送EmbedServer走http协议完成的。
java
调度中心(xxl-job admin) 执行器(你的程序)
│ │
│ 1. 触发任务 │
│ JobTriggerPoolHelper.trigger() │
│ │ │
│ │ 2. HTTP POST /run │
│ ├──────────────────────>│
│ │ TriggerParam │
│ │ │ 3. 接收请求
│ │ │ EmbedServer.process()
│ │ │
│ │ 4. 调用业务方法 │
│ │ │ ExecutorBizImpl.run()
│ │ │
│ │ 5. 加入队列 │
│ │ │ jobThread.pushTriggerQueue()
│ │ │
│ │ 6. HTTP 200 OK │
│ │<──────────────────────┤
│ │ ReturnT │
│ │ │
│ │ │ 7. 执行任务
│ │ │ JobThread.run()
│ │ │ handler.execute()
│ │ │
│ │ │ 8. 推送回调
│ │ │ TriggerCallbackThread.pushCallBack()
│ │ │
│ │ 9. HTTP POST /api/callback│
│ │<──────────────────────┤
│ │ HandleCallbackParam │
│ │ │
│ 10. 处理回调 │
│ JobCompleteHelper.callback() │
│ │ │
│ │ 11. HTTP 200 OK │
│ ├──────────────────────>│
│ │ ReturnT │
│ │ │
接下来,就是根据下述代码流程,跟着源代码单步追踪,即可执行xxl-job-admin(调度中心)和xxl-job-executor-samples(执行器)。
一、调度中心的启动阶段
java
xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/scheduler/XxlJobScheduler.java
public void init() throws Exception {
// init i18n
initI18n();
// admin trigger pool start
JobTriggerPoolHelper.toStart();
// admin registry monitor run
JobRegistryHelper.getInstance().start();
// admin fail-monitor run
JobFailMonitorHelper.getInstance().start();
// admin lose-monitor run ( depend on JobTriggerPoolHelper )
JobCompleteHelper.getInstance().start();
// admin log report start
JobLogReportHelper.getInstance().start();
// start-schedule ( depend on JobTriggerPoolHelper )
JobScheduleHelper.getInstance().start();
logger.info(">>>>>>>>> init xxl-job admin success.");
}
二、执行器的启动阶段
2.1 配置application.properties
java
### xxl-job admin address list
xxl.job.admin.addresses=http://127.0.0.1:9091/xxl-job-admin
### xxl-job, access token
xxl.job.admin.accessToken=default_token
### xxl-job timeout by second
xxl.job.admin.timeout=3
### xxl-job executor appname
xxl.job.executor.appname=xxl-job-executor-sample
### xxl-job executor registry-address
xxl.job.executor.address=
### xxl-job executor server-info
xxl.job.executor.ip=
xxl.job.executor.port=9999
### xxl-job executor log-path
xxl.job.executor.logpath=D:/logs/xxl-job/jobhandler
### xxl-job executor log-retention-days
xxl.job.executor.logretentiondays=30
2.2 JobHandler注册
2.2.1 扫描 Bean
java
private void initJobHandlerMethodRepository(ApplicationContext applicationContext) {
if (applicationContext == null) {
return;
}
// init job handler from method
// 获取所有 Bean 定义,单例,allowEagerInit=false,避免提前初始化
String[] beanDefinitionNames = applicationContext.getBeanNamesForType(Object.class, false, false); // allowEagerInit=false, avoid early initialization
for (String beanDefinitionName : beanDefinitionNames) {
// filter system bean
// 跳过 Spring 框架自身的 Bean,只处理业务 Bean
if (isSystemBean(beanDefinitionName)) {
continue;
}
// get bean
// 跳过标记为 @Lazy 的 Bean,避免触发延迟初始化,非 @Lazy 的 Bean 才获取实例
Object bean = null;
Lazy onBean = applicationContext.findAnnotationOnBean(beanDefinitionName, Lazy.class);
if (onBean!=null){
logger.debug("xxl-job annotation scan, skip @Lazy Bean:{}", beanDefinitionName);
continue;
}else {
bean = applicationContext.getBean(beanDefinitionName);
}
// filter method
// 使用 MethodIntrospector.selectMethods 扫描类的方法
// 通过 MetadataLookup 对每个方法检查 @XxlJob 注解
// AnnotatedElementUtils.findMergedAnnotation 支持注解继承与合并
// 返回 Map<Method, XxlJob>,键为方法,值为注解对象
Map<Method, XxlJob> annotatedMethods = null; // referred to :org.springframework.context.event.EventListenerMethodProcessor.processBean
try {
// 查找 @XxlJob 方法
annotatedMethods = MethodIntrospector.selectMethods(bean.getClass(),
new MethodIntrospector.MetadataLookup<XxlJob>() {
@Override
public XxlJob inspect(Method method) {
return AnnotatedElementUtils.findMergedAnnotation(method, XxlJob.class);
}
});
} catch (Throwable ex) {
logger.error("xxl-job method-jobhandler resolve error for bean[" + beanDefinitionName + "].", ex);
}
if (annotatedMethods==null || annotatedMethods.isEmpty()) {
continue;
}
// generate and regist method job handler
// 遍历扫描结果,调用 registJobHandler 注册
for (Map.Entry<Method, XxlJob> methodXxlJobEntry : annotatedMethods.entrySet()) {
Method executeMethod = methodXxlJobEntry.getKey();
XxlJob xxlJob = methodXxlJobEntry.getValue();
// regist
// 注册处理器
registJobHandler(xxlJob, bean, executeMethod);
}
}
}
2.2.2 GlueFactory刷新
java
public class SpringGlueFactory extends GlueFactory {
private static Logger logger = LoggerFactory.getLogger(SpringGlueFactory.class);
/**
* inject action of spring
* @param instance
*/
@Override
public void injectService(Object instance){
if (instance==null) {
return;
}
if (XxlJobSpringExecutor.getApplicationContext() == null) {
return;
}
Field[] fields = instance.getClass().getDeclaredFields();
for (Field field : fields) {
if (Modifier.isStatic(field.getModifiers())) {
continue;
}
Object fieldBean = null;
// with bean-id, bean could be found by both @Resource and @Autowired, or bean could only be found by @Autowired
if (AnnotationUtils.getAnnotation(field, Resource.class) != null) {
try {
Resource resource = AnnotationUtils.getAnnotation(field, Resource.class);
if (resource.name()!=null && resource.name().length()>0){
fieldBean = XxlJobSpringExecutor.getApplicationContext().getBean(resource.name());
} else {
fieldBean = XxlJobSpringExecutor.getApplicationContext().getBean(field.getName());
}
} catch (Exception e) {
}
if (fieldBean==null ) {
fieldBean = XxlJobSpringExecutor.getApplicationContext().getBean(field.getType());
}
} else if (AnnotationUtils.getAnnotation(field, Autowired.class) != null) {
Qualifier qualifier = AnnotationUtils.getAnnotation(field, Qualifier.class);
if (qualifier!=null && qualifier.value()!=null && qualifier.value().length()>0) {
fieldBean = XxlJobSpringExecutor.getApplicationContext().getBean(qualifier.value());
} else {
fieldBean = XxlJobSpringExecutor.getApplicationContext().getBean(field.getType());
}
}
if (fieldBean!=null) {
field.setAccessible(true);
try {
field.set(instance, fieldBean);
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
}
}
}
2.2.3创建客户端列表
java
private void initAdminBizList(String adminAddresses, String accessToken, int timeout) throws Exception {
if (adminAddresses!=null && adminAddresses.trim().length()>0) {
for (String address: adminAddresses.trim().split(",")) {
if (address!=null && address.trim().length()>0) {
AdminBiz adminBiz = new AdminBizClient(address.trim(), accessToken, timeout);
if (adminBizList == null) {
adminBizList = new ArrayList<AdminBiz>();
}
adminBizList.add(adminBiz);
}
}
}
}
2.2.4 回调线程启动
java
public void start() {
// valid
if (XxlJobExecutor.getAdminBizList() == null) {
logger.warn(">>>>>>>>>>> xxl-job, executor callback config fail, adminAddresses is null.");
return;
}
// callback
triggerCallbackThread = new Thread(new Runnable() {
@Override
public void run() {
// normal callback
while(!toStop){
try {
HandleCallbackParam callback = getInstance().callBackQueue.take();
if (callback != null) {
// callback list param
List<HandleCallbackParam> callbackParamList = new ArrayList<HandleCallbackParam>();
int drainToNum = getInstance().callBackQueue.drainTo(callbackParamList);
callbackParamList.add(callback);
// callback, will retry if error
if (callbackParamList!=null && callbackParamList.size()>0) {
doCallback(callbackParamList);
}
}
} catch (Throwable e) {
if (!toStop) {
logger.error(e.getMessage(), e);
}
}
}
// last callback
try {
List<HandleCallbackParam> callbackParamList = new ArrayList<HandleCallbackParam>();
int drainToNum = getInstance().callBackQueue.drainTo(callbackParamList);
if (callbackParamList!=null && callbackParamList.size()>0) {
doCallback(callbackParamList);
}
} catch (Throwable e) {
if (!toStop) {
logger.error(e.getMessage(), e);
}
}
logger.info(">>>>>>>>>>> xxl-job, executor callback thread destroy.");
}
});
triggerCallbackThread.setDaemon(true);
triggerCallbackThread.setName("xxl-job, executor TriggerCallbackThread");
triggerCallbackThread.start();
// retry
triggerRetryCallbackThread = new Thread(new Runnable() {
@Override
public void run() {
while(!toStop){
try {
retryFailCallbackFile();
} catch (Throwable e) {
if (!toStop) {
logger.error(e.getMessage(), e);
}
}
try {
TimeUnit.SECONDS.sleep(RegistryConfig.BEAT_TIMEOUT);
} catch (Throwable e) {
if (!toStop) {
logger.error(e.getMessage(), e);
}
}
}
logger.info(">>>>>>>>>>> xxl-job, executor retry callback thread destroy.");
}
});
triggerRetryCallbackThread.setDaemon(true);
triggerRetryCallbackThread.start();
}
2.2.5 内嵌服务器启动
java
// XxlJobExecutor
private void initEmbedServer(String address, String ip, int port, String appname, String accessToken) throws Exception {
// fill ip port
port = port>0?port: NetUtil.findAvailablePort(9999);
ip = (ip!=null&&ip.trim().length()>0)?ip: IpUtil.getIp();
// generate address
if (address==null || address.trim().length()==0) {
String ip_port_address = IpUtil.getIpPort(ip, port); // registry-address:default use address to registry , otherwise use ip:port if address is null
address = "http://{ip_port}/".replace("{ip_port}", ip_port_address);
}
// accessToken
if (accessToken==null || accessToken.trim().length()==0) {
logger.warn(">>>>>>>>>>> xxl-job accessToken is empty. To ensure system security, please set the accessToken.");
}
// start
embedServer = new EmbedServer();
embedServer.start(address, port, appname, accessToken);
}
// EmbedServer
public void start(final String address, final int port, final String appname, final String accessToken) {
executorBiz = new ExecutorBizImpl();
thread = new Thread(new Runnable() {
@Override
public void run() {
// param
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ThreadPoolExecutor bizThreadPool = new ThreadPoolExecutor(
0,
200,
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(2000),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "xxl-job, EmbedServer bizThreadPool-" + r.hashCode());
}
},
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
throw new RuntimeException("xxl-job, EmbedServer bizThreadPool is EXHAUSTED!");
}
});
try {
// start server
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
.addLast(new IdleStateHandler(0, 0, 30 * 3, TimeUnit.SECONDS)) // beat 3N, close if idle
.addLast(new HttpServerCodec())
.addLast(new HttpObjectAggregator(5 * 1024 * 1024)) // merge request & reponse to FULL
.addLast(new EmbedHttpServerHandler(executorBiz, accessToken, bizThreadPool));
}
})
.childOption(ChannelOption.SO_KEEPALIVE, true);
// bind
ChannelFuture future = bootstrap.bind(port).sync();
logger.info(">>>>>>>>>>> xxl-job remoting server start success, nettype = {}, port = {}", EmbedServer.class, port);
// start registry
startRegistry(appname, address);
// wait util stop
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
logger.info(">>>>>>>>>>> xxl-job remoting server stop.");
} catch (Throwable e) {
logger.error(">>>>>>>>>>> xxl-job remoting server error.", e);
} finally {
// stop
try {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
} catch (Throwable e) {
logger.error(e.getMessage(), e);
}
}
}
});
thread.setDaemon(true); // daemon, service jvm, user thread leave >>> daemon leave >>> jvm leave
thread.start();
}
三、执行阶段:阶段1: 调度中心触发阶段
JobScheduleHelper / JobTriggerPoolHelper
java
**位置**:`XxlJobTrigger.runExecutor()`
**调用链**:
```207:211:xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/trigger/XxlJobTrigger.java
public static ReturnT<String> runExecutor(TriggerParam triggerParam, String address){
ReturnT<String> runResult = null;
try {
ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
runResult = executorBiz.run(triggerParam);
```
**关键操作**:
1. **获取执行器客户端**
- 类:`XxlJobScheduler`
- 方法:`getExecutorBiz(address)`
- 作用:从缓存获取或创建 `ExecutorBizClient` 实例
- 缓存键:执行器地址(如 `http://192.168.1.100:9999/`)
2. **调用执行器接口**
- 接口:`ExecutorBiz.run(TriggerParam)`
- 实现类:`ExecutorBizClient`
- 参数:`TriggerParam`(包含任务ID、Handler、参数等)
**TriggerParam 参数内容**:
```java
TriggerParam {
jobId: 1, // 任务ID
executorHandler: "demoJobHandler", // 执行器Handler名称
executorParams: "param1=value1", // 执行器参数
executorBlockStrategy: "SERIAL_EXECUTION", // 阻塞策略
executorTimeout: 0, // 超时时间
logId: 12345, // 日志ID
logDateTime: 1699123456789, // 日志时间
glueType: "BEAN", // 任务类型
glueSource: null, // GLUE源码(GLUE类型时使用)
glueUpdatetime: 0, // GLUE更新时间
broadcastIndex: 0, // 广播分片索引
broadcastTotal: 1 // 广播分片总数
}
```
四、执行阶段:阶段2: HTTP请求发送阶段 (调用执行器)
ExecutorBizClient → HTTP POST
java
**位置**:`ExecutorBizClient.run()`
**关键代码**:
```45:48:xxl-job-core/src/main/java/com/xxl/job/core/biz/client/ExecutorBizClient.java
@Override
public ReturnT<String> run(TriggerParam triggerParam) {
return XxlJobRemotingUtil.postBody(addressUrl + "run", accessToken, timeout, triggerParam, String.class);
}
```
**HTTP请求详情**:
```
POST http://192.168.1.100:9999/run HTTP/1.1
Host: 192.168.1.100:9999
Connection: Keep-Alive
Content-Type: application/json;charset=UTF-8
XXL-JOB-ACCESS-TOKEN: your-access-token
Content-Length: 256
{
"jobId": 1,
"executorHandler": "demoJobHandler",
"executorParams": "param1=value1",
"executorBlockStrategy": "SERIAL_EXECUTION",
"executorTimeout": 0,
"logId": 12345,
"logDateTime": 1699123456789,
"glueType": "BEAN",
"glueSource": null,
"glueUpdatetime": 0,
"broadcastIndex": 0,
"broadcastTotal": 1
}
```
**关键点**:
- **协议**:HTTP POST
- **URL**:`http://执行器地址/run`
- **认证**:通过 `XXL-JOB-ACCESS-TOKEN` 请求头传递Token
- **数据格式**:JSON(`TriggerParam` 对象序列化)
- **超时时间**:默认3秒(可配置)
五、执行阶段:阶段3: 执行器接收处理阶段
EmbedServer → ExecutorBizImpl
java
**位置**:`EmbedServer.process()` → `ExecutorBizImpl.run()`
**处理流程**:
1. **HTTP服务器接收请求**
- 类:`EmbedServer.EmbedHttpServerHandler`
- 操作:Netty HTTP服务器接收HTTP请求
- 路由:根据URI(`/run`)路由到对应的处理方法
2. **解析请求参数**
- 操作:将HTTP请求体(JSON)反序列化为 `TriggerParam` 对象
- 工具:`GsonTool.fromJson(requestData, TriggerParam.class)`
3. **调用业务处理方法**
- 类:`ExecutorBizImpl`
- 方法:`run(TriggerParam triggerParam)`
- 作用:
- 加载或创建 `JobThread`
- 加载任务处理器(`IJobHandler`)
- 将任务推送到 `JobThread` 的队列中
**ExecutorBizImpl.run() 核心逻辑**:
```47:75:xxl-job-core/src/main/java/com/xxl/job/core/biz/impl/ExecutorBizImpl.java
@Override
public ReturnT<String> run(TriggerParam triggerParam) {
// load old:jobHandler + jobThread
JobThread jobThread = XxlJobExecutor.loadJobThread(triggerParam.getJobId());
IJobHandler jobHandler = jobThread!=null?jobThread.getHandler():null;
String removeOldReason = null;
// valid:jobHandler + jobThread
GlueTypeEnum glueTypeEnum = GlueTypeEnum.match(triggerParam.getGlueType());
if (GlueTypeEnum.BEAN == glueTypeEnum) {
// new jobhandler
IJobHandler newJobHandler = XxlJobExecutor.loadJobHandler(triggerParam.getExecutorHandler());
// valid old jobThread
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;
}
// valid handler
if (jobHandler == null) {
jobHandler = newJobHandler;
if (jobHandler == null) {
return ReturnT.ofFail( "job handler [" + triggerParam.getExecutorHandler() + "] not found.");
}
}
```
**关键步骤**:
1. **加载JobThread**
- 方法:`XxlJobExecutor.loadJobThread(jobId)`
- 作用:从执行器的 `jobThreadRepository` 中获取对应的任务线程
2. **加载JobHandler**
- 方法:`XxlJobExecutor.loadJobHandler(executorHandler)`
- 作用:从执行器的 `jobHandlerRepository` 中获取任务处理器
3. **推送任务到队列**
- 方法:`jobThread.pushTriggerQueue(triggerParam)`
- 作用:将 `TriggerParam` 添加到 `JobThread` 的 `triggerQueue` 队列中
4. **返回响应**
- 返回:`ReturnT<String>`(包含状态码和消息)
- HTTP响应:JSON格式返回给调度中心
**HTTP响应内容**:
```
HTTP/1.1 200 OK
Content-Type: text/html;charset=UTF-8
Content-Length: 45
Connection: Keep-Alive
{
"code": 200,
"msg": null,
"content": null
}
```
**关键点**:
- **异步执行**:`run()` 方法只负责将任务加入队列,立即返回,不等待任务执行完成
- **队列机制**:使用 `LinkedBlockingQueue` 作为任务队列,支持阻塞操作
- **线程模型**:每个 `jobId` 对应一个 `JobThread`,线程持续运行,从队列中取任务执行
##### 步骤1:Netty接收HTTP请求
**类**:`EmbedServer.EmbedHttpServerHandler`
**方法**:`channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg)`
**触发方式**:Netty框架自动调用(当HTTP请求到达时)
```148:172:xxl-job-core/src/main/java/com/xxl/job/core/server/EmbedServer.java
@Override
protected void channelRead0(final ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
// request parse
//final byte[] requestBytes = ByteBufUtil.getBytes(msg.content()); // byteBuf.toString(io.netty.util.CharsetUtil.UTF_8);
String requestData = msg.content().toString(CharsetUtil.UTF_8);
String uri = msg.uri();
HttpMethod httpMethod = msg.method();
boolean keepAlive = HttpUtil.isKeepAlive(msg);
String accessTokenReq = msg.headers().get(XxlJobRemotingUtil.XXL_JOB_ACCESS_TOKEN);
// invoke
bizThreadPool.execute(new Runnable() {
@Override
public void run() {
// do invoke
Object responseObj = process(httpMethod, uri, requestData, accessTokenReq);
// to json
String responseJson = GsonTool.toJson(responseObj);
// write response
writeResponse(ctx, keepAlive, responseJson);
}
});
}
```
**关键方法调用**:
1. **解析请求内容**
- 类:`FullHttpRequest`
- 方法:`content().toString(CharsetUtil.UTF_8)`
- 作用:将HTTP请求体转换为字符串(JSON格式)
2. **解析URI**
- 类:`FullHttpRequest`
- 方法:`uri()`
- 作用:获取请求URI(如 `/run`)
3. **解析HTTP方法**
- 类:`FullHttpRequest`
- 方法:`method()`
- 作用:获取HTTP方法(POST)
4. **解析Token**
- 类:`FullHttpRequest`
- 方法:`headers().get(XxlJobRemotingUtil.XXL_JOB_ACCESS_TOKEN)`
- 作用:获取访问令牌
5. **提交到业务线程池**
- 类:`ThreadPoolExecutor`(`bizThreadPool`)
- 方法:`execute(Runnable)`
- 作用:异步处理业务逻辑,不阻塞I/O线程
##### 步骤2:路由处理和参数解析
**类**:`EmbedServer.EmbedHttpServerHandler`
**方法**:`process(HttpMethod httpMethod, String uri, String requestData, String accessTokenReq)`
```174:212:xxl-job-core/src/main/java/com/xxl/job/core/server/EmbedServer.java
private Object process(HttpMethod httpMethod, String uri, String requestData, String accessTokenReq) {
// valid
if (HttpMethod.POST != httpMethod) {
return ReturnT.ofFail("invalid request, HttpMethod not support.");
}
if (uri == null || uri.trim().length() == 0) {
return ReturnT.ofFail( "invalid request, uri-mapping empty.");
}
if (accessToken != null
&& accessToken.trim().length() > 0
&& !accessToken.equals(accessTokenReq)) {
return ReturnT.ofFail("The access token is wrong.");
}
// services mapping
try {
switch (uri) {
case "/beat":
return executorBiz.beat();
case "/idleBeat":
IdleBeatParam idleBeatParam = GsonTool.fromJson(requestData, IdleBeatParam.class);
return executorBiz.idleBeat(idleBeatParam);
case "/run":
TriggerParam triggerParam = GsonTool.fromJson(requestData, TriggerParam.class);
return executorBiz.run(triggerParam);
case "/kill":
KillParam killParam = GsonTool.fromJson(requestData, KillParam.class);
return executorBiz.kill(killParam);
case "/log":
LogParam logParam = GsonTool.fromJson(requestData, LogParam.class);
return executorBiz.log(logParam);
default:
return ReturnT.ofFail( "invalid request, uri-mapping(" + uri + ") not found.");
}
} catch (Throwable e) {
logger.error(e.getMessage(), e);
return ReturnT.ofFail("request error:" + ThrowableUtil.toString(e));
}
}
```
**关键方法调用**:
1. **验证HTTP方法**
- 操作:检查是否为POST方法
- 作用:只支持POST请求
2. **验证URI**
- 操作:检查URI是否为空
- 作用:确保URI有效
3. **验证AccessToken**
- 操作:比较请求Token和配置Token
- 作用:安全认证
4. **反序列化请求参数**
- 类:`GsonTool`
- 方法:`fromJson(requestData, TriggerParam.class)`
- 作用:将JSON字符串反序列化为 `TriggerParam` 对象
- 参数:`requestData`(JSON字符串)、`TriggerParam.class`(目标类型)
5. **调用业务方法**
- 类:`ExecutorBizImpl`
- 方法:`run(triggerParam)`
- 作用:处理任务触发请求
##### 步骤3:业务处理 - ExecutorBizImpl.run()
**类**:`ExecutorBizImpl`
**方法**:`run(TriggerParam triggerParam)`
```47:149:xxl-job-core/src/main/java/com/xxl/job/core/biz/impl/ExecutorBizImpl.java
@Override
public ReturnT<String> run(TriggerParam triggerParam) {
// load old:jobHandler + jobThread
JobThread jobThread = XxlJobExecutor.loadJobThread(triggerParam.getJobId());
IJobHandler jobHandler = jobThread!=null?jobThread.getHandler():null;
String removeOldReason = null;
// valid:jobHandler + jobThread
GlueTypeEnum glueTypeEnum = GlueTypeEnum.match(triggerParam.getGlueType());
if (GlueTypeEnum.BEAN == glueTypeEnum) {
// new jobhandler
IJobHandler newJobHandler = XxlJobExecutor.loadJobHandler(triggerParam.getExecutorHandler());
// valid old jobThread
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;
}
// valid handler
if (jobHandler == null) {
jobHandler = newJobHandler;
if (jobHandler == null) {
return ReturnT.ofFail( "job handler [" + triggerParam.getExecutorHandler() + "] not found.");
}
}
} else if (GlueTypeEnum.GLUE_GROOVY == glueTypeEnum) {
// valid old jobThread
if (jobThread != null &&
!(jobThread.getHandler() instanceof GlueJobHandler
&& ((GlueJobHandler) jobThread.getHandler()).getGlueUpdatetime()==triggerParam.getGlueUpdatetime() )) {
// change handler or gluesource updated, need kill old thread
removeOldReason = "change job source or glue type, and terminate the old job thread.";
jobThread = null;
jobHandler = null;
}
// valid handler
if (jobHandler == null) {
try {
IJobHandler originJobHandler = GlueFactory.getInstance().loadNewInstance(triggerParam.getGlueSource());
jobHandler = new GlueJobHandler(originJobHandler, triggerParam.getGlueUpdatetime());
} catch (Exception e) {
logger.error(e.getMessage(), e);
return ReturnT.ofFail( e.getMessage());
}
}
} else if (glueTypeEnum!=null && glueTypeEnum.isScript()) {
// valid old jobThread
if (jobThread != null &&
!(jobThread.getHandler() instanceof ScriptJobHandler
&& ((ScriptJobHandler) jobThread.getHandler()).getGlueUpdatetime()==triggerParam.getGlueUpdatetime() )) {
// change script or gluesource updated, need kill old thread
removeOldReason = "change job source or glue type, and terminate the old job thread.";
jobThread = null;
jobHandler = null;
}
// valid handler
if (jobHandler == null) {
jobHandler = new ScriptJobHandler(triggerParam.getJobId(), triggerParam.getGlueUpdatetime(), triggerParam.getGlueSource(), GlueTypeEnum.match(triggerParam.getGlueType()));
}
} else {
return ReturnT.ofFail("glueType[" + triggerParam.getGlueType() + "] is not valid.");
}
// executor block strategy
if (jobThread != null) {
ExecutorBlockStrategyEnum blockStrategy = ExecutorBlockStrategyEnum.match(triggerParam.getExecutorBlockStrategy(), null);
if (ExecutorBlockStrategyEnum.DISCARD_LATER == blockStrategy) {
// discard when running
if (jobThread.isRunningOrHasQueue()) {
return ReturnT.ofFail("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
}
}
// replace thread (new or exists invalid)
if (jobThread == null) {
jobThread = XxlJobExecutor.registJobThread(triggerParam.getJobId(), jobHandler, removeOldReason);
}
// push data to queue
ReturnT<String> pushResult = jobThread.pushTriggerQueue(triggerParam);
return pushResult;
}
```
**关键方法调用**:
1. **加载JobThread**
- 类:`XxlJobExecutor`
- 方法:`loadJobThread(triggerParam.getJobId())`
- 作用:从 `jobThreadRepository` 中获取已存在的任务线程
2. **匹配任务类型**
- 类:`GlueTypeEnum`
- 方法:`match(triggerParam.getGlueType())`
- 作用:根据 `glueType` 匹配任务类型(BEAN、GLUE_GROOVY、脚本等)
3. **加载JobHandler**(BEAN模式)
- 类:`XxlJobExecutor`
- 方法:`loadJobHandler(triggerParam.getExecutorHandler())`
- 作用:从 `jobHandlerRepository` 中获取任务处理器
- 返回值:`IJobHandler`(如 `MethodJobHandler`)
4. **处理阻塞策略**
- 类:`ExecutorBlockStrategyEnum`
- 方法:`match(triggerParam.getExecutorBlockStrategy(), null)`
- 作用:匹配阻塞策略(SERIAL_EXECUTION、DISCARD_LATER、COVER_EARLY)
5. **检查任务状态**(阻塞策略处理)
- 类:`JobThread`
- 方法:`isRunningOrHasQueue()`
- 作用:检查任务是否正在运行或队列中有任务
6. **注册JobThread**(如果不存在)
- 类:`XxlJobExecutor`
- 方法:`registJobThread(triggerParam.getJobId(), jobHandler, removeOldReason)`
- 作用:创建并启动新的任务线程
- 内部调用:`new JobThread(jobId, handler)` → `jobThread.start()`
7. **推送任务到队列**
- 类:`JobThread`
- 方法:`pushTriggerQueue(triggerParam)`
- 作用:将 `TriggerParam` 添加到 `triggerQueue` 队列中
##### 步骤4:响应序列化和返回
**类**:`EmbedServer.EmbedHttpServerHandler`
**方法**:`writeResponse(ChannelHandlerContext ctx, boolean keepAlive, String responseJson)`
**关键方法调用**:
1. **序列化响应**
- 类:`GsonTool`
- 方法:`toJson(responseObj)`
- 作用:将响应对象序列化为JSON字符串
2. **写回响应**
- 操作:通过Netty的 `ChannelHandlerContext` 写回HTTP响应
- 作用:返回 `ReturnT<String>` 给调度中心
六、执行阶段 阶段4: 任务执行阶段
JobThread → IJobHandler.execute()
java
**位置**:`JobThread.run()`
**执行流程**:
1. **JobThread持续运行**
- 类:`JobThread`(继承 `Thread`)
- 状态:线程持续运行,循环从队列中取任务
2. **从队列获取任务**
- 操作:`triggerQueue.poll(3L, TimeUnit.SECONDS)`
- 作用:阻塞等待,最多等待3秒
3. **执行任务处理器**
- 方法:`handler.execute(executorParams)`
- 作用:调用任务处理器的 `execute()` 方法执行任务
- 返回:`ReturnT<String>`(执行结果)
4. **推送回调**
- 方法:`TriggerCallbackThread.pushCallBack(handleCallbackParam)`
- 作用:将执行结果推送到回调队列,异步回调调度中心
**JobThread执行关键代码**(伪代码):
```java
// JobThread.run()
@Override
public void run() {
while (!toStop) {
try {
// 1. 从队列获取任务
TriggerParam triggerParam = triggerQueue.poll(3L, TimeUnit.SECONDS);
if (triggerParam != null) {
// 2. 执行任务
IJobHandler handler = this.handler;
ReturnT<String> executeResult = handler.execute(triggerParam.getExecutorParams());
// 3. 构建回调参数
HandleCallbackParam handleCallbackParam = new HandleCallbackParam();
handleCallbackParam.setLogId(triggerParam.getLogId());
handleCallbackParam.setExecuteResult(executeResult);
// 4. 推送回调
TriggerCallbackThread.pushCallBack(handleCallbackParam);
}
} catch (Throwable e) {
// 异常处理
HandleCallbackParam handleCallbackParam = new HandleCallbackParam();
handleCallbackParam.setLogId(triggerParam.getLogId());
handleCallbackParam.setExecuteCode(ReturnT.FAIL_CODE);
handleCallbackParam.setExecuteMsg(e.getMessage());
TriggerCallbackThread.pushCallBack(handleCallbackParam);
}
}
}
```
**HandleCallbackParam 参数内容**:
```java
HandleCallbackParam {
logId: 12345, // 日志ID(对应触发时的logId)
executeCode: 200, // 执行状态码(200-成功,500-失败)
executeMsg: "执行成功", // 执行消息
logDateTime: 1699123456789 // 日志时间
}
```
##### 步骤1:JobThread初始化
**类**:`JobThread`
**方法**:`run()`
```98:105:xxl-job-core/src/main/java/com/xxl/job/core/thread/JobThread.java
public void run() {
// init
try {
handler.init();
} catch (Throwable e) {
logger.error(e.getMessage(), e);
}
```
**关键方法调用**:
1. **初始化任务处理器**
- 类:`IJobHandler`(实际是 `MethodJobHandler`)
- 方法:`init()`
- 作用:调用任务处理器的初始化方法
- **MethodJobHandler.init()**:
- 类:`MethodJobHandler`
- 方法:`init()`
- 操作:如果 `initMethod != null`,调用 `initMethod.invoke(target)`
- 作用:反射调用用户定义的初始化方法
##### 步骤2:从队列获取任务
**类**:`JobThread`
**方法**:`run()` → `triggerQueue.poll()`
```108:120:xxl-job-core/src/main/java/com/xxl/job/core/thread/JobThread.java
// execute
while(!toStop){
running = false;
idleTimes++;
TriggerParam triggerParam = null;
try {
// to check toStop signal, we need cycle, so we cannot use queue.take(), instead of poll(timeout)
triggerParam = triggerQueue.poll(3L, TimeUnit.SECONDS);
if (triggerParam!=null) {
running = true;
idleTimes = 0;
triggerLogIdSet.remove(triggerParam.getLogId());
```
**关键方法调用**:
1. **从队列获取任务**
- 类:`LinkedBlockingQueue<TriggerParam>`(`triggerQueue`)
- 方法:`poll(3L, TimeUnit.SECONDS)`
- 作用:阻塞等待,最多等待3秒,获取任务参数
- 返回值:`TriggerParam`(如果队列为空且超时,返回null)
2. **更新运行状态**
- 操作:`running = true`
- 操作:`idleTimes = 0`
- 操作:`triggerLogIdSet.remove(triggerParam.getLogId())`
- 作用:标记任务正在运行,清除日志ID记录
##### 步骤3:准备执行环境
**类**:`JobThread`
**方法**:`run()` → 创建任务上下文
```121:132:xxl-job-core/src/main/java/com/xxl/job/core/thread/JobThread.java
// log filename, like "logPath/yyyy-MM-dd/9999.log"
String logFileName = XxlJobFileAppender.makeLogFileName(new Date(triggerParam.getLogDateTime()), triggerParam.getLogId());
XxlJobContext xxlJobContext = new XxlJobContext(
triggerParam.getJobId(),
triggerParam.getExecutorParams(),
logFileName,
triggerParam.getBroadcastIndex(),
triggerParam.getBroadcastTotal());
// init job context
XxlJobContext.setXxlJobContext(xxlJobContext);
```
**关键方法调用**:
1. **生成日志文件名**
- 类:`XxlJobFileAppender`
- 方法:`makeLogFileName(new Date(triggerParam.getLogDateTime()), triggerParam.getLogId())`
- 作用:生成日志文件路径(格式:`logPath/yyyy-MM-dd/{logId}.log`)
2. **创建任务上下文**
- 类:`XxlJobContext`(构造函数)
- 参数:`jobId`、`executorParams`、`logFileName`、`broadcastIndex`、`broadcastTotal`
- 作用:创建任务执行上下文对象
3. **设置任务上下文**
- 类:`XxlJobContext`
- 方法:`setXxlJobContext(xxlJobContext)`
- 作用:将上下文存储到线程局部变量(`ThreadLocal`)中,供任务执行时使用
##### 步骤4:记录开始日志
**类**:`JobThread`
**方法**:`run()` → `XxlJobHelper.log()`
```133:134:xxl-job-core/src/main/java/com/xxl/job/core/thread/JobThread.java
// execute
XxlJobHelper.log("<br>----------- xxl-job job execute start -----------<br>----------- Param:" + xxlJobContext.getJobParam());
```
**关键方法调用**:
1. **记录日志**
- 类:`XxlJobHelper`
- 方法:`log(String appendLogPattern, Object... appendLogArguments)`
- 作用:记录任务开始执行的日志
- 内部调用:
- `XxlJobContext.getXxlJobContext()` - 获取任务上下文
- `XxlJobFileAppender.appendLog()` - 追加日志到文件
##### 步骤5:执行任务处理器
**类**:`JobThread`
**方法**:`run()` → `handler.execute()`
**情况1:有超时时间**
```136:164:xxl-job-core/src/main/java/com/xxl/job/core/thread/JobThread.java
if (triggerParam.getExecutorTimeout() > 0) {
// limit timeout
Thread futureThread = null;
try {
FutureTask<Boolean> futureTask = new FutureTask<Boolean>(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
// init job context
XxlJobContext.setXxlJobContext(xxlJobContext);
handler.execute();
return true;
}
});
futureThread = new Thread(futureTask);
futureThread.start();
Boolean tempResult = futureTask.get(triggerParam.getExecutorTimeout(), TimeUnit.SECONDS);
} catch (TimeoutException e) {
XxlJobHelper.log("<br>----------- xxl-job job execute timeout");
XxlJobHelper.log(e);
// handle result
XxlJobHelper.handleTimeout("job execute timeout ");
} finally {
futureThread.interrupt();
}
```
**情况2:无超时时间**
```165:168:xxl-job-core/src/main/java/com/xxl/job/core/thread/JobThread.java
} else {
// just execute
handler.execute();
}
```
**关键方法调用**:
1. **创建Future任务**(有超时时间的情况)
- 类:`FutureTask<Boolean>`(构造函数)
- 参数:`Callable<Boolean>`(包含任务执行逻辑)
- 作用:创建可获取结果的异步任务
2. **启动任务线程**(有超时时间的情况)
- 类:`Thread`(构造函数)
- 参数:`futureTask`
- 方法:`start()`
- 作用:启动线程执行任务
3. **等待任务完成**(有超时时间的情况)
- 类:`FutureTask<Boolean>`
- 方法:`get(triggerParam.getExecutorTimeout(), TimeUnit.SECONDS)`
- 作用:等待任务完成,最多等待 `executorTimeout` 秒
- 如果超时:抛出 `TimeoutException`
4. **执行任务处理器**
- 类:`IJobHandler`(实际是 `MethodJobHandler`)
- 方法:`execute()`
- 作用:调用任务处理器的执行方法
- **MethodJobHandler.execute()**:
- 类:`MethodJobHandler`
- 方法:`execute()`
- 操作:
- 获取方法参数类型:`method.getParameterTypes()`
- 反射调用:`method.invoke(target)` 或 `method.invoke(target, new Object[paramTypes.length])`
- 作用:通过反射调用用户定义的业务方法
5. **处理超时**(有超时时间的情况)
- 类:`XxlJobHelper`
- 方法:`log()` - 记录超时日志
- 方法:`handleTimeout()` - 设置超时结果
- 方法:`futureThread.interrupt()` - 中断任务线程
##### 步骤6:处理执行结果
**类**:`JobThread`
**方法**:`run()` → 验证和处理执行结果
```170:184:xxl-job-core/src/main/java/com/xxl/job/core/thread/JobThread.java
// valid execute handle data
if (XxlJobContext.getXxlJobContext().getHandleCode() <= 0) {
XxlJobHelper.handleFail("job handle result lost.");
} else {
String tempHandleMsg = XxlJobContext.getXxlJobContext().getHandleMsg();
tempHandleMsg = (tempHandleMsg!=null&&tempHandleMsg.length()>50000)
?tempHandleMsg.substring(0, 50000).concat("...")
:tempHandleMsg;
XxlJobContext.getXxlJobContext().setHandleMsg(tempHandleMsg);
}
XxlJobHelper.log("<br>----------- xxl-job job execute end(finish) -----------<br>----------- Result: handleCode="
+ XxlJobContext.getXxlJobContext().getHandleCode()
+ ", handleMsg = "
+ XxlJobContext.getXxlJobContext().getHandleMsg()
);
```
**关键方法调用**:
1. **获取执行结果**
- 类:`XxlJobContext`
- 方法:`getXxlJobContext()` - 获取任务上下文
- 方法:`getHandleCode()` - 获取处理状态码
- 方法:`getHandleMsg()` - 获取处理消息
2. **处理结果丢失**
- 类:`XxlJobHelper`
- 方法:`handleFail("job handle result lost.")`
- 作用:如果任务没有调用 `handleSuccess()` 或 `handleFail()`,设置为失败
- 内部调用:`handleResult(HANDLE_CODE_FAIL, handleMsg)`
3. **限制消息长度**
- 操作:如果 `handleMsg` 长度超过50000字符,截断并添加 "..."
- 作用:避免消息过长
4. **记录结束日志**
- 类:`XxlJobHelper`
- 方法:`log()` - 记录任务执行结束日志
##### 步骤7:异常处理
**类**:`JobThread`
**方法**:`run()` → catch块
```193:205:xxl-job-core/src/main/java/com/xxl/job/core/thread/JobThread.java
} catch (Throwable e) {
if (toStop) {
XxlJobHelper.log("<br>----------- JobThread toStop, stopReason:" + stopReason);
}
// handle result
StringWriter stringWriter = new StringWriter();
e.printStackTrace(new PrintWriter(stringWriter));
String errorMsg = stringWriter.toString();
XxlJobHelper.handleFail(errorMsg);
XxlJobHelper.log("<br>----------- JobThread Exception:" + errorMsg + "<br>----------- xxl-job job execute end(error) -----------");
} finally {
```
**关键方法调用**:
1. **捕获异常堆栈**
- 类:`StringWriter`、`PrintWriter`
- 操作:`e.printStackTrace(new PrintWriter(stringWriter))`
- 作用:将异常堆栈信息转换为字符串
2. **设置失败结果**
- 类:`XxlJobHelper`
- 方法:`handleFail(errorMsg)`
- 作用:设置执行结果为失败
- 内部调用:`handleResult(HANDLE_CODE_FAIL, errorMsg)`
3. **记录异常日志**
- 类:`XxlJobHelper`
- 方法:`log()` - 记录异常日志
##### 步骤8:推送回调
**类**:`JobThread`
**方法**:`run()` → finally块 → `TriggerCallbackThread.pushCallBack()`
```206:226:xxl-job-core/src/main/java/com/xxl/job/core/thread/JobThread.java
finally {
if(triggerParam != null) {
// callback handler info
if (!toStop) {
// common
TriggerCallbackThread.pushCallBack(new HandleCallbackParam(
triggerParam.getLogId(),
triggerParam.getLogDateTime(),
XxlJobContext.getXxlJobContext().getHandleCode(),
XxlJobContext.getXxlJobContext().getHandleMsg() )
);
} else {
// is killed
TriggerCallbackThread.pushCallBack(new HandleCallbackParam(
triggerParam.getLogId(),
triggerParam.getLogDateTime(),
XxlJobContext.HANDLE_CODE_FAIL,
stopReason + " [job running, killed]" )
);
}
}
}
```
**关键方法调用**:
1. **构建回调参数**
- 类:`HandleCallbackParam`(构造函数)
- 参数:
- `logId` - 日志ID
- `logDateTime` - 日志时间
- `handleCode` - 处理状态码(从 `XxlJobContext` 获取)
- `handleMsg` - 处理消息(从 `XxlJobContext` 获取)
2. **获取执行结果**
- 类:`XxlJobContext`
- 方法:`getXxlJobContext()` - 获取任务上下文
- 方法:`getHandleCode()` - 获取处理状态码
- 方法:`getHandleMsg()` - 获取处理消息
3. **推送回调**
- 类:`TriggerCallbackThread`
- 方法:`pushCallBack(HandleCallbackParam)`
- 作用:将回调参数添加到回调队列
- 内部调用:`callBackQueue.add(callback)`
##### 步骤9:清理任务上下文
**类**:`JobThread`
**方法**:`run()` → finally块(隐式)
**关键操作**:
- 任务执行完成后,`finally` 块执行,当前循环结束
- 下一轮循环开始前,如果有新的任务,会重新设置 `XxlJobContext`
- 如果线程结束,会调用 `handler.destroy()` 清理资源
七、执行阶段 阶段5: 结果回调阶段
TriggerCallbackThread → AdminBiz.callback()
java
**位置**:`TriggerCallbackThread` → `AdminBiz.callback()`
**回调流程**:
1. **执行器推送回调到队列**
- 类:`TriggerCallbackThread`
- 方法:`pushCallBack(HandleCallbackParam)`
- 操作:将回调参数添加到 `callBackQueue` 队列
2. **回调线程批量处理**
- 类:`TriggerCallbackThread`
- 线程:`triggerCallbackThread`
- 操作:
- 从队列中取出回调参数
- 批量收集(使用 `drainTo()` 提高效率)
- 调用调度中心的回调接口
**TriggerCallbackThread 回调处理**:
```58:84:xxl-job-core/src/main/java/com/xxl/job/core/thread/TriggerCallbackThread.java
triggerCallbackThread = new Thread(new Runnable() {
@Override
public void run() {
// normal callback
while(!toStop){
try {
HandleCallbackParam callback = getInstance().callBackQueue.take();
if (callback != null) {
// callback list param
List<HandleCallbackParam> callbackParamList = new ArrayList<HandleCallbackParam>();
int drainToNum = getInstance().callBackQueue.drainTo(callbackParamList);
callbackParamList.add(callback);
// callback, will retry if error
if (callbackParamList!=null && callbackParamList.size()>0) {
doCallback(callbackParamList);
}
}
} catch (Throwable e) {
if (!toStop) {
logger.error(e.getMessage(), e);
}
}
}
```
3. **HTTP回调请求发送**
- 操作:通过 `AdminBizClient` 发送HTTP POST请求
- URL:`http://调度中心地址/api/callback`
- 数据:`List<HandleCallbackParam>`(批量回调)
**HTTP回调请求**:
```
POST http://192.168.1.1:9091/xxl-job-admin/api/callback HTTP/1.1
Host: 192.168.1.1:9091
Content-Type: application/json;charset=UTF-8
XXL-JOB-ACCESS-TOKEN: your-access-token
Content-Length: 512
[
{
"logId": 12345,
"executeCode": 200,
"executeMsg": "执行成功",
"logDateTime": 1699123456789
}
]
```
4. **调度中心接收回调**
- 类:`JobApiController`
- 路径:`/api/callback`
- 方法:`callback(HttpServletRequest request, @RequestBody String data)`
5. **处理回调**
- 类:`JobCompleteHelper`
- 方法:`callback(HandleCallbackParam handleCallbackParam)`
- 操作:
- 验证日志记录(防止重复回调)
- 更新任务日志(执行时间、状态码、消息)
- 触发子任务(如果执行成功)
- 更新日志到数据库
**JobCompleteHelper.callback() 处理**:
```154:180:xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/thread/JobCompleteHelper.java
private ReturnT<String> callback(HandleCallbackParam handleCallbackParam) {
// valid log item
XxlJobLog log = XxlJobAdminConfig.getAdminConfig().getXxlJobLogMapper().load(handleCallbackParam.getLogId());
if (log == null) {
return ReturnT.ofFail( "log item not found.");
}
if (log.getHandleCode() > 0) {
return ReturnT.ofFail("log repeate callback."); // avoid repeat callback, trigger child job etc
}
// handle msg
StringBuffer handleMsg = new StringBuffer();
if (log.getHandleMsg()!=null) {
handleMsg.append(log.getHandleMsg()).append("<br>");
}
if (handleCallbackParam.getHandleMsg() != null) {
handleMsg.append(handleCallbackParam.getHandleMsg());
}
// success, save log
log.setHandleTime(new Date());
log.setHandleCode(handleCallbackParam.getHandleCode());
log.setHandleMsg(handleMsg.toString());
XxlJobCompleter.updateHandleInfoAndFinish(log);
return ReturnT.ofSuccess();
}
```
**回调处理关键点**:
1. **防重复回调**
- 检查:`log.getHandleCode() > 0`
- 作用:如果日志已经处理过,拒绝重复回调
2. **更新日志**
- 字段:`handleTime`(处理时间)、`handleCode`(状态码)、`handleMsg`(消息)
- 操作:更新到 `xxl_job_log` 表
3. **触发子任务**
- 类:`XxlJobCompleter.finishJob()`
- 条件:任务执行成功(`handleCode == 200`)且有子任务配置
- 操作:调用 `JobTriggerPoolHelper.trigger()` 触发子任务
##### 步骤1:推送回调到队列
**类**:`TriggerCallbackThread`
**方法**:`pushCallBack(HandleCallbackParam callback)`
```38:41:xxl-job-core/src/main/java/com/xxl/job/core/thread/TriggerCallbackThread.java
public static void pushCallBack(HandleCallbackParam callback){
getInstance().callBackQueue.add(callback);
logger.debug(">>>>>>>>>>> xxl-job, push callback request, logId:{}", callback.getLogId());
}
```
**关键方法调用**:
1. **添加到回调队列**
- 类:`LinkedBlockingQueue<HandleCallbackParam>`(`callBackQueue`)
- 方法:`add(callback)`
- 作用:将回调参数添加到队列中
- 调用位置:`JobThread.run()` 的 `finally` 块中
##### 步骤2:回调线程处理
**类**:`TriggerCallbackThread`
**线程**:`triggerCallbackThread`
**方法**:`run()`
```58:84:xxl-job-core/src/main/java/com/xxl/job/core/thread/TriggerCallbackThread.java
triggerCallbackThread = new Thread(new Runnable() {
@Override
public void run() {
// normal callback
while(!toStop){
try {
HandleCallbackParam callback = getInstance().callBackQueue.take();
if (callback != null) {
// callback list param
List<HandleCallbackParam> callbackParamList = new ArrayList<HandleCallbackParam>();
int drainToNum = getInstance().callBackQueue.drainTo(callbackParamList);
callbackParamList.add(callback);
// callback, will retry if error
if (callbackParamList!=null && callbackParamList.size()>0) {
doCallback(callbackParamList);
}
}
} catch (Throwable e) {
if (!toStop) {
logger.error(e.getMessage(), e);
}
}
}
```
**关键方法调用**:
1. **从队列获取回调参数**
- 类:`LinkedBlockingQueue<HandleCallbackParam>`(`callBackQueue`)
- 方法:`take()`
- 作用:阻塞等待,直到队列中有数据
2. **批量获取回调参数**
- 类:`LinkedBlockingQueue<HandleCallbackParam>`(`callBackQueue`)
- 方法:`drainTo(callbackParamList)`
- 作用:批量取出队列中的所有回调参数,提高效率
3. **执行回调**
- 类:`TriggerCallbackThread`
- 方法:`doCallback(callbackParamList)`
- 作用:向调度中心发送回调请求
##### 步骤3:执行回调 - doCallback()
**类**:`TriggerCallbackThread`
**方法**:`doCallback(List<HandleCallbackParam> callbackParamList)`
```163:183:xxl-job-core/src/main/java/com/xxl/job/core/thread/TriggerCallbackThread.java
private void doCallback(List<HandleCallbackParam> callbackParamList){
boolean callbackRet = false;
// callback, will retry if error
for (AdminBiz adminBiz: XxlJobExecutor.getAdminBizList()) {
try {
ReturnT<String> callbackResult = adminBiz.callback(callbackParamList);
if (callbackResult!=null && callbackResult.isSuccess()) {
callbackLog(callbackParamList, "<br>----------- xxl-job job callback finish.");
callbackRet = true;
break;
} else {
callbackLog(callbackParamList, "<br>----------- xxl-job job callback fail, callbackResult:" + callbackResult);
}
} catch (Throwable e) {
callbackLog(callbackParamList, "<br>----------- xxl-job job callback error, errorMsg:" + e.getMessage());
}
}
if (!callbackRet) {
appendFailCallbackFile(callbackParamList);
}
}
```
**关键方法调用**:
1. **获取调度中心客户端列表**
- 类:`XxlJobExecutor`
- 方法:`getAdminBizList()`
- 作用:获取所有调度中心客户端(支持多个调度中心)
- 返回值:`List<AdminBiz>`(`AdminBizClient` 列表)
2. **回调调度中心**(遍历多个调度中心)
- 类:`AdminBiz`(实际是 `AdminBizClient`)
- 方法:`callback(callbackParamList)`
- 作用:向调度中心发送回调请求
- 成功条件:返回 `ReturnT.isSuccess() == true`
- 如果成功:跳出循环
- 如果失败:尝试下一个调度中心
3. **记录回调日志**
- 类:`TriggerCallbackThread`
- 方法:`callbackLog(callbackParamList, logContent)`
- 作用:记录回调结果日志
4. **写入失败回调文件**(所有调度中心都失败时)
- 类:`TriggerCallbackThread`
- 方法:`appendFailCallbackFile(callbackParamList)`
- 作用:将失败的回调参数序列化后写入文件,等待重试
##### 步骤4:HTTP回调请求发送
**类**:`AdminBizClient`
**方法**:`callback(List<HandleCallbackParam> callbackParamList)`
**关键方法调用**:
1. **发送HTTP POST请求**
- 类:`XxlJobRemotingUtil`
- 方法:`postBody(url, accessToken, timeout, callbackParamList, String.class)`
- 参数:
- `url` - 调度中心回调地址(`http://调度中心地址/api/callback`)
- `accessToken` - 访问令牌
- `timeout` - 超时时间
- `callbackParamList` - 回调参数列表(`List<HandleCallbackParam>`)
- 作用:发送HTTP POST请求到调度中心
2. **序列化回调参数**
- 类:`GsonTool`
- 方法:`toJson(callbackParamList)`
- 作用:将 `List<HandleCallbackParam>` 序列化为JSON字符串
- HTTP请求体:
```json
[
{
"logId": 12345,
"executeCode": 200,
"executeMsg": "执行成功",
"logDateTime": 1699123456789
}
]
```
3. **HTTP请求发送**(`XxlJobRemotingUtil.postBody()` 内部)
- 操作:使用 `HttpURLConnection` 发送HTTP POST请求
- 请求头:
- `Content-Type: application/json;charset=UTF-8`
- `XXL-JOB-ACCESS-TOKEN: {accessToken}`
- 请求体:JSON格式的回调参数列表
4. **解析响应**
- 操作:读取HTTP响应,反序列化为 `ReturnT<String>`
- 类:`GsonTool`
- 方法:`fromJson(responseJson, ReturnT.class, String.class)`
##### 步骤5:失败回调文件处理
**类**:`TriggerCallbackThread`
**方法**:`appendFailCallbackFile()`、`retryFailCallbackFile()`
**关键方法调用**:
1. **序列化回调参数**
- 类:`JdkSerializeTool`
- 方法:`serialize(callbackParamList)`
- 作用:将回调参数列表序列化为字节数组(用于文件存储)
2. **写入文件**
- 类:`FileUtil`
- 方法:`writeFileContent(callbackLogFile, callbackParamList_bytes)`
- 作用:将序列化后的数据写入文件
- 文件路径:`logPath/callbacklog/xxl-job-callback-{timestamp}.log`
3. **重试失败回调**(`triggerRetryCallbackThread` 线程)
- 类:`TriggerCallbackThread`
- 方法:`retryFailCallbackFile()`
- 执行频率:每30秒执行一次
- 操作:
- 读取失败回调文件
- 反序列化回调参数
- 调用 `doCallback()` 重新回调
- 删除已处理的文件
八、停止阶段:
java
xxl-job-admin/src/main/java/com/xxl/job/admin/scheduler/scheduler/XxlJobScheduler.java
public void destroy() throws Exception {
// stop-schedule
JobScheduleHelper.getInstance().toStop();
// admin log report stop
JobLogReportHelper.getInstance().toStop();
// admin lose-monitor stop
JobCompleteHelper.getInstance().toStop();
// admin fail-monitor stop
JobFailMonitorHelper.getInstance().toStop();
// admin registry stop
JobRegistryHelper.getInstance().toStop();
// admin trigger pool stop
JobTriggerPoolHelper.toStop();
}