1、xxl-job-admin:调度中心,里面很多执行器
2、客户端:或者说叫任务服务,也即是我们自己写的定时任务,最终就是由调度中心的执行器来触发任务。
客户端像服务端注册后,会在服务端把客户端的信息存到这个表里面去。并且注册的是哪个执行器
registryGroup 这个是注册模式:是自动注册进来的还是,手动添加的。 待确认。
registryKey 这个是执行器name
registry_value 这个是客户端的ip地址
update_time 最近一次心跳检测时间
注意:最先注册进来的ID越小,就越排在最上面
执行器表,执行任务时会通过任务找到里面有个:
addressList字段 ,会有定时任务把客户端的ip通过逗号拼接的方式更新进来,而且会把过期的ip剔除。
注意这里是有顺序的。端口最小的排在最前面。
如果是ArrayList<String>排序的规则是ASCII对应的数字越小的在最前面(A<Z<a<z)
原理是从注册进来的Ip里面取,取完之后。调用Collections.sort对集合排序然后通过逗号拼接塞进去。注意有2个顺序一个ip逗号拼接的顺序,
一个是XxlJobRegistry表的顺序,这个顺序是最先注册进来的排在最上边。
在选择路由策略的时候,比如第一个机器执行,那么这个第一个机器是最先注册进来的还是哪个? 是通过sort排好序的那台。所有这里容易搞混了,
是取ip或者端口最靠前的那一台。
配置了要执行的Job,以及Job执行的时间表达式,以及最近一次执行时间,和上一次执行完的时间,各种状态。
//通过时间从表里面找出准备执行的任务
SELECT * FROM xxl_job_info AS t WHERE t.trigger_status = 1 and t.trigger_next_time <= #{maxNextTime}
ORDER BY id ASC LIMIT #{pagesize}
更新要执行任务的状态
UPDATE xxl_job_info SET
trigger_last_time = #{triggerLastTime}, trigger_next_time = #{triggerNextTime}, trigger_status = #{triggerStatus}
WHERE id = #{id}
执行任务时,会先保存JobLog日志,记录了【哪个执行器执行】的,【执行时间】,以及【任务的Id】
这个表会有monitorThread线程从里面取失败的任务日志,然后判断是否【重跑】、或者是发送告警邮件。
1、alarm_status: 报警状态,如果失败就要去报警,那么在处理报警时,需要提前通过状态来锁表。
告警状态:0-默认、-1=锁定状态、1-无需告警、2-告警成功、3-告警失败
2、executorFailRetryCount: 失败重试:只要这个值大于0,就会发起重试。
3、trigger_code:任务触发调度状态,200-成功、500失败
4、handle_code:表示任务的执行结果,500任务执行失败但是触发成功,200任务执行成功,0表示执行中。
handle_code = 0表示任务还在处理中,那么任务也一定是触发成功或者是触发中,也就是trigger_code=200 或者 trigger_code=0
handle_code = 200表示任务执行成功,那么任务一定是触发成功也就是trigger_code=200
失败有几种情况,一个是拿到结果被标记处失败,一个是没有拿到结果,但是超过10分钟还没有结果的标记成失败。
trigger_time 表示什么? 开始调度任务时,塞的当前时间。
一共四个表,当启动调度中心服务后,客户端注册进来,先进【注册表】
客户端注册到调度中心时,需要绑定一个调度器,这个是客户端启动配置文件设置好的。然后进到了【调度器表】
当我们配置任务后,任务就会进到【任务表】,这里会记录调度策略,任务执行时间等等。
最后任务调度后,会通过任务调度日志记录,并且会记录调度状态。
1、AppName:就是触发我们任务的那个执行器,所以客户端要指定使用那个执行器,这样在触发任务的时候,执行器就可以找到,
对应的客户端IP地址,发送任务执行指令。
2、名称:
3、注册方式:分为下面2个钟
自动注册:启动时候时候注册进去
手动录入:手动填写
1、选择执行器,执行器和客户端的任务,建立绑定关系。 执行器+客户端Ip+具体任务
2、任务描述:邮件告警的时候会用到。
3、负责人:无实际作用
4、报警邮件:填写邮箱地址,多个逗号分隔,当任务失败时,失败一次发送一次邮件。
1、无:该类型不会主动触发调度;
2、CRON:该类型将会通过CRON,触发任务调度;
3、固定速度:该类型将会以固定速度,触发任务调度;按照固定的间隔时间,周期性触发;
4、固定延迟:该类型将会以固定延迟,触发任务调度;按照固定的延迟时间,从上次调度结束后开始计算延迟时间,到达延迟时间后触发下次调度;
问题:固定速度和固定延迟什么区别
一个是固定时间间隔,间隔是从任务开始执行时统计,固定延迟是任务结束时间开始统计。主要差异在任务开始时间到任务结束这个时间差。
Bean: Spring Bean
GLUE(Java):使用GLUE这种,就要在页面手动敲代码。
只有运行模式是Bean模式,才可以使用
就是每次任务执行时,传的固定参数。
问题:能写表达式吗?
不能适用表达式。如果是多个参数可以使用逗号拼接,在代码收到请求时切割
1、第一个
这里的第一个是,第一个注册进来的,还是通过ip排序后,ip最小或者最大的那一个? 不是最先注册进来的,是拿到所有客户端的所有的ip,
然后通过sort排序的第一个,也是ASCII码规则排序的第一台。addressList.get(0)
2、最后一个:和第一个的取数规则什么区别? 刚好和第一个相反 addressList.get(addressList.size()-1)
3、轮询:
选择一台执行,拿到当天执行的次数,在通过机器数取模,如果机器数不变化的情况下,比如固定5台,基本上每台机器执行的次数会差不多,
每天第一次执行的时候有点特殊,为了避免压力,所以在每个JobId在一开始会随机生成一个100以内的数,然后每次执行次数+1,
避免多个任务一开始都打在第一台机器上,这种方式类似于解决缓存雪崩一样。避免缓存同时失效流量瞬间打在DB上面。
4、忙碌转移路由:
5、随机:
6、hash一致性:
7、最不经常使用
8、最近最久未使用:和最不经常使用有什么区别?
9、故障转移: 按照顺序依次进行心跳检测,第一个心跳检测成功的机器选定为目标执行器并发起调度
10、忙碌转移: 按照顺序依次进行空闲检测,第一个空闲检测成功的机器选定为目标执行器并发起调度 (怎么判断空闲?)
11、分片广播:
每个任务都拥有一个唯一的任务ID(任务ID可以从任务列表获取),当本任务执行结束并且执行成功时,将会触发子任务ID所对应的任务的一次主动调度。
在执行任务时,发现当前触发时间,超过任务的执行时间,并且超过5秒了
1、忽略:什么都不做
2、立即执行: 就算超过五秒也执行一次,并且更新任务的下一个的执行时间。
单机串行
丢弃后续调度
覆盖之前调度
0和其他值有什么区别,如果是0,那就无限制,一直等待
主要是在这里会用:com.xxl.job.core.thread.JobThread#run执行任务,这里会判断超时时间是否大于0,
如果大于0就会使用FutureTask来执行任务,并且设置任务的超时时间,
如果没有设置超时时间,就会直接使用当前线程执行任务,一直执行
新增一个FutureTask,就相当于多定义了一个线程,是不是浪费了,不会在最后会使用中断逻辑方法来中断这个线程。
当任务执行一次时,会把任务的执行记录存到XxlJobLogDao表里,包括任务的重试次数,这个重试次数第一存的值,
就是JobInfo配的重试次数,当任务失败,发送失败邮件的时候,会判断Log表的这个重试次数的值,当他大于0的时候,
就会立即发起一次重试,然后并且在生成一条Log表记录,这时新生成的Log表的重试次数减去了1,
原先一开始的那条Log记录状态会变成其他状态,下次扫描记录,就不会扫到这条记录,也就是一条失败记录,会被更新成其他状态,
同时生成一条新的Log,后面重跑就依赖这条新的,以此类推。作废一条失败的,在生成一条新的。
通过cron表达式,加上当前的时间,得到下一次执行的时间。
new CronExpression(jobInfo.getScheduleConf()).getNextValidTimeAfter(fromTime);
关键代码
JobTriggerPoolHelper.trigger 触发任务
com.xxl.job.admin.core.trigger.XxlJobTrigger#processTrigger 通过策略选择机器
调度时:
1、执行任务时,指定客户端的ip
2、指定重试次数
3、如何设置分片参数
执行任务和计算任务都使用了大量的线程池和线程
fastTriggerPool 快线程池:处理服务端内部传过来的任务Id,然后给客户端发起执行任务的请求。
slowTriggerPool 慢线程池:主要是接受外部穿过来的任务Id,然后执行任务。和快线程的区别在于60秒内,超时次数超过10次,就会进到这里。
registryOrRemoveThreadPool:注册和移除处理线程池
1、当客户端像服务端发起注册时,服务端收到注册请求,会把执行塞到这个线程池里面。
1、当客户端像服务端发起移除时,服务端收到移除请求,会把执行塞到这个线程池里面。
callbackThreadPool 回调任务处理线程池
1、处理客户端请求服务端的回调任务。
registryMonitorThread:
1、删除超过心跳检测时间的注册记录,也就是XxlJobRegistry表的数据。
2、把所有【自动注册】的ip查询出来,然后更新到XxlJobGroup表里面去,多个ip逗号分隔。
JobFailMonitorHelper.monitorThread
1、从XxlJobLog表找出失败的记录,并且发送
logrThread:
1、生成报表,通知一段时间内,成功多了,失败了多少,执行时间信息等,查询的是xxlJobLog
2、每天会清理xxlJobLog表里,符合一点规则的数据。直接delete掉
monitorThread
1、处理xxlJobLog表里面超过10分钟但是没有返回执行结果的数据,将其更新为失败
scheduleThread:取出任务塞到时间轮里面去
5秒钟睡眠一次,睡醒后,从任务表里面xxlJobInfoDao查询,近五秒内要执行的任务,当然也会拿到超过当前时间的任务。
1、如果过期超过5秒了,判断规则-调度过期策略
2、如果超过执行时间,但是小于5秒,就执行一次。但是有个特殊情况,就是前面执行完任务,刷新一次任务下一次执行时间,发现下一次执行时间还是小于【当前时间+5秒】
就把任务放到时间轮里面,在刷行一次任务下一次的执行时间。。
3、否则就还没到执行时间,就把任务放到时间轮盘里面。刷新下一次执行时间。
注意:只要是执行了一次任务,或者是把任务放到时间轮里面去,都必须要刷新一次任务下一次的执行时间。
ringThread:执行时间轮里的任务
1、一秒钟执行一次,每次从时间轮里面取出这个刻度所对应的任务集合Id,注意是个集合,一个刻度会有多个任务,立即执行任务。
2、任务的执行也就是把任务Id,往快慢线程池里面丢fastTriggerPool、slowTriggerPool,由快慢线程向客户端发起执行某个任务的http请求。
客户端的线程池和线程
localThread:清理日志文件
这个线程是清日志文件的线程,清完睡眠一天,第二天在开始执行。
triggerCallbackThread
回调线程,通过循环固定从callBackQueue队列里面取出要任务执行结果通知给服务端。queue里面存的是LogId之类的信息。while里面虽然没有睡眠时间,但是通过queue的take来实现阻塞睡眠。
2、一个回调任务,会通知所有的服务端,如果所有的服务端都通知失败,就会把回调任务存到本地文件,等triggerRetryCallbackThread线程来发起重试
triggerRetryCallbackThread:读取本地文件存到回调任务,发起重试。
先读取本地文件存到回调失败的记录,是triggerCallbackThread线程存的,然后删除,在发起重试。
registryThread
注册到服务端的执行器里面去,等待服务端的执行器来触发任务。
间隔30秒再发起一次注册。
步骤五:执行器集群(可选):
执行器支持集群部署,提升调度系统可用性,同时提升任务处理能力。
执行器集群部署时,几点要求和建议:
执行器回调地址(xxl.job.admin.addresses)需要保持一致;执行器根据该配置进行执行器自动注册等操作。
同一个执行器集群内AppName(xxl.job.executor.appname)需要保持一致;调度中心根据该配置动态发现不同集群的在线执行器列表。
账号和密码:
角色:普通用户和管理什么区别
普通用户:分配权限是基于执行器来分派的,用于对应执行器,在任务管理才可以看到对应的执行器。和这个执行器下面所绑定的任务。可以查看日志,但是不能新增执行器.
比如是否可以操作一下执行器(多选)
1、示例执行器(xxl-job-executor-sample)
2、手动录入ip的执行器(xxl-job-executor-sample-shoudong)
XxlJobScheduler
1、JobTriggerPoolHelper
主要有fastTriggerPool、slowTriggerPool 快慢线程,来触发需要触发的任务。
2、JobRegistryHelper
registryOrRemoveThreadPool
客户端注册到服务端的执行器里面去了。该线程会根据执行器下注册进来客户端,检查表里面的updateTime来剔除长时间没有更新的客户端。
registryMonitorThread 先删除updateTime超过指定时间的客户端,删除后,在把客户端排好序存到执行器里面去。
3、JobFailMonitorHelper
monitorThread 发送告警,如果重试次数不为空,在发一次一次重试。
4、JobCompleteHelper
callbackThreadPool 处理客户端要发给服务端回调请求。
monitorThread 针对触发中,但是还没有更新成成功的调用,并且超过10分钟,给Handler_code打上500的标签。 trigger_code = 200 and handle_code = 0 并且超过10分钟,一般是任务触发,但是出的服务器不在regist表里面。
5、JobLogReportHelper
logrThread:日志报表线程,每分钟刷新一次任务执行报表数据
6、JobScheduleHelper
scheduleThread 找到准备要执行的任务,放到时间轮盘里面。
ringThread 从时间轮盘里面取出任务,触发任务。
客户端:
XxlJobExecutor
1、XxlJobFileAppender生成log文件。
2、initAdminBizList 根据服务端的地址,创建AdminBizClient对象。
3、JobLogFileCleanThread
localThread:日志清理线程,定时清理,每天清理一次,清理完睡眠一天。
4、TriggerCallbackThread
triggerCallbackThread 回调线程,专门从回调队列里面把任务取出来,去调用服务端的回调接口来告诉服务端处理结果。
triggerRetryCallbackThread:重试机制线程,如果回调服务器失败,那就从日志里面找到记录,再次回调给服务器。
5、initEmbedServer
初始化netty,客户端通过ip+端口监听,如果服务端一发请求,这里就会收到,主要是接受服务端发过来的请求,并处理。
注册到服务端的执行执行器里面去。
1、客户端怎么注册到服务端,并且完成心跳检测的。 答:通过registryThread这个线程来完成的。
2、服务端通过什么来触发任务的。 答案:通过时间滚轮线程ringThread来把任务塞到,快慢线程池fastTriggerPool、slowTriggerPool来触发调度,当调用客户端的run方法来执行任务,run方法会实时返回执行结果,注意这个只是调度结果,也就是triggerCode=200,那么任务实际的结果时怎么拿到的呢?
3、任务的实际执行结果
在客户端执行run方法后,会通过TriggerCallbackThread的pushCallBack方法来吧调度任务的实际结果,存到这个线程的队列中,通过这个线程while死循环从queue里面取任务,并解析结果发送给服务端。主要是HandleCode和HandleMsg。服务端收到回调请求,会把handleMsg和HandleCode、HandleTime ,更新到log表里面去。
4、run方法是怎么执行到加了@XxlJob的方法?
是实例化的时候,会处理xxlJob.class注解的方法,通过反射拿到这个Method的对象,生成一个代理对象Jobhandler对象,在收到服务端发过来的run请求后,会找到这个代理对象,然后执行目标方法。
5、
callbackLog 做什么的?
appendFailCallbackFile是做什么的
jobThreadRepository和jobHandlerRepository什么区别
xxlJob的PPT
https://max.book118.com/html/2022/0328/7134132102004105.shtm
1、XxlJobAdminConfig-afterPropertiesSet
2、xxlJobScheduler = new XxlJobScheduler(); 整体都是围着这个
3、xxlJobScheduler.init(); 里面会做很多事情
JobTriggerPoolHelper.toStart(); 2个线程池