demo搭建:
1、下载xxl-job调度中心代码:
可从这两个地址下载:
https://github.com/xuxueli/xxl-job
https://gitee.com/xuxueli0323/xxl-job
2、执行脚本:
下载后解压用idea打开,目录 doc/db 下面有个 tables_xxl_job.sql 脚本,是xxl-job调度中心用到的一些表,拖到数据库中执行即可
3、修改调度中的的配置文件:
application.property,修改数据库链接即可:
4、启动调度中心
启动xxl-job-admin,默认访问地址是:http://localhost:8080/xxl-job-admin,admin/123456
5、搭建执行器:
我新建的spring boot 项目,需要添加xxl-job的配置如下,其中xxl.job.accessToken 的值要和调度中心一样
引入 xxl-job 的jar包:
添加执行配置器:
添加执行器代码,使用 @XxlJob 注解
6、启动执行器并在调度中心中配置执行器:
启动执行器,此时执行器在 xxl-job 中还没有配置,所以执行器不会执行
打开调度中心控制台,配置任务:
JobHandler 中填写 执行器方法 @XxlJob注解的值
路由策略 中可以选择负载均衡策略(适用集群部署的执行器)
填写完后 保存,此时可以看到新建的任务,可以对任务进行启动\停止等操作:
7、计划任务的分片:
如果定时任务需要处理的数据量比较大,一个服务器处理时间长,此时可以使用分片策略
分片策略是给每个执行器节点都发起请求,执行器可以获取到总分片数和当前分片数,根据这两个参数进行任务的派分,如有1000条数据需要处理,用1000除以总分片数得到每台服务器处理的数量,然后根据当前分片数获取要处理的数据
将路由策略改成分片广播:
执行器获得总分片数和当前分片:
执行器管理中可以看到一个任务有哪些执行器:执行器启动后向调度中心发送心跳,调度中心的执行器管理中就可以看到了;
xxl-job的源码解读:
1、调度中心源码:
从xxl-job-admin的XxlJobAdminConfig进入,从后置处理器(后置处理器是在bean创建后执行的方法)afterPropertiesSet的init()进入,源码逻辑如下:
/**
* 创建两个线程池:fastTriggerPool,slowTriggerPool
* 如果一分钟内一个任务有10次执行时间超过0.5秒,则使用 是slowTriggerPool
* 这两个线程池都是用来调用执行器的,其中 fastTriggerPool的队列中 最大任务数量和最大线程数量 是slowTriggerPool 的两倍
* 执行器的调用是异步调用,通过下面的线程池 callbackThreadPool 回调
*/
JobTriggerPoolHelper.toStart();
/**
* 1、创建线程池 registryOrRemoveThreadPool,用于服务器注册、服务器移除
* 以下每30秒执行一次(心跳时间间隔)
* 2、移除掉 90 秒没有收到心跳的执行器,
* 3、将90秒内收到心跳的执行器 地址更新到 xxl_job_group.address_list
*/
JobRegistryHelper.getInstance().start();
/**
* 每10秒执行一次
* 查询 xxl_job_log 中处理失败的日志,如果剩余重试次数大于0,则执行一次任务
* 对执行失败的任务,进行告警(邮件通知),告警之后更新 xxl_job_log.alarm_status 告警状态
*/
JobFailMonitorHelper.getInstance().start();
/**
* 1、创建 线程池 callbackThreadPool,用于执行器执行任务之后回调
* 2、调度记录停留在 "运行中" 状态超过10min,且对应执行器心跳注册失败不在线,则将本地调度主动标记失败, 更新 xxl_job_log.handle_time、handle_code、handle_msg(每60秒执行一次)
* 以下是回调源码,使用线程池 callbackThreadPool 执行
*/
JobCompleteHelper.getInstance().start();
/**
* 每分钟执行一次
* 将任务执行的总数、成功数、失败数 写入到 xxl_job_log_report
* 根据配置的日志保留时间 删除 xxl_job_log
*/
JobLogReportHelper.getInstance().start();
/**
* 以下步骤调用执行器,每秒执行一次(线程最后睡眠一秒)
* 首先获得分布式锁:
* select * from xxl_job_lock where lock_name = 'schedule_lock' for update
* 1、查询 xxl_job_info 任务信息: 下次实行时间(trigger_next_time<当前时间+5秒)、执行器 trigger_status 状态为 有效,也就是任务管理配置的 running 状态、限制查询 6000 条数据
* (1)、当前时间大于 下次执行时间加5秒,如果任务 调度过期策略是执行一次,则执行一次任务(写入xxl_job_log 失败剩余次数改为默认次数),刷新下一次执行时间
* (2)、当前时间减去 下一次执行时间小于5秒,则执行任务 并 执行任务后刷新下一次执行时间,刷新后当前时间距离下一次执行时间 小于 5秒,将任务id放在 ringData 中
* (3)、当前时间小于下次执行时间,将要执行数据放到 ringData 中并刷新 下次执行时间
*/
JobScheduleHelper.getInstance().start();
下图为调用执行器的代码:executorHandler 的值是执行器方法上面 @XxlJob 的值
2、执行器源码解读:
进入到XxlJobConfig中创建的bean(XxlJobSpringExecutor),进入方法afterSingletonsInstantiated(这个方法是所有的bean都创建完成后执行):
(1)、initJobHandlerMethodRepository 找到使用xxl-job注解的方法,将方法放入 jobHandlerRepository
(2)、super.start() 方法逻辑,主要是最后一步比较复杂,使用netty了,从channel中获得调度中心的的请求放入triggerQueue中,然后使用一个线程从triggerQueue取出handler来执行业务方法,根据调度中心传过来的executorHandler 从 jobHandlerRepository 中获得执行的方法
// 初始化日志目录
XxlJobFileAppender.initLogPath(logPath);
// 初始化AdminBiz(注册中心地址、access_token)
initAdminBizList(adminAddresses, accessToken);
/*
* 清除日志文件,logRetentionDays 小于3 时不执行清除逻辑,每天执行一次
*/
JobLogFileCleanThread.getInstance().start(logRetentionDays);
// 执行器执行任务后回调调度中心,记录日志
TriggerCallbackThread.getInstance().start();
/**
* 执行器注册或发送心跳,30秒一次,调用注册中心 api/registry
* 这一步使用netty实现,指定线程池 bizThreadPool 来接收注册中心的调度请求,(EmbedHttpServerHandler.channelRead0.process)从jobHandlerRepository通过jobId获取handler,如果handler不为空则启动线程JobThread,然后将请求放入到 triggerQueue 中,并返回注册中心
* JobThread线程从triggerQueue获取handler,依次执行里面的任务,执行任务后将执行结果放入callBackQueue队列中
* TriggerCallbackThread 从callBackQueue 中取出执行结果,回调调度中心(api/callback)
*/
initEmbedServer(address, ip, port, appname, accessToken);
执行结果放入callBackQueue队列:
下面是下载的xxl-job调度中心代码及 执行器demo:
链接:https://pan.baidu.com/s/1iWCscUvWoVeHmRaj-6OpZg?pwd=44hp