项目完整解读源码地址:https://gitee.com/duhai123/xxl_job_study
XxlJobDynamicScheduler中的方法:
/**
* add trigger + job 增加并开始执行任务
*
* @param jobName
* 任务id
* @param jobGroup
* 执行器id
* @param cronExpression
* cron表达式
* @return
* @throws SchedulerException
*/
public static boolean addJob(String jobName, String jobGroup, String cronExpression) throws SchedulerException {
// 1、job key
TriggerKey triggerKey = TriggerKey.triggerKey(jobName, jobGroup);
JobKey jobKey = new JobKey(jobName, jobGroup);
// 2、valid 验证是否存在这个触发器任务
if (scheduler.checkExists(triggerKey)) {
return true; // PASS
}
// 3、corn trigger
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression)
.withMisfireHandlingInstructionDoNothing(); // withMisfireHandlingInstructionDoNothing 忽略掉调度终止过程中忽略的调度
CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder)
.build();
// 4、job detail RemoteHttpJobBean的方式执行
Class extends Job> jobClass_ = RemoteHttpJobBean.class; // Class.forName(jobInfo.getJobClass());
JobDetail jobDetail = JobBuilder.newJob(jobClass_).withIdentity(jobKey).build();
/*
* if (jobInfo.getJobData()!=null) { JobDataMap jobDataMap = jobDetail.getJobDataMap();
* jobDataMap.putAll(JacksonUtil.readValue(jobInfo.getJobData(), Map.class)); // JobExecutionContext
* context.getMergedJobDataMap().get("mailGuid"); }
*/
// 5、schedule job 放入到定时任务中
Date date = scheduler.scheduleJob(jobDetail, cronTrigger);
logger.info(">>>>>>>>>>> addJob success, jobDetail:{}, cronTrigger:{}, date:{}", jobDetail, cronTrigger, date);
return true;
}
xxl-job 所有的任务触发最终都是通过RemotehttpJobBean 来执行 , 该类继承关系如下:
RemoteHttpJobBean > QuartzJobBean > Job
当quartz监听到有任务需要触发是,会调用 JobRunShell 的run方法, 在该类的run方法中,会调用当前任务的JOB_CLASS 的excute方法
/**
* 触发器任务执行的时候,调用这个方法
*/
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
logger.info(">>>>>>>>> RemoteHttpJobBean >>>> put in JobTriggerPool...start......");
// load jobId
JobKey jobKey = context.getTrigger().getJobKey();
Integer jobId = Integer.valueOf(jobKey.getName());
// trigger 加入到触发器的线程池中
JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null);
logger.info(">>>>>>>>> RemoteHttpJobBean >>>> put in JobTriggerPool...end......");
}
JobTriggerPoolHelper:job触发器线程池
/**
* 调用JobTriggerPoolHelper的执行任务方法
*
* @param jobId
* 调度任务信息XxlJobInfo表主键
* @param triggerType
* 触发类型
* @param failRetryCount
* >=0: use this param <0: use param from job info config
* >=0: 用这个值 <0: 用XxlJobInfo中配置好的重试次数
* @param executorShardingParam
* 执行分片的参数
* @param executorParam
* 执行参数:
* null: use job param ;not null: cover job param
* null: 用XxlJobInfo中配置好的参数 ;not null: 使用这个参数(覆盖XxlJobInfo中配置好的参数)
*/
public static void trigger(int jobId, TriggerTypeEnum triggerType, int failRetryCount, String executorShardingParam,
String executorParam) {
helper.addTrigger(jobId, triggerType, failRetryCount, executorShardingParam, executorParam);
}
addTrigger方法:将任务提交给一个线程池(任务调度中心默认开启50个线程),在线程池中调用XxlJobTrigger.trigger
/**
* 将任务提交给一个线程池(任务调度中心默认开启50个线程),在线程池中调用XxlJobTrigger.trigger
*
* @param jobId
* 调度任务信息XxlJobInfo表主键
* @param triggerType
* 触发类型
* @param failRetryCount
* >=0: use this param <0: use param from job info config
* >=0: 用这个值 <0: 用XxlJobInfo中配置好的重试次数
* @param executorShardingParam
* 执行分片的参数
* @param executorParam
* 执行参数:
* null: use job param ;not null: cover job param
* null: 用XxlJobInfo中配置好的参数 ;not null: 使用这个参数(覆盖XxlJobInfo中配置好的参数)
*/
public void addTrigger(final int jobId, final TriggerTypeEnum triggerType, final int failRetryCount,
final String executorShardingParam, final String executorParam) {
triggerPool.execute(new Runnable() {
@Override
public void run() {
XxlJobTrigger.trigger(jobId, triggerType, failRetryCount, executorShardingParam, executorParam);
}
});
}
XxlJobTrigger: 任务触发执行类
/**
* 触发任务
*
* @param jobId
* 调度任务信息XxlJobInfo表主键
* @param triggerType
* 触发类型
* @param failRetryCount
* >=0: use this param <0: use param from job info config
* >=0: 用这个值 <0: 用XxlJobInfo中配置好的重试次数
* @param executorShardingParam
* 执行分片的参数
* @param executorParam
* 执行参数:
* null: use job param ;not null: cover job param
* null: 用XxlJobInfo中配置好的参数 ;not null: 使用这个参数(覆盖XxlJobInfo中配置好的参数)
*/
public static void trigger(int jobId, TriggerTypeEnum triggerType, int failRetryCount, String executorShardingParam,
String executorParam) {
// 查询job信息
XxlJobInfo jobInfo = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().loadById(jobId);
if (jobInfo == null) {
logger.warn(">>>>>>>>>>>> trigger fail, jobId invalid,jobId={}", jobId);
return;
}
if (executorParam != null) {// 执行参数不为空,则覆盖设置执行参数
jobInfo.setExecutorParam(executorParam);
}
// 设置失败重试次数失败重试次数
int finalFailRetryCount = failRetryCount >= 0 ? failRetryCount : jobInfo.getExecutorFailRetryCount();
// 获取执行器信息
XxlJobGroup group = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().load(jobInfo.getJobGroup());
// sharding param
int[] shardingParam = null;
if (executorShardingParam != null) {
String[] shardingArr = executorShardingParam.split("/");
if (shardingArr.length == 2 && StringUtils.isNumeric(shardingArr[0])
&& StringUtils.isNumeric(shardingArr[1])) {
shardingParam = new int[2];
shardingParam[0] = Integer.valueOf(shardingArr[0]);
shardingParam[1] = Integer.valueOf(shardingArr[1]);
}
}
// 路由:分片广播
if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST == ExecutorRouteStrategyEnum
.match(jobInfo.getExecutorRouteStrategy(), null) && CollectionUtils.isNotEmpty(group.getRegistryList())
&& shardingParam == null) {
for (int i = 0; i < group.getRegistryList().size(); i++) {
processTrigger(group, jobInfo, finalFailRetryCount, triggerType, i, group.getRegistryList().size());
}
} else {// 路由:非分片广播
if (shardingParam == null) {
shardingParam = new int[] { 0, 1 };
}
// 执行任务
processTrigger(group, jobInfo, finalFailRetryCount, triggerType, shardingParam[0], shardingParam[1]);
}
}
processTrigger:
/**
* 执行任务
*
* @param group
* 执行器信息
* @param jobInfo
* 任务信息
* @param finalFailRetryCount
* 失败重试次数失败重试次数
* @param triggerType
* 触发类型
* @param index
* sharding index
* @param total
* sharding index
*/
private static void processTrigger(XxlJobGroup group, XxlJobInfo jobInfo, int finalFailRetryCount,
TriggerTypeEnum triggerType, int index, int total) {
// 阻塞处理策略
ExecutorBlockStrategyEnum blockStrategy = ExecutorBlockStrategyEnum.match(jobInfo.getExecutorBlockStrategy(),
ExecutorBlockStrategyEnum.SERIAL_EXECUTION); // block strategy
// 路由策略
ExecutorRouteStrategyEnum executorRouteStrategyEnum = ExecutorRouteStrategyEnum
.match(jobInfo.getExecutorRouteStrategy(), null); // route strategy
// 路由策略是否是分片广播
String shardingParam = (ExecutorRouteStrategyEnum.SHARDING_BROADCAST == executorRouteStrategyEnum)
? String.valueOf(index).concat("/").concat(String.valueOf(total)) : null;
// ----------------执行前进行的操作-----------------------TODO--------
// 1、save log-id
XxlJobLog jobLog = new XxlJobLog();
jobLog.setJobGroup(jobInfo.getJobGroup());
jobLog.setJobId(jobInfo.getId());
jobLog.setTriggerTime(new Date());
// 保存日志
XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().save(jobLog);
logger.debug(">>>>>>>>>>> xxl-job trigger start, jobId:{}", jobLog.getId());
// 2、init trigger-param===初始化设置触发器参数
TriggerParam triggerParam = new TriggerParam();
triggerParam.setJobId(jobInfo.getId());
triggerParam.setExecutorHandler(jobInfo.getExecutorHandler());
triggerParam.setExecutorParams(jobInfo.getExecutorParam());
triggerParam.setExecutorBlockStrategy(jobInfo.getExecutorBlockStrategy());
triggerParam.setExecutorTimeout(jobInfo.getExecutorTimeout());
triggerParam.setLogId(jobLog.getId());
triggerParam.setLogDateTim(jobLog.getTriggerTime().getTime());
triggerParam.setGlueType(jobInfo.getGlueType());
triggerParam.setGlueSource(jobInfo.getGlueSource());
triggerParam.setGlueUpdatetime(jobInfo.getGlueUpdatetime().getTime());
triggerParam.setBroadcastIndex(index);
triggerParam.setBroadcastTotal(total);
// 3、init address(根据路由策略执行器地址)
String address = null;
ReturnT routeAddressResult = null;
// 执行器地址列表不为空
if (CollectionUtils.isNotEmpty(group.getRegistryList())) {
// 分片广播
if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST == executorRouteStrategyEnum) {
if (index < group.getRegistryList().size()) {
address = group.getRegistryList().get(index);
} else {
address = group.getRegistryList().get(0);
}
} else {// 其他方式
// 此处使用了策略模式, 根据不同的策略 使用不同的实现类 TODO
routeAddressResult = executorRouteStrategyEnum.getRouter().route(triggerParam, group.getRegistryList());
if (routeAddressResult.getCode() == ReturnT.SUCCESS_CODE) {
address = routeAddressResult.getContent();
logger.debug(">>>>>>>>>>> xxl-job trigger excute address, address:{}", address);
}
}
} else {
routeAddressResult = new ReturnT(ReturnT.FAIL_CODE,
I18nUtil.getString("jobconf_trigger_address_empty"));
}
// 4、trigger remote executor 调用执行器远程执行
ReturnT triggerResult = null;
if (address != null) {
// ---- 调用执行器远程执行 ----- TODO --
triggerResult = runExecutor(triggerParam, address);
} else {
triggerResult = new ReturnT(ReturnT.FAIL_CODE, null);
}
// 5、collection trigger info 触发执行后的消息
StringBuffer triggerMsgSb = new StringBuffer();
triggerMsgSb.append(I18nUtil.getString("jobconf_trigger_type")).append(":").append(triggerType.getTitle());
triggerMsgSb.append("
").append(I18nUtil.getString("jobconf_trigger_admin_adress")).append(":")
.append(IpUtil.getIp());
triggerMsgSb.append("
").append(I18nUtil.getString("jobconf_trigger_exe_regtype")).append(":")
.append((group.getAddressType() == 0) ? I18nUtil.getString("jobgroup_field_addressType_0")
: I18nUtil.getString("jobgroup_field_addressType_1"));
triggerMsgSb.append("
").append(I18nUtil.getString("jobconf_trigger_exe_regaddress")).append(":")
.append(group.getRegistryList());
triggerMsgSb.append("
").append(I18nUtil.getString("jobinfo_field_executorRouteStrategy")).append(":")
.append(executorRouteStrategyEnum.getTitle());
if (shardingParam != null) {
triggerMsgSb.append("(" + shardingParam + ")");
}
triggerMsgSb.append("
").append(I18nUtil.getString("jobinfo_field_executorBlockStrategy")).append(":")
.append(blockStrategy.getTitle());
triggerMsgSb.append("
").append(I18nUtil.getString("jobinfo_field_timeout")).append(":")
.append(jobInfo.getExecutorTimeout());
triggerMsgSb.append("
").append(I18nUtil.getString("jobinfo_field_executorFailRetryCount")).append(":")
.append(finalFailRetryCount);
triggerMsgSb
.append("
>>>>>>>>>>>"
+ I18nUtil.getString("jobconf_trigger_run") + "<<<<<<<<<<<
")
.append((routeAddressResult != null && routeAddressResult.getMsg() != null)
? routeAddressResult.getMsg() + "
" : "")
.append(triggerResult.getMsg() != null ? triggerResult.getMsg() : "");
// 6、save log trigger-info 修改触发器日志信息
jobLog.setExecutorAddress(address);
jobLog.setExecutorHandler(jobInfo.getExecutorHandler());
jobLog.setExecutorParam(jobInfo.getExecutorParam());
jobLog.setExecutorShardingParam(shardingParam);
jobLog.setExecutorFailRetryCount(finalFailRetryCount);
// jobLog.setTriggerTime();
jobLog.setTriggerCode(triggerResult.getCode());
jobLog.setTriggerMsg(triggerMsgSb.toString());// 修改日志触发信息
// 修改日志触发信息
XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateTriggerInfo(jobLog);
// 7、monitor trigger
// 最后:这个触发器任务正在执行,放到队列里面
// 要么执行器回调后被操作,要么超时被失败日志监控线程操作
JobFailMonitorHelper.monitor(jobLog.getId());
logger.debug(">>>>>>>>>>> xxl-job trigger end, jobId:{}", jobLog.getId());
}
runExecutor:调用执行器执行该触发器任务
/**
* 调用执行器执行该触发器任务 run executor
*
* @param triggerParam
* 触发器参数(调用执行器进行执行)
* @param address
* 执行器地址
* @return
*/
public static ReturnT runExecutor(TriggerParam triggerParam, String address) {
ReturnT runResult = null;
try {
// 创建一个ExcutorBiz 的对象,重点在这个方法里面
ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address);
// 这个run 方法不会最终执行,仅仅只是为了触发 proxy object 的 invoke方法,
// 同时将目标的类型传送给执行器端, 因为在代理对象的invoke的方法里面没有执行目标对象的方法
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(ReturnT.FAIL_CODE, ThrowableUtil.toString(e));
}
StringBuffer runResultSB = new StringBuffer(I18nUtil.getString("jobconf_trigger_run") + ":");
runResultSB.append("
address:").append(address);
runResultSB.append("
code:").append(runResult.getCode());
runResultSB.append("
msg:").append(runResult.getMsg());
runResult.setMsg(runResultSB.toString());
return runResult;
}
XxlJobDynamicScheduler.getExecutorBiz (address) , 通过机器地址,获取一个executor
// -------------- executor-client -----执行器client(用这个去调用执行器的接口)---TODO-------------
// 执行器client的map
private static ConcurrentHashMap executorBizRepository = new ConcurrentHashMap();
/**
* 获取执行器client
*
* @param address
* @return
* @throws Exception
*/
public static ExecutorBiz getExecutorBiz(String address) throws Exception {
// valid
if (address == null || address.trim().length() == 0) {
return null;
}
// 执行器client的map 中获取
address = address.trim();
// 查看缓存里面是否存在,如果存在则不需要再去创建executorBiz了
ExecutorBiz executorBiz = executorBizRepository.get(address);
if (executorBiz != null) {
return executorBiz;
}
// 创建ExecutorBiz的代理对象
executorBiz = (ExecutorBiz) new XxlRpcReferenceBean(NetEnum.JETTY,
Serializer.SerializeEnum.HESSIAN.getSerializer(), CallType.SYNC, ExecutorBiz.class, null, 10000,
address, XxlJobAdminConfig.getAdminConfig().getAccessToken(), null).getObject();
// 放入执行器client的map
executorBizRepository.put(address, executorBiz);
return executorBiz;
}
XxlRpcReferenceBean:xxl-rpc项目可以仔细研究源代码,这里我们不深究,但还是大约看看是怎么RPC通过远程调用的
public Object getObject() {
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[] { iface },
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String className = method.getDeclaringClass().getName();
// filter method like "Object.toString()"
if (Object.class.getName().equals(className)) {
logger.info(">>>>>>>>>>> xxl-rpc proxy class-method not support [{}.{}]", className,
method.getName());
throw new XxlRpcException("xxl-rpc proxy class-method not support");
}
// address
String address = routeAddress();
if (address == null || address.trim().length() == 0) {
throw new XxlRpcException("xxl-rpc reference bean[" + className + "] address empty");
}
// request
// 创建request信息, 发送HTTP请求到执行器服务器上。
XxlRpcRequest xxlRpcRequest = new XxlRpcRequest();
xxlRpcRequest.setRequestId(UUID.randomUUID().toString());
// 创建时间, 用于判断请求是否超时
xxlRpcRequest.setCreateMillisTime(System.currentTimeMillis());
// 数据校验
xxlRpcRequest.setAccessToken(accessToken);
// 将目标类的class名称传给执行器,让那边来创建对象,并执行逻辑代码
xxlRpcRequest.setClassName(className);
// 方法名称为run
xxlRpcRequest.setMethodName(method.getName());
// 参数类型
xxlRpcRequest.setParameterTypes(method.getParameterTypes());
// 参数
xxlRpcRequest.setParameters(args);
// send
if (CallType.SYNC == callType) {
try {
// future set
XxlRpcFutureResponse futureResponse = new XxlRpcFutureResponse(xxlRpcRequest, null);
// do invoke 发送执行方法请求
client.asyncSend(address, xxlRpcRequest);
// future get
XxlRpcResponse xxlRpcResponse = futureResponse.get(timeout, TimeUnit.MILLISECONDS);
if (xxlRpcResponse.getErrorMsg() != null) {
throw new XxlRpcException(xxlRpcResponse.getErrorMsg());
}
return xxlRpcResponse.getResult();
} catch (Exception e) {
logger.info(">>>>>>>>>>> xxl-job, invoke error, address:{}, XxlRpcRequest{}", address,
xxlRpcRequest);
throw (e instanceof XxlRpcException) ? e : new XxlRpcException(e);
} finally {
// remove-InvokerFuture
XxlRpcFutureResponseFactory.removeInvokerFuture(xxlRpcRequest.getRequestId());
}
} else if (CallType.FUTURE == callType) {
// thread future set
XxlRpcInvokeFuture invokeFuture = null;
try {
// future set
invokeFuture = new XxlRpcInvokeFuture(new XxlRpcFutureResponse(xxlRpcRequest, null));
XxlRpcInvokeFuture.setFuture(invokeFuture);
// do invoke
client.asyncSend(address, xxlRpcRequest);
return null;
} catch (Exception e) {
logger.info(">>>>>>>>>>> xxl-job, invoke error, address:{}, XxlRpcRequest{}", address,
xxlRpcRequest);
// remove-InvokerFuture
invokeFuture.stop();
throw (e instanceof XxlRpcException) ? e : new XxlRpcException(e);
}
} else if (CallType.CALLBACK == callType) {
// get callback
XxlRpcInvokeCallback finalInvokeCallback = invokeCallback;
XxlRpcInvokeCallback threadInvokeCallback = XxlRpcInvokeCallback.getCallback();
if (threadInvokeCallback != null) {
finalInvokeCallback = threadInvokeCallback;
}
if (finalInvokeCallback == null) {
throw new XxlRpcException("xxl-rpc XxlRpcInvokeCallback(CallType="
+ CallType.CALLBACK.name() + ") cannot be null.");
}
try {
// future set
XxlRpcFutureResponse futureResponse = new XxlRpcFutureResponse(xxlRpcRequest,
finalInvokeCallback);
client.asyncSend(address, xxlRpcRequest);
} catch (Exception e) {
logger.info(">>>>>>>>>>> xxl-job, invoke error, address:{}, XxlRpcRequest{}", address,
xxlRpcRequest);
// future remove
XxlRpcFutureResponseFactory.removeInvokerFuture(xxlRpcRequest.getRequestId());
throw (e instanceof XxlRpcException) ? e : new XxlRpcException(e);
}
return null;
} else if (CallType.ONEWAY == callType) {
client.asyncSend(address, xxlRpcRequest);
return null;
} else {
throw new XxlRpcException("xxl-rpc callType[" + callType + "] invalid");
}
}
});
}