官方文档:https://www.xuxueli.com/xxl-job/
先来看下任务触发和执行的 完整的任务触发执行总体流程图 如下:

不管是自动触发还是UI人工手动触发,都是依赖于DB的,定时触发使用了经典的时间轮算法,扫描数据库内数据,根据对应线程池策略,启动异步线程,再根据对应的分片和路游策略,最终决定任务的执行人,通过xxl-rpc告知对应的执行器。
看着这相比大家一定在想,任务是如何自动触发的呢?那么就一起看看任务是如何真正触发的,如下:

1.分布式锁
xxl-job极有可能也是多机部署,那他是如何数据的一致性呢?刚我们提到不管是人工触发还是自动,都是依赖于DB的,想到数据第一个就想到了锁,那么对于数据库而言,是天然的保证数据的一致性的,一起看一下是怎么做的

细心的小伙伴一眼就看出来了,那就是“事务”和悲观锁,很多人看到这会有2个疑问,一个是性能,第二个是分布式锁为什么不是redis或是zookeeper什么的,干嘛用数据库的,那么回到问题本身,xxl-job是什么,他是一个“定时任务”,那么定时任务对性能要求是不是要求有那么高?既然对性能要求没那么高,mysql本身是不是就足够了,何必在引入其他的组件呢?
2.扫描可执行数据
方法的调用

xxl-job的dao使用mybatis实现的,具体sql如下

pagesize:配置定死
maxNextTime:当前时间毫秒值+5000
3.触发算法
拿到了距now 5秒内的任务列表数据:scheduleList,分三种情况处理:for循环遍历scheduleList集合
(1)对到达now时间后的任务:(超出now 5秒外):直接跳过不执行; 重置trigger_next_time;

(2)对到达now时间后的任务:(超出now 5秒内):线程执行触发逻辑; 若任务下一次触发时间是在5秒内, 则放到时间轮内(Map<Integer, List<Integer>> 秒数(1-60) => 任务id列表);再 重置trigger_next_time

(3)对未到达now时间的任务:直接放到时间轮内;重置trigger_next_time 。

3.时间轮算法触发
xxl-job时间轮数据结构: Map<Integer, List<Integer>> key是秒数(1-60) value是任务id列表,具体结构如下图 :
代码具体如下:

间轮数据结构: Map<Integer, List<Integer>>key是hash计算触发时间获得的秒数(1-60),value是任务id列表
入轮:扫描任务触发时 (1)本次任务处理完成,但下一次触发时间是在5秒内(2)本次任务未达到触发时间 出轮:获取当前时间秒数,从时间轮内移出当前秒数前2个秒数的任务id列表, 依次进行触发任务;(避免处理耗时太长,跨过刻度,多向前校验一秒)
增加时间轮的目的是:任务过多可能会延迟,为了保障触发时间尽可能和 任务设置的触发时间尽量一致,把即将要触发的任务提前放到时间轮里,每秒来触发时间轮相应节点的任务
到这xxl-job的执行流程也大致讲完了,那么说一说我对他的执行流程的一点理解和可能存在的问题,首先明确一下他是一个定时任务,既然是“定时”任务,那么第一个想到时效性,当一次取出的任务过多时,Map的结果vaule过于庞大,那么在1秒内极有可能是不能执行完的,最直接的解决办法就是“少吃多餐”,增加取得频率降低每次取出的数目,这么做必然降低性能,这个时候就需要使用者自行斟酌了,到底是要“性能”还是要“时效性”,个人建议更加注重性能,定时任务大多数时候晚几秒影响没那么大,当然具体还要看使用者的具体场景。