preface
This article mainly analyzes the source code of several main operations of XXL job.
1. Create actuator - dispatch center background
On the background admin page, add an actuator.
be careful:
- Select "automatic registration" (automatic registration method is introduced here)
- AppName corresponds to xxl.xml configured in the project application job. executor. The appName attribute is the appName of the XxlJobSpringExecutor.
The / jobgroup/save method will be called. After basic verification, the executor information will be saved to table xxl_job_group.
The specific location of the source code is in the JobGroupController of XXL job admin.
@RequestMapping("/save") @ResponseBody public ReturnT<String> save(XxlJobGroup xxlJobGroup){ // valid if (xxlJobGroup.getAppname()==null || xxlJobGroup.getAppname().trim().length()==0) { return new ReturnT<String>(500, (I18nUtil.getString("system_please_input")+"AppName") ); } if (xxlJobGroup.getAppname().length()<4 || xxlJobGroup.getAppname().length()>64) { return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_appname_length") ); } if (xxlJobGroup.getAppname().contains(">") || xxlJobGroup.getAppname().contains("<")) { return new ReturnT<String>(500, "AppName"+I18nUtil.getString("system_unvalid") ); } if (xxlJobGroup.getTitle()==null || xxlJobGroup.getTitle().trim().length()==0) { return new ReturnT<String>(500, (I18nUtil.getString("system_please_input") + I18nUtil.getString("jobgroup_field_title")) ); } if (xxlJobGroup.getTitle().contains(">") || xxlJobGroup.getTitle().contains("<")) { return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_title")+I18nUtil.getString("system_unvalid") ); } if (xxlJobGroup.getAddressType()!=0) { if (xxlJobGroup.getAddressList()==null || xxlJobGroup.getAddressList().trim().length()==0) { return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_addressType_limit") ); } if (xxlJobGroup.getAddressList().contains(">") || xxlJobGroup.getAddressList().contains("<")) { return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_registryList")+I18nUtil.getString("system_unvalid") ); } String[] addresss = xxlJobGroup.getAddressList().split(","); for (String item: addresss) { if (item==null || item.trim().length()==0) { return new ReturnT<String>(500, I18nUtil.getString("jobgroup_field_registryList_unvalid") ); } } } // process xxlJobGroup.setUpdateTime(new Date()); int ret = xxlJobGroupDao.save(xxlJobGroup); return (ret>0)?ReturnT.SUCCESS:ReturnT.FAIL; }
2. Initialization - automatic registration
Assign user-defined parameters to the XxlJobSpringExecutor.
Put the method annotated with @ XxlJob into a concurrent map (jobHandlerRepository).
● initialize jobHandler from applicationContext
● get the set defined by the bean according to the applicationContext, and then traverse it.
● call getBean() to get the bean. Find the methods of all @ xxljobs through the MethodIntrospector reflection.
● traverse the method and register jobHandler.
Start a netty service, register address+port with the dispatching center and save it to the table.
XxlJobSpringExecutor
public class XxlJobSpringExecutor extends XxlJobExecutor implements ApplicationContextAware, SmartInitializingSingleton, DisposableBean { public void afterSingletonsInstantiated() { //Initialize jobhandler this.initJobHandlerMethodRepository(applicationContext); GlueFactory.refreshInstance(1); try { super.start(); } catch (Exception var2) { throw new RuntimeException(var2); } } private void initJobHandlerMethodRepository(ApplicationContext applicationContext) { if (applicationContext != null) { String[] beanDefinitionNames = applicationContext.getBeanNamesForType(Object.class, false, true); String[] var3 = beanDefinitionNames; int var4 = beanDefinitionNames.length; for(int var5 = 0; var5 < var4; ++var5) { String beanDefinitionName = var3[var5]; Object bean = applicationContext.getBean(beanDefinitionName); Map annotatedMethods = null; try { annotatedMethods = MethodIntrospector.selectMethods(bean.getClass(), new MetadataLookup<XxlJob>() { public XxlJob inspect(Method method) { return (XxlJob)AnnotatedElementUtils.findMergedAnnotation(method, XxlJob.class); } }); } catch (Throwable var19) { logger.error("xxl-job method-jobhandler resolve error for bean[" + beanDefinitionName + "].", var19); } if (annotatedMethods != null && !annotatedMethods.isEmpty()) { Iterator var9 = annotatedMethods.entrySet().iterator(); while(var9.hasNext()) { Entry<Method, XxlJob> methodXxlJobEntry = (Entry)var9.next(); Method executeMethod = (Method)methodXxlJobEntry.getKey(); XxlJob xxlJob = (XxlJob)methodXxlJobEntry.getValue(); if (xxlJob != null) { String name = xxlJob.value(); if (name.trim().length() == 0) { throw new RuntimeException("xxl-job method-jobhandler name invalid, for[" + bean.getClass() + "#" + executeMethod.getName() + "] ."); } if (loadJobHandler(name) != null) { throw new RuntimeException("xxl-job jobhandler[" + name + "] naming conflicts."); } executeMethod.setAccessible(true); Method initMethod = null; Method destroyMethod = null; if (xxlJob.init().trim().length() > 0) { try { initMethod = bean.getClass().getDeclaredMethod(xxlJob.init()); initMethod.setAccessible(true); } catch (NoSuchMethodException var18) { throw new RuntimeException("xxl-job method-jobhandler initMethod invalid, for[" + bean.getClass() + "#" + executeMethod.getName() + "] ."); } } if (xxlJob.destroy().trim().length() > 0) { try { destroyMethod = bean.getClass().getDeclaredMethod(xxlJob.destroy()); destroyMethod.setAccessible(true); } catch (NoSuchMethodException var17) { throw new RuntimeException("xxl-job method-jobhandler destroyMethod invalid, for[" + bean.getClass() + "#" + executeMethod.getName() + "] ."); } } registJobHandler(name, new MethodJobHandler(bean, executeMethod, initMethod, destroyMethod)); } } } } } } }
XxlJobExecutor
public class XxlJobExecutor { //Register jobHandler public static IJobHandler registJobHandler(String name, IJobHandler jobHandler){ logger.info(">>>>>>>>>>> xxl-job register jobhandler success, name:{}, jobHandler:{}", name, jobHandler); return (IJobHandler)jobHandlerRepository.put(name, jobHandler); } public void start() throws Exception { //Initialize log path XxlJobFileAppender.initLogPath(this.logPath); //Initialize admin address this.initAdminBizList(this.adminAddresses, this.accessToken); //Start the task log file cleanup thread JobLogFileCleanThread.getInstance().start((long)this.logRetentionDays); //Start task scheduling callback thread TriggerCallbackThread.getInstance().start(); //Initialize built-in server this.initEmbedServer(this.address, this.ip, this.port, this.appname, this.accessToken); } private void initEmbedServer(String address, String ip, int port, String appname, String accessToken) throws Exception { port = port > 0 ? port : NetUtil.findAvailablePort(9999); ip = ip != null && ip.trim().length() > 0 ? ip : IpUtil.getIp(); if (address == null || address.trim().length() == 0) { String ip_port_address = IpUtil.getIpPort(ip, port); address = "http://{ip_port}/".replace("{ip_port}", ip_port_address); } if (accessToken == null || accessToken.trim().length() == 0) { logger.warn(">>>>>>>>>>> xxl-job accessToken is empty. To ensure system security, please set the accessToken."); } this.embedServer = new EmbedServer(); this.embedServer.start(address, port, appname, accessToken); } }
EmbedServer
public class EmbedServer { public void start(final String address, final int port, final String appname, final String accessToken) { this.executorBiz = new ExecutorBizImpl(); this.thread = new Thread(new Runnable() { public void run() { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); final ThreadPoolExecutor bizThreadPool = new ThreadPoolExecutor(0, 200, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue(2000), new ThreadFactory() { public Thread newThread(Runnable r) { return new Thread(r, "xxl-rpc, EmbedServer bizThreadPool-" + r.hashCode()); } }, new RejectedExecutionHandler() { public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { throw new RuntimeException("xxl-job, EmbedServer bizThreadPool is EXHAUSTED!"); } }); try { ServerBootstrap bootstrap = new ServerBootstrap(); ((ServerBootstrap)bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)).childHandler(new ChannelInitializer<SocketChannel>() { public void initChannel(SocketChannel channel) throws Exception { channel.pipeline().addLast(new ChannelHandler[]{new IdleStateHandler(0L, 0L, 90L, TimeUnit.SECONDS)}).addLast(new ChannelHandler[]{new HttpServerCodec()}).addLast(new ChannelHandler[]{new HttpObjectAggregator(5242880)}).addLast(new ChannelHandler[]{new EmbedServer.EmbedHttpServerHandler(EmbedServer.this.executorBiz, accessToken, bizThreadPool)}); } }).childOption(ChannelOption.SO_KEEPALIVE, true); ChannelFuture future = bootstrap.bind(port).sync(); EmbedServer.logger.info(">>>>>>>>>>> xxl-job remoting server start success, nettype = {}, port = {}", EmbedServer.class, port); EmbedServer.this.startRegistry(appname, address); future.channel().closeFuture().sync(); } catch (InterruptedException var14) { if (var14 instanceof InterruptedException) { EmbedServer.logger.info(">>>>>>>>>>> xxl-job remoting server stop."); } else { EmbedServer.logger.error(">>>>>>>>>>> xxl-job remoting server error.", var14); } } finally { try { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } catch (Exception var13) { EmbedServer.logger.error(var13.getMessage(), var13); } } } }); this.thread.setDaemon(true); this.thread.start(); } }
3. Create task - dispatch center background
The / jobinfo/add method will be called. After basic verification, the executor information will be saved to table xxl_job_info.
The specific location of the source code is in the JobInfoController of XXL job admin.
@Override public ReturnT<String> add(XxlJobInfo jobInfo) { // Verify the information of the actuator XxlJobGroup group = xxlJobGroupDao.load(jobInfo.getJobGroup()); if (group == null) { return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_choose")+I18nUtil.getString("jobinfo_field_jobgroup")) ); } if (jobInfo.getJobDesc()==null || jobInfo.getJobDesc().trim().length()==0) { return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_input")+I18nUtil.getString("jobinfo_field_jobdesc")) ); } if (jobInfo.getAuthor()==null || jobInfo.getAuthor().trim().length()==0) { return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_input")+I18nUtil.getString("jobinfo_field_author")) ); } // Verification scheduling type: cron, fixed rate ScheduleTypeEnum scheduleTypeEnum = ScheduleTypeEnum.match(jobInfo.getScheduleType(), null); if (scheduleTypeEnum == null) { return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) ); } if (scheduleTypeEnum == ScheduleTypeEnum.CRON) { if (jobInfo.getScheduleConf()==null || !CronExpression.isValidExpression(jobInfo.getScheduleConf())) { return new ReturnT<String>(ReturnT.FAIL_CODE, "Cron"+I18nUtil.getString("system_unvalid")); } } else if (scheduleTypeEnum == ScheduleTypeEnum.FIX_RATE/* || scheduleTypeEnum == ScheduleTypeEnum.FIX_DELAY*/) { if (jobInfo.getScheduleConf() == null) { return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")) ); } try { int fixSecond = Integer.valueOf(jobInfo.getScheduleConf()); if (fixSecond < 1) { return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) ); } } catch (Exception e) { return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("schedule_type")+I18nUtil.getString("system_unvalid")) ); } } // Verification task information if (GlueTypeEnum.match(jobInfo.getGlueType()) == null) { return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_gluetype")+I18nUtil.getString("system_unvalid")) ); } if (GlueTypeEnum.BEAN==GlueTypeEnum.match(jobInfo.getGlueType()) && (jobInfo.getExecutorHandler()==null || jobInfo.getExecutorHandler().trim().length()==0) ) { return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("system_please_input")+"JobHandler") ); } // >fix "\r" in shell if (GlueTypeEnum.GLUE_SHELL==GlueTypeEnum.match(jobInfo.getGlueType()) && jobInfo.getGlueSource()!=null) { jobInfo.setGlueSource(jobInfo.getGlueSource().replaceAll("\r", "")); } // Verify advanced policies: actuator routing policy, scheduling expiration policy and blocking policy if (ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null) == null) { return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_executorRouteStrategy")+I18nUtil.getString("system_unvalid")) ); } if (MisfireStrategyEnum.match(jobInfo.getMisfireStrategy(), null) == null) { return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("misfire_strategy")+I18nUtil.getString("system_unvalid")) ); } if (ExecutorBlockStrategyEnum.match(jobInfo.getExecutorBlockStrategy(), null) == null) { return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_executorBlockStrategy")+I18nUtil.getString("system_unvalid")) ); } // Verify subtask information if (jobInfo.getChildJobId()!=null && jobInfo.getChildJobId().trim().length()>0) { String[] childJobIds = jobInfo.getChildJobId().split(","); for (String childJobIdItem: childJobIds) { if (childJobIdItem!=null && childJobIdItem.trim().length()>0 && isNumeric(childJobIdItem)) { XxlJobInfo childJobInfo = xxlJobInfoDao.loadById(Integer.parseInt(childJobIdItem)); if (childJobInfo==null) { return new ReturnT<String>(ReturnT.FAIL_CODE, MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId")+"({0})"+I18nUtil.getString("system_not_found")), childJobIdItem)); } } else { return new ReturnT<String>(ReturnT.FAIL_CODE, MessageFormat.format((I18nUtil.getString("jobinfo_field_childJobId")+"({0})"+I18nUtil.getString("system_unvalid")), childJobIdItem)); } } // join , avoid "xxx,," String temp = ""; for (String item:childJobIds) { temp += item + ","; } temp = temp.substring(0, temp.length()-1); jobInfo.setChildJobId(temp); } // Task information warehousing jobInfo.setAddTime(new Date()); jobInfo.setUpdateTime(new Date()); jobInfo.setGlueUpdatetime(new Date()); xxlJobInfoDao.save(jobInfo); if (jobInfo.getId() < 1) { return new ReturnT<String>(ReturnT.FAIL_CODE, (I18nUtil.getString("jobinfo_field_add")+I18nUtil.getString("system_fail")) ); } return new ReturnT<String>(String.valueOf(jobInfo.getId())); }
4. Manually trigger task scheduling
/The jobinfo/trigger entry is in the JobInfoController
@RequestMapping("/trigger") @ResponseBody public ReturnT<String> triggerJob(int id, String executorParam, String addressList) { // force cover job param if (executorParam == null) { executorParam = ""; } JobTriggerPoolHelper.trigger(id, TriggerTypeEnum.MANUAL, -1, null, executorParam, addressList); return ReturnT.SUCCESS; }
The core processing logic is in JobTriggerPoolHelper.
public static void trigger(int jobId, TriggerTypeEnum triggerType, int failRetryCount, String executorShardingParam, String executorParam, String addressList) { helper.addTrigger(jobId, triggerType, failRetryCount, executorShardingParam, executorParam, addressList); } //Add scheduling task public void addTrigger(final int jobId, final TriggerTypeEnum triggerType, final int failRetryCount, final String executorShardingParam, final String executorParam, final String addressList) { //Select the thread pool. The default is fastTriggerPool. If the timeout times of the task is more than 10, select slowTriggerPool ThreadPoolExecutor triggerPool_ = fastTriggerPool; AtomicInteger jobTimeoutCount = jobTimeoutCountMap.get(jobId); 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 { // do trigger XxlJobTrigger.trigger(jobId, triggerType, failRetryCount, executorShardingParam, executorParam, addressList); } catch (Exception e) { logger.error(e.getMessage(), e); } finally { // check timeout-count-map long minTim_now = System.currentTimeMillis()/60000; if (minTim != minTim_now) { minTim = minTim_now; jobTimeoutCountMap.clear(); } // incr timeout-count-map long cost = System.currentTimeMillis()-start; if (cost > 500) { // ob-timeout threshold 500ms AtomicInteger timeoutCount = jobTimeoutCountMap.putIfAbsent(jobId, new AtomicInteger(1)); if (timeoutCount != null) { timeoutCount.incrementAndGet(); } } } } }); }
XxlJobTrigger
public static void trigger(int jobId, TriggerTypeEnum triggerType, int failRetryCount, String executorShardingParam, String executorParam, String addressList) { //Get the task information from the database, corresponding to table xxl_job_info 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(); //Extract the actuator information from the database, corresponding to the table xxl_job_group XxlJobGroup group = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().load(jobInfo.getJobGroup()); // cover addressList if (addressList!=null && addressList.trim().length()>0) { group.setAddressType(1); group.setAddressList(addressList.trim()); } // sharding param int[] shardingParam = null; if (executorShardingParam!=null){ String[] shardingArr = executorShardingParam.split("/"); if (shardingArr.length==2 && isNumeric(shardingArr[0]) && isNumeric(shardingArr[1])) { shardingParam = new int[2]; shardingParam[0] = Integer.valueOf(shardingArr[0]); shardingParam[1] = Integer.valueOf(shardingArr[1]); } } //All broadcast instances need to be traversed. If all broadcast instances need to be traversed if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST==ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null) && group.getRegistryList()!=null && !group.getRegistryList().isEmpty() && 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]); } } //Final execution method private static void processTrigger(XxlJobGroup group, XxlJobInfo jobInfo, int finalFailRetryCount, TriggerTypeEnum triggerType, int index, int total){ // param 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; // 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.setLogDateTime(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<String> routeAddressResult = null; if (group.getRegistryList()!=null && !group.getRegistryList().isEmpty()) { if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST == executorRouteStrategyEnum) { if (index < group.getRegistryList().size()) { address = group.getRegistryList().get(index); } else { address = group.getRegistryList().get(0); } } else { routeAddressResult = executorRouteStrategyEnum.getRouter().route(triggerParam, group.getRegistryList()); if (routeAddressResult.getCode() == ReturnT.SUCCESS_CODE) { address = routeAddressResult.getContent(); } } } else { routeAddressResult = new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString("jobconf_trigger_address_empty")); } // 4,trigger remote executor ReturnT<String> triggerResult = null; if (address != null) { triggerResult = runExecutor(triggerParam, address); } else { triggerResult = new ReturnT<String>(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("<br>").append(I18nUtil.getString("jobconf_trigger_admin_adress")).append(": ").append(IpUtil.getIp()); triggerMsgSb.append("<br>").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("<br>").append(I18nUtil.getString("jobconf_trigger_exe_regaddress")).append(": ").append(group.getRegistryList()); triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_executorRouteStrategy")).append(": ").append(executorRouteStrategyEnum.getTitle()); if (shardingParam != null) { triggerMsgSb.append("("+shardingParam+")"); } triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_executorBlockStrategy")).append(": ").append(blockStrategy.getTitle()); triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_timeout")).append(": ").append(jobInfo.getExecutorTimeout()); triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_executorFailRetryCount")).append(": ").append(finalFailRetryCount); triggerMsgSb.append("<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>"+ I18nUtil.getString("jobconf_trigger_run") +"<<<<<<<<<<< </span><br>") .append((routeAddressResult!=null&&routeAddressResult.getMsg()!=null)?routeAddressResult.getMsg()+"<br><br>":"").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); logger.debug(">>>>>>>>>>> xxl-job trigger end, jobId:{}", jobLog.getId()); }
Taking the manual execution of tasks as an example, the source code process of task execution is introduced. In addition, there are other task trigger types.
public enum TriggerTypeEnum { MANUAL(I18nUtil.getString("jobconf_trigger_type_manual")), CRON(I18nUtil.getString("jobconf_trigger_type_cron")), RETRY(I18nUtil.getString("jobconf_trigger_type_retry")), PARENT(I18nUtil.getString("jobconf_trigger_type_parent")), API(I18nUtil.getString("jobconf_trigger_type_api")), MISFIRE(I18nUtil.getString("jobconf_trigger_type_misfire")); private TriggerTypeEnum(String title){ this.title = title; } private String title; public String getTitle() { return title; } }
jobconf_trigger_type_cron=Cron trigger
jobconf_trigger_type_manual = manual trigger
jobconf_trigger_type_parent = triggered by parent task
jobconf_trigger_type_api=API trigger
jobconf_trigger_type_retry = failed to retry triggering
jobconf_trigger_type_misfire = scheduling overdue compensation
5. cron triggered task scheduling
The commonly used cron trigger is in JobScheduleHelper.
public void start(){ // schedule thread scheduleThread = new Thread(new Runnable() { @Override public void run() { try { TimeUnit.MILLISECONDS.sleep(5000 - System.currentTimeMillis()%1000 ); } catch (InterruptedException e) { if (!scheduleThreadToStop) { logger.error(e.getMessage(), e); } } logger.info(">>>>>>>>> init xxl-job admin scheduler success."); // pre-read count: treadpool-size * trigger-qps (each trigger cost 50ms, qps = 1000/50 = 20) int preReadCount = (XxlJobAdminConfig.getAdminConfig().getTriggerPoolFastMax() + XxlJobAdminConfig.getAdminConfig().getTriggerPoolSlowMax()) * 20; while (!scheduleThreadToStop) { // Scan Job long start = System.currentTimeMillis(); Connection conn = null; Boolean connAutoCommit = null; PreparedStatement preparedStatement = null; boolean preReadSuc = true; try { conn = XxlJobAdminConfig.getAdminConfig().getDataSource().getConnection(); connAutoCommit = conn.getAutoCommit(); conn.setAutoCommit(false); preparedStatement = conn.prepareStatement( "select * from xxl_job_lock where lock_name = 'schedule_lock' for update" ); preparedStatement.execute(); // tx start // 1,pre read long nowTime = System.currentTimeMillis(); List<XxlJobInfo> scheduleList = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleJobQuery(nowTime + PRE_READ_MS, preReadCount); if (scheduleList!=null && scheduleList.size()>0) { // 2,push time-ring for (XxlJobInfo jobInfo: scheduleList) { // time-ring jump if (nowTime > jobInfo.getTriggerNextTime() + PRE_READ_MS) { // 2.1,trigger-expire > 5s: pass && make next-trigger-time logger.warn(">>>>>>>>>>> xxl-job, schedule misfire, jobId = " + jobInfo.getId()); // 1,misfire match MisfireStrategyEnum misfireStrategyEnum = MisfireStrategyEnum.match(jobInfo.getMisfireStrategy(), MisfireStrategyEnum.DO_NOTHING); if (MisfireStrategyEnum.FIRE_ONCE_NOW == misfireStrategyEnum) { // FIRE_ONCE_NOW > trigger JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.MISFIRE, -1, null, null, null); logger.debug(">>>>>>>>>>> xxl-job, schedule push trigger : jobId = " + jobInfo.getId() ); } // 2,fresh next refreshNextValidTime(jobInfo, new Date()); } else if (nowTime > jobInfo.getTriggerNextTime()) { // 2.2,trigger-expire < 5s: direct-trigger && make next-trigger-time // 1,trigger JobTriggerPoolHelper.trigger(jobInfo.getId(), TriggerTypeEnum.CRON, -1, null, null, null); logger.debug(">>>>>>>>>>> xxl-job, schedule push trigger : jobId = " + jobInfo.getId() ); // 2,fresh next refreshNextValidTime(jobInfo, new Date()); // next-trigger-time in 5s, pre-read again if (jobInfo.getTriggerStatus()==1 && nowTime + PRE_READ_MS > jobInfo.getTriggerNextTime()) { // 1,make ring second int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60); // 2,push time ring pushTimeRing(ringSecond, jobInfo.getId()); // 3,fresh next refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime())); } } else { // 2.3,trigger-pre-read: time-ring trigger && make next-trigger-time // 1,make ring second int ringSecond = (int)((jobInfo.getTriggerNextTime()/1000)%60); // 2,push time ring pushTimeRing(ringSecond, jobInfo.getId()); // 3,fresh next refreshNextValidTime(jobInfo, new Date(jobInfo.getTriggerNextTime())); } } // 3,update trigger info for (XxlJobInfo jobInfo: scheduleList) { XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().scheduleUpdate(jobInfo); } } else { preReadSuc = false; } // tx stop } catch (Exception e) { if (!scheduleThreadToStop) { logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread error:{}", e); } } finally { // commit if (conn != null) { try { conn.commit(); } catch (SQLException e) { if (!scheduleThreadToStop) { logger.error(e.getMessage(), e); } } try { conn.setAutoCommit(connAutoCommit); } catch (SQLException e) { if (!scheduleThreadToStop) { logger.error(e.getMessage(), e); } } try { conn.close(); } catch (SQLException e) { if (!scheduleThreadToStop) { logger.error(e.getMessage(), e); } } } // close PreparedStatement if (null != preparedStatement) { try { preparedStatement.close(); } catch (SQLException e) { if (!scheduleThreadToStop) { logger.error(e.getMessage(), e); } } } } long cost = System.currentTimeMillis()-start; // Wait seconds, align second if (cost < 1000) { // scan-overtime, not wait try { // pre-read period: success > scan each second; fail > skip this period; TimeUnit.MILLISECONDS.sleep((preReadSuc?1000:PRE_READ_MS) - System.currentTimeMillis()%1000); } catch (InterruptedException e) { if (!scheduleThreadToStop) { logger.error(e.getMessage(), e); } } } } logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#scheduleThread stop"); } }); scheduleThread.setDaemon(true); scheduleThread.setName("xxl-job, admin JobScheduleHelper#scheduleThread"); scheduleThread.start(); // ring thread ringThread = new Thread(new Runnable() { @Override public void run() { while (!ringThreadToStop) { // align second try { TimeUnit.MILLISECONDS.sleep(1000 - System.currentTimeMillis() % 1000); } catch (InterruptedException e) { if (!ringThreadToStop) { logger.error(e.getMessage(), e); } } try { // second data List<Integer> ringItemData = new ArrayList<>(); int nowSecond = Calendar.getInstance().get(Calendar.SECOND); // Avoid taking too long to process. Cross the scale and check one scale forward; for (int i = 0; i < 2; i++) { List<Integer> tmpData = ringData.remove( (nowSecond+60-i)%60 ); if (tmpData != null) { ringItemData.addAll(tmpData); } } // ring trigger logger.debug(">>>>>>>>>>> xxl-job, time-ring beat : " + nowSecond + " = " + Arrays.asList(ringItemData) ); if (ringItemData.size() > 0) { // do trigger for (int jobId: ringItemData) { // do trigger JobTriggerPoolHelper.trigger(jobId, TriggerTypeEnum.CRON, -1, null, null, null); } // clear ringItemData.clear(); } } catch (Exception e) { if (!ringThreadToStop) { logger.error(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread error:{}", e); } } } logger.info(">>>>>>>>>>> xxl-job, JobScheduleHelper#ringThread stop"); } }); ringThread.setDaemon(true); ringThread.setName("xxl-job, admin JobScheduleHelper#ringThread"); ringThread.start(); }
Summary
1. Integrate mysql data. Task execution and scheduling are decoupled, and various types of configuration parameters are provided to meet the task scheduling requirements under distributed environment.
2. Use of netty.
3. Flexible use of high parallel threads and thread pools.