【分布式】XXL-JOB实现定时任务源码详解


大家好,我是被白菜拱的猪。

一个热爱学习废寝忘食头悬梁锥刺股,痴迷于girl的潇洒从容淡然coding handsome boy。

XXL-JOB

XXL-JOB是一个分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用。

设计思想

设计思想就是将调度行为抽象形成“调度中心”公共平台,而平台自身并不承担业务逻辑,业务逻辑都在执行器那,“调度中心”负责发起调用请求。

将任务抽象成分散的JobHandler,交由“执行器”统一管理,“执行器”负责接收调度请求并执行对应的 JobHandler 中业务逻辑。

因此,“调度”和“任务”两部分可以相互解耦,提高系统整体稳定性和扩展性。

系统组成

  • 调度模块(调度中心)
    负责管理调度信息,按照调度配置发出调度请求,自身不承担业务代码。调度系统与任务解耦,提高了系统可用性和稳定性,同时调度系统性能不再受限于任务模块;
    支持可视化、简单且动态的管理调度信息,包括任务新建,更新,删除,GLUE开发和任务报警等,所有上述操作都会实时生效,同时支持监控调度结果以及执行日志,支持执行器Failover。
  • 执行模块(执行器)
    负责接收调度请求并执行任务逻辑。任务模块专注于任务的执行等操作,开发和维护更加简单和高效;
    接收“调度中心”的执行请求、终止请求和日志请求等。

总结介绍

XXL-JOB是一个分布式任务调度平台,开发迅速、学习简单、轻量级、易扩展。

它的整体架构分为两大模块:调度模块和执行模块,也就是调度中心和执行器,它的设计思想就是就是将调度和任务进行解耦,从而提升系统的稳定性和扩展性。

调度中心主要是负责管理调度信息,按照调度配置发出调度请求,自身是不承担业务代码。调度系统与任务进行解耦,提高了系统可用性和稳定性,调用系统性能不再受限于任务模块。支持可视化、简单且动态的管理调度信息,包括任务新建,更新,删除。同时支撑监控监督结果以及执行日志,支持执行器Failover(故障转移)。

执行器则负责接收调度请求并执行任务逻辑。任务模块专注于任务的执行等操作,开发和维护更加简单和高效;接收“调度中心”的执行请求、终止请求和日志请求。

我们采用Bean模式下的方法模式,使用@XxlJob注解去写我们的任务。

源码分析

下面从一个定时任务是如何执行的开始进行解析。

1、调度中心

首先是从 XxlJobAdminConfig 这个配置类出发 ,它实现了InitializingBean 这个Bean,所以Spring容器在初始化的时候会调用aferPropertiesSet这个方法,在这个方法中创建了XxlJobScheduler,调用了它的init方法,所有关于调度中心的都是在这个方法中进行初始化。

    public void init() throws Exception {
        // init i18n
        initI18n();

        // admin registry monitor run
        JobRegistryMonitorHelper.getInstance().start();

        // admin fail-monitor run
        JobFailMonitorHelper.getInstance().start();

        // admin lose-monitor run
        JobLosedMonitorHelper.getInstance().start();

        // admin trigger pool start
        JobTriggerPoolHelper.toStart();

        // admin log report start
        JobLogReportHelper.getInstance().start();

        // start-schedule
        JobScheduleHelper.getInstance().start();

        logger.info(">>>>>>>>> init xxl-job admin success.");
    }

第一个是国际化相关、监控、失败重试、触发器注册接收注册请求、日志。

第五步JobScheduleHelper调度器,死循环,在xxl_job_info表里取将要执行的任务,更新下次执行时间的,调用JobTriggerPoolHelper类,来给执行器发送调度任务的。

不同的模块都是新启一个线程池,所以互不影响,我们主要是看如果执行调度任务的,他是一个死循环。

在这个方法中主要维护了两个线程schedule thread和ring thread,在这两个线程当中都是使用while循环来执行任务。

schedule thread

因为调用中心有可能是集群部署,那么就有可能会有多个调用中心去执行任务,所以这里涉及到分布式锁的问题,他的解决方案是从数据库层面出发,单独有一个xxl_job_info 表,表很简单就一个字段,lock_name.使用for update语句来进行上锁,然后设置的是不自动提交,当其他线程执行任务时就会在数据库层面进行阻塞,这就保证下面执行的任务都是在同一事务下。

select * from xxl_job_lock where lock_name = 'schedule_lock' for update

然后开始从xxl_job_info表中查询要执行的调度任务,查询条件为下一次任务执行时间小于等于当前时间+5秒预留时间,也就是说把未来5s要执行的时间也查询出来了,为什么要预留五秒呢?假如我们第10秒有个任务要执行,那么不可能等到第10秒的时候才去执行sql语句吧,执行的时候老早就过了时间了,所以得提前查出来。

对要执行的任务进行遍历,有三个分支。方便理解我们可以分为三段0-5-10-15

0-5,5-10,10-15。当前时间为10秒。

第一个分支,下一次时间+5秒预留时间 < 当前时间 ,也就是0-5这个时间段,表示当前任务触发时间超过5秒,已经超时,则不再执行,更新下次任务执行时间。

// 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());
	// fresh next
	refreshNextValidTime(jobInfo, new Date());

