本文主要向大家介绍一下xxljob在调度任务时执行了哪些操作,这也是xxljob最核心的功能
xxljob是如何触发任务的,首先我们先了解一下xxljob的表结构
xxl_job_info 记录的是各个具体job的信息 是xxljob中最重要的表 这张表记录的job 的调度类型,调度时机,路由策略,阻塞策略等信息
CREATE TABLE `xxl_job_info` ( `id` int(11) NOT NULL AUTO_INCREMENT, `job_group` int(11) NOT NULL COMMENT '执行器主键ID', `job_desc` varchar(255) NOT NULL, `add_time` datetime DEFAULT NULL, `update_time` datetime DEFAULT NULL, `author` varchar(64) DEFAULT NULL COMMENT '作者', `alarm_email` varchar(255) DEFAULT NULL COMMENT '报警邮件', `schedule_type` varchar(50) NOT NULL DEFAULT 'NONE' COMMENT '调度类型', `schedule_conf` varchar(128) DEFAULT NULL COMMENT '调度配置,值含义取决于调度类型', `misfire_strategy` varchar(50) NOT NULL DEFAULT 'DO_NOTHING' COMMENT '调度过期策略', `executor_route_strategy` varchar(50) DEFAULT NULL COMMENT '执行器路由策略', `executor_handler` varchar(255) DEFAULT NULL COMMENT '执行器任务handler', `executor_param` varchar(512) DEFAULT NULL COMMENT '执行器任务参数', `executor_block_strategy` varchar(50) DEFAULT NULL COMMENT '阻塞处理策略', `executor_timeout` int(11) NOT NULL DEFAULT '0' COMMENT '任务执行超时时间,单位秒', `executor_fail_retry_count` int(11) NOT NULL DEFAULT '0' COMMENT '失败重试次数', `glue_type` varchar(50) NOT NULL COMMENT 'GLUE类型', `glue_source` mediumtext COMMENT 'GLUE源代码', `glue_remark` varchar(128) DEFAULT NULL COMMENT 'GLUE备注', `glue_updatetime` datetime DEFAULT NULL COMMENT 'GLUE更新时间', `child_jobid` varchar(255) DEFAULT NULL COMMENT '子任务ID,多个逗号分隔', `trigger_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '调度状态:0-停止,1-运行', `trigger_last_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '上次调度时间', `trigger_next_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '下次调度时间', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
xxl_job_log 这张表记录了job执行时的一些参数 包括了执行次数 执行参数 jobid, 调度的时机和结果 xxl_job_log_report 记录了调度中心的统计数据 xxl_job_logglue记录了glue类型job的日志
xxl_job_registry 记录了在调度中心注册的执行器信息 xxl_job_group 记录了调度中心的分组情况 xxl_job_user 记录了用户信息 xxl_job_lock记录了锁信息 其实我们通过表的设计就以了解一些xxljob的设计思想,下面我们就来看一下一个任务是如何被调度的
图片
大家可以看到上图是一个简要的job执行的流程图 在了解代码前和大家简要的介绍一下XXLJOB的通信结构,调度中心和执行器各维护一个netty的服务,双方使用HTTP通信,执行器的ip和端口在启动时会通过http调用通知调度中心,各位在启动服务时是可以看到XXLJOB对应的日志的,在看XXLJOB的代码时大家会看到一些以client或impl结尾的实现,对应的就是调度中心和执行器的netty的实现。
图片
下面笔者带大家深入了解一下具体的代码 在执行器端需配置一些参数,从而时执行器能够找到调度中心
图片
包括了调度中心的地址,执行器的ip,端口(可以不填),日志的过期时间(非必填)
XxlJobSpringExecutor#afterSingletonsInstantiated()->super.start()->initEmbedServer();->embedServer.start();->startRegistry();->ExecutorRegistryThread.getInstance().start()
最终启动一个名为registryThread的线程,在这个线程中调用 adminBiz.registry()方法,调用调度中心的接口实现注册 此时调度中心与执行器(客户端)就建立起了连接 此外在spring的bean初始化完成后 在XxlJobAdminConfig这个类中还利用afterPropertiesSet实现了初始化了一些线程
图片
这些线程的作用分别为处理国际化对应的语言,快慢线程池的初始化,服务注册监控,任务执行监控,任务完成监控,日志报表处理 ,任务执行,限于篇幅,这些线程的作用我们下期再详细的解释,到现在为止,XXLJOB的启动就已经完成了。
图片
在调度中心的界面我们可以新建一个调度任务,其中有几个参数需要注意,jobhandler,运行模式,阻塞策略,cron。由于目前大部分是在springboot中运行的,所以大部分是依赖Bean执行的,jobHandler则是代码中定义的job名称。运行模式决定了调度中心选择执行器的逻辑
图片
阻塞策略决定了连续执行时的策略
图片
cron则决定了调度的时间
下面我们深入了解一下一个job是如果被调度中心执行的。执行的逻辑在上文中提到的JobScheduleHelper中实现。首先会先获取锁,此处是利用mysql的for update实现的, 然后会获取5秒内的任务列表
图片
逐个遍历拿到的任务,如果任务已经超过了执行时间+预读时间(5s),根据过期策略决定是丢弃还是立即执行一次,并且更新下次执行时间 如果当前时间大于任务下次执行时间,则将任务放入执行线程池,等待执行,更新下次执行时间,如果下次执行时间在5s内,则会将任务放入时间轮中,等待其他线程处理,再此更新此任务执行时间如果均不是上述两种情况,则会将任务放入时间轮中,更新执行时间,等待其他线程处理。
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()));/ }}
具体执行任务的方法是 com.xxl.job.admin.core.trigger.XxlJobTrigger#trigger
在这个方法中会根据执行策略(ExecutorRouteStrategyEnum)选择合适的执行器,找到对应的执行器,利用netty的接口执行对应的任务
在本期文章中,向大家介绍了XXLJOB是如何启动和执行任务的,在下期文章中将向大家介绍XXLJOB其他的细节,由于一个定时任务系统的复杂,很多内容还是需要各位读者自己看一遍才能真正的理解,毕竟代码才是真正的老师。
本文链接:http://www.28at.com/showinfo-26-44391-0.html揭秘XXLJOB:任务调度和执行的全面指南
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com
上一篇: 探索Taro:跨平台开发的实践与原理