第二个分支,当前时间大于下一次触发时间,也就是10-15这个区间,这时候就使用触发器立即执行任务,然后更新下一次执行时间,假如当前时间+5s大于下一次执行时间,也就是在这个阶段也是要执行的,则把他加入到ringData里面,ringData是要在ring thread里面要执行的任务,是一个concurrentHashMap,key是要执行的时间,value是该时间要执行的jobId,比如在第10秒,有id为1,2,3的任务要执行,所以是list。

private volatile static Map<Integer, List<Integer>> ringData = new ConcurrentHashMap<>();

第三个分支也就是未来五秒内要执行的任务,也是要放进时间轮时间ring thread。他们都是一秒一秒去执行。

而且两个线程最后都是调用JobTriggerPoolHelper,trigger执行器去执行任务,在trigger里面有两个线程池,fastTriggerPool和slowTriggerPool,用来处理不同的任务,用来降低任务与任务之间的影响,比如超时次数大于10,则交给慢线程池去执行。

调度线程池隔离,拆分为”Fast”和”Slow”两个线程池,1分钟窗口期内任务耗时达500ms超过10次,该窗口期内判定为慢任务,慢任务自动降级进入”Slow”线程池,避免耗尽调度线程,提高系统稳定性;

ring thread

ring thread根据当前秒数刻度和前一个刻度进行时间轮的任务获取,每次取完就remove,比如当前时间为10秒,那他从ringData取的就是10秒和9秒,这样是为了避免处理耗时太长,跨过刻度,所以向前校验一个刻度;

常见定时任务实现方式

1、Timer

优点:

是JDK自带的定时任务执行类

缺点:

1、当一个任务执行时间过长,会影响其他任务。比如当任务 1 运行时间超过设定的间隔时间时,任务 2 也会延迟执行。 原本任务 1 和任务 2 的执行时间间隔都是 3s,但因为任务 1 执行了 5s,因此任务 2 的执行时间间隔也变成了 10s(和原定时间不符)。

2、当一个任务报异常时,其他任务也会终止,所以在生产环境谨慎使用。

2、ScheduledExecutorService

ScheduledExecutorService 也是 JDK 1.5 自带的 API,我们可以使用它来实现定时任务的功能,ScheduledExecutorService 可以实现 Timer 类具备的所有功能,并且它可以解决了 Timer 类存在的所有问题。

底层是使用了延迟队列,DelayQueue,而延迟队列的底层又是优先级队列PriorityQueue。

他有两个方法,一个固定频率一个是固定延迟时间。

在单机生产环境下建议使用 ScheduledExecutorService 来执行定时任务,它是 JDK 1.5 之后自带的 API,因此使用起来也比较方便,并且使用 ScheduledExecutorService 来执行任务,不会造成任务间的相互影响。

3、SpringTask

使用上面两种定时任务的实现方式,很难实现设定了具体时间的定时任务,比如当我们需要每周五来执行某项任务时。

@EnableScheduling
@Scheduled 

以上都是单机的

分布式定时任务比较

向分布式定时任务,我们熟悉的主要有Quartz和XXL-JOB。

Quartz作为开源作业调度中的佼佼者,是作业调度的首选。但是集群环境中Quartz采用API的方式对任务进行管理,从而可以避免上述问题,但是同样存在以下问题:

  • 问题一:调用API的的方式操作任务,不人性化;
  • 问题二:需要持久化业务QuartzJobBean到底层数据表中,系统侵入性相当严重。
  • 问题三:调度逻辑和QuartzJobBean耦合在同一个项目中,这将导致一个问题,在调度任务数量逐渐增多,同时调度任务逻辑逐渐加重的情况下,此时调度系统的性能将大大受限于业务;
  • 问题四:quartz底层以“抢占式”获取DB锁并由抢占成功节点负责运行任务,会导致节点负载悬殊非常大;而XXL-JOB通过执行器实现“协同分配式”运行任务,充分发挥集群优势,负载各节点均衡。

XXL-JOB弥补了quartz的上述不足之处。

比如我们想改时间就必须要修改源代码,非常麻烦。而且也不能手动的去停止定时任务。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
xxl-job是一个分布式任务调度框架,可以用于实现定时任务的调度和执行。在xxl-job中,定时任务的配置和管理主要涉及到三个部分:xxl-job-admin、执行器组件和定时任务的数据处理。 1. xxl-job-admin是xxl-job的管理后台,可以通过调用com.xxl.job.admin.controller.JobApiController.callback接口来设置定时任务。这个接口可以用于添加、修改、删除定时任务,以及启动、停止定时任务的执行。 2. 执行器组件是用于执行定时任务的组件,其中的配置文件地址为/xxl-job/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/java/com/xxl/job/executor/core/config/XxlJobConfig.java。在这个配置文件中,你可以设置定时任务的一些参数,比如调度线程池大小、任务执行器等。 3. 定时任务的数据处理需要在xxl-job-executor-sample-springboot项目中进行,这是业务代码所在的项目。在这个项目中,你可以使用BEAN模式来设置定时任务。BEAN模式是指通过在类中定义方法并使用@XxlJob注解来标识定时任务,然后在XxlJobConfig.java配置文件中将这个类注册为定时任务。这种方式比较简单,但在官网上没有提供具体的示例。 所以,如果你想使用xxl-job实现定时任务,你可以先在xxl-job-admin中设置定时任务,然后在执行器组件中配置定时任务的相关参数,最后在xxl-job-executor-sample-springboot项目中使用BEAN模式设置定时任务。这样就能够实现定时任务的调度和执行了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值