从xxl-job源码看Scheduler定时任务的原始实现

news2024/12/24 9:11:40

一、背景

因为xxl-job本身是统一的分布式任务调度框架,所以在实现定时任务的时候,就断不能再去依赖别人了。
其次,它尽可能只依赖spring框架,或者说spring boot/cloud。
也就是说,它会尽少地使用spring外的三方框架。于是,我们看到xxl-job都未使用
@EnableScheduling和@Scheduled,去实现定时任务,而是使用本文要讲的原始方式。

本文试着梳理下,它是怎么实现的。

二、入口类XxlJobAdminConfig.java

1、作为整个框架的入口

  • servlet容器启动的时候,实例化
  • 本身是一个单例,对外提供方法getAdminConfig()以访问其他成员变量
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

// 使用注解@Component实例化类,注入到spring容器里,保证单例。
@Component
public class XxlJobAdminConfig implements InitializingBean, DisposableBean {

    private static XxlJobAdminConfig adminConfig = null;
    public static XxlJobAdminConfig getAdminConfig() {
        return adminConfig;
    }


    // ---------------------- XxlJobScheduler ----------------------

    private XxlJobScheduler xxlJobScheduler;

    @Override
    public void afterPropertiesSet() throws Exception {
        adminConfig = this;

        xxlJobScheduler = new XxlJobScheduler();
        xxlJobScheduler.init();
    }

    @Override
    public void destroy() throws Exception {
        xxlJobScheduler.destroy();
    }
}

2、InitializingBean接口

还有一个替代方式,就是在方法上使用@PostConstruct 注解。

InitializingBean 是 Spring 框架中的一个接口,它定义了一个回调方法 afterPropertiesSet(),在实现了这个接口的 Bean 在初始化完成之后会被自动调用。

具体来说,当 Spring 容器创建一个 Bean 并完成依赖注入(包括属性的设置和依赖的注入)后,会检查该 Bean 是否实现了 InitializingBean 接口。如果实现了,容器会在完成依赖注入之后,调用 afterPropertiesSet() 方法,这样你可以在这个方法中进行一些初始化操作。

举个例子,在你的 Bean 类中实现了 InitializingBean 接口,并且在 afterPropertiesSet() 方法中做了一些初始化操作,当这个 Bean 被 Spring 容器创建并完成属性注入时,afterPropertiesSet() 方法就会被自动调用。

在xxl-job这里,是创建XxlJobScheduler实例对象,并且执行其init()方法。

3、DisposableBean接口

还有一个替代方式,就是在方法上使用@PreDestroy注解。

DisposableBean 是 Spring 框架中的另一个接口,它定义了一个回调方法 destroy(),在实现了这个接口的 Bean 在销毁时会被自动调用。

与 InitializingBean 类似,当 Spring 容器检测到一个 Bean 实现了 DisposableBean 接口时,在该 Bean 被销毁前(例如应用程序关闭时)会调用其 destroy() 方法,以便你可以在这个方法中执行一些资源释放或清理的操作。

举个例子,在你的 Bean 类中实现了 DisposableBean 接口,并在 destroy() 方法中释放了某些资源,当该 Bean 被 Spring 容器销毁时,destroy() 方法会被自动调用。

在xxl-job这里,是执行XxlJobScheduler的destroy()方法。

下面就看一看XxlJobScheduler的init()和destroy()

三、XxlJobScheduler.java

    public void init() throws Exception {
        // admin log report start
        JobLogReportHelper.getInstance().start();
    }

    
    public void destroy() throws Exception {
        // admin log report stop
        JobLogReportHelper.getInstance().toStop();
    }

本身没什么代码量,值得一看是下面的缓存实现。

根据地址反查ExecutorBiz;ExecutorBizClient实现了接口ExecutorBiz。

不要看它的变量名是executorBizRepository,可和数据库没有啥关系,只是一个Map集合。

// ---------------------- executor-client ----------------------
    private static ConcurrentMap<String, ExecutorBiz> executorBizRepository = new ConcurrentHashMap<String, ExecutorBiz>();
    public static ExecutorBiz getExecutorBiz(String address) throws Exception {
        // valid
        if (address==null || address.trim().length()==0) {
            return null;
        }

        // load-cache
        address = address.trim();
        ExecutorBiz executorBiz = executorBizRepository.get(address);
        if (executorBiz != null) {
            return executorBiz;
        }

        // set-cache
        executorBiz = new ExecutorBizClient(address, XxlJobAdminConfig.getAdminConfig().getAccessToken());

        executorBizRepository.put(address, executorBiz);
        return executorBiz;
    }

四、本文的重点类JobLogReportHelper.java

前面都是实例化和引用,现在是核心的实现了。
首先看它的成员变量:

  • private Thread logrThread;
  • private volatile boolean toStop = false;

1、成员变量logrThread

在方法init()中进行定义并启动。

       logrThread = new Thread(new Runnable() {

            @Override
            public void run() {
				// 略
            }
        });
        logrThread.setDaemon(true);
        logrThread.setName("xxl-job, admin JobLogReportHelper");
        logrThread.start();

2、成员变量toStop

它是一个布尔类型,默认是false–不停止。那么在什么时候停止呢,答案是在方法toStop()被调用的时候,设置为ture–停止。

另外,由volatile关键词修饰它,做到线程安全。

3、方法toStop()

中断子线程 logrThread 并等待它执行完毕。如果等待过程中发生了中断,会在日志中记录错误信息。

   public void toStop(){
        toStop = true;
        // interrupt and wait
        logrThread.interrupt();
        try {
            logrThread.join();
        } catch (InterruptedException e) {
            logger.error(e.getMessage(), e);
        }
    }

4、重点方法start()的实现

删除Log记录的频率为1天一次。在删除的时候,每次先拉取1000条记录的logId,然后删除,循环,直至没有拉取到记录为止。第三点,每次删除完,线程会进行延时1分钟。

换句话说,每分钟会循环执行一次,从判断的入口开始。

逻辑上讲,线程延时1分钟,和删除的时间区间为1天,存在冲突,并不是很好地配合。

线程虽然每隔1分钟来检测,是否有需要删除的日志,皆是徒劳。

// 判断条件:当前时间-最后一次删除时间 > 24小时
          if (XxlJobAdminConfig.getAdminConfig().getLogretentiondays()>0
                            && System.currentTimeMillis() - lastCleanLogTime > 24*60*60*1000) {

           // expire-time
           Calendar expiredDay = Calendar.getInstance();
           expiredDay.add(Calendar.DAY_OF_MONTH, -1 * XxlJobAdminConfig.getAdminConfig().getLogretentiondays());
           expiredDay.set(Calendar.HOUR_OF_DAY, 0);
           expiredDay.set(Calendar.MINUTE, 0);
           expiredDay.set(Calendar.SECOND, 0);
           expiredDay.set(Calendar.MILLISECOND, 0);
           Date clearBeforeTime = expiredDay.getTime();

           // clean expired log
           // 每次拉取1000条log记录,循环删除
           List<Long> logIds = null;
           do {
               logIds = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().findClearLogIds(0, 0, clearBeforeTime, 0, 1000);
               if (logIds!=null && logIds.size()>0) {
                   XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().clearLog(logIds);
               }
           } while (logIds!=null && logIds.size()>0);

           // update clean time
           // 更新最后一次删除的时间戳
           lastCleanLogTime = System.currentTimeMillis();
       }

       try {
           // 延时一分钟
           TimeUnit.MINUTES.sleep(1);
       } catch (Exception e) {
           if (!toStop) {
               logger.error(e.getMessage(), e);
           }
       }
     
  • 时间戳机制

使用了一个时间戳变量,用来记录上一次的删除时间,才有了时间区间的判断入口。

在这里插入图片描述

五、总结

本文从xxl-job源码,分析了如何在spring框架下,既不使用注解@Scheduled,也不使用第三方框架,如何实现定时任务。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/892185.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AutoSAR系列讲解(深入篇)13.3-Mcal Dio配置

目录 一、Dio port配置 二、Dio pin配置 一、Dio port配置 同之前的Port一样,双击进入Dio配置界面后会看到几乎差不多的配置界面。General和Port类似,我们不再赘述,主要讲解Dio的配置 1. 其实Dio并没有什么实质的作用,主要起到了一个重命名的功能。双击DioConfig_0进入下…

小黑子—JavaWeb:第七章 - Vue 与 Element 综合案例

JavaWeb入门7.0 1. VUE1.1 Vue 快速入门1.2 Vue 常用指令1.2.1 v-bind1.2.2 v-model1.2.3 v-on1.2.4 v-if1.2.5 v-show1.2.6 v-for 1.3 Vue 生命周期1.4 Vue案例1.4.1 查询所有1.4.2 新增品牌 2. Element2.1 Element快速入门2.2 Element布局2.3 Element 常用组件2.3.1 表格2.3.…

京东API简介及应用实例

京东API&#xff08;Application Programming Interface&#xff09;&#xff0c;即京东开放平台的接口&#xff0c;是京东提供给开发者的一组规定格式和约定的接口&#xff0c;用于实现与京东电商平台进行数据交互和功能调用。通过使用京东API&#xff0c;开发者可以实现商品查…

Unity的TimeScale的影响范围分析

大家好&#xff0c;我是阿赵。 这期来说一下Unity的TimeScale。 一、前言 Unity提供了Time这个类&#xff0c;来控制时间。其实我自己倒是很少使用这个Time&#xff0c;因为做网络同步的游戏&#xff0c;一般是需要同步服务器时间&#xff0c;所以我比较多是在使用System.Date…

【运筹优化】贪心启发式算法和蜘蛛猴优化算法求解连续选址问题 + Java代码实现

文章目录 一、问题描述二、思路分析三、解决方案3.1 贪心启发式算法3.2 群体智能算法&#xff08;蜘蛛猴优化算法&#xff09; 四、总结 一、问题描述 选址问题是指在规划区域里选择一个或多个设施的位置&#xff0c;使得目标最优。 按照规划区域的结构划分&#xff0c;可以将…

QT的network的使用

一个简单的双向的网络连接和信息发送。效果如下图所示&#xff1a; 只需要配置这个主机的IP和端口号&#xff0c;由客户端发送链接请求。即可进行连接。 QT的network模块是一个用于网络编程的模块&#xff0c;它提供了一系列的类和函数&#xff0c;可以让您使用TCP/IP协议来创…

pdf格式文件下载不预览,云存储的跨域解决

需求背景 后端接口中返回的是pdf文件路径比如&#xff1a; pdf文件路径 &#xff08;https://wangzhendongsky.oss-cn-beijing.aliyuncs.com/wzd-test.pdf&#xff09; 前端适配是这样的 <ahref"https://wangzhendongsky.oss-cn-beijing.aliyuncs.com/wzd-test.pdf&…

Spring框架之AOP详解

目录 一、前言 1.1.Spring简介 1.2.使用Spring的优点 二、Spring之AOP详解 2.1.什么是AOP 2.2.AOP在Spring的作用 2.3.AOP案例讲解 三、AOP案例实操 3.0.代理小故事&#xff08;方便理解代理模式&#xff09; 3.1.代码演示 3.2.前置通知 3.3.后置通知 3.3.环绕通知…

聚观早报|俞敏洪东方甄选带货北京特产;京东物流上半年盈利

【聚观365】8月17日消息 俞敏洪东方甄选直播间带货北京特产 京东物流上半年实现盈利 百度CTO称大语言模型为人工智能带来曙光 腾讯控股第二季度盈利262亿元 2023中国家庭智慧大屏消费白皮书 俞敏洪东方甄选直播间带货北京特产 近日&#xff0c;东方甄选在北京平谷区开播&…

Linux:shell脚本:基础使用(5)《正则表达式-sed工具》

sed是一种流编辑器&#xff0c;它是文本处理中非常中的工具&#xff0c;能够完美的配合正则表达式使用&#xff0c;功能不同凡响。 处理时&#xff0c;把当前处理的行存储在临时缓冲区中&#xff0c;称为“模式空间”&#xff08;pattern space&#xff09;&#xff0c;接着用s…

数据库MySQL 创建表INSERT

创建表 常见数据类型(列类型) 列类型之整型 unsigned的用法 列类型之bit 二进制表示 bit&#xff08;8&#xff09;表示一个字节 列类型之小数 1.单精度float 双精度double 2.decimal 自定义 M为小数点前面有多少位 D是小数点后面有多少位 列类型之字符串 1.char( 字符 )…

实现简单的element-table的拖拽效果

第一步&#xff0c;先随便创建element表格 <el-table ref"dragTable" :data"tableData" style"width: 100%" border fit highlight-current-row><el-table-column label"日期" width"180"><template slot-sc…

element-Plus中el-menu菜单无法正常收缩解决方案

<el-menu :collapse"true">如图所示收缩之后&#xff0c;有子级的菜单还有箭头文字显示 从代码对比看层级就不太对了&#xff0c;嵌套错误了&#xff0c;正常下方官网的ul标签下直接是li&#xff0c;在自己的代码中&#xff0c;ul标签下是div标签&#xff0c;层…

爬虫工具的选择与使用:阐述Python爬虫优劣势

作为专业爬虫ip方案解决服务商&#xff0c;我们每天都面对着大量的数据采集任务需求。在众多的爬虫工具中&#xff0c;Python爬虫凭借其灵活性和功能强大而备受青睐。本文将为大家分享Python爬虫在市场上的优势与劣势&#xff0c;帮助你在爬虫业务中脱颖而出。 一、优势篇 灵活…

初试rabbitmq

rabbitmq的七种模式 Hello word 客户端引入依赖 <!--rabbitmq 依赖客户端--><dependency><groupId>com.rabbitmq</groupId><artifactId>amqp-client</artifactId><version>5.8.0</version></dependency> 生产者 imp…

相对于多进程,你真的知道为什么要使用多线程吗(C/C++多线程编程)

目录 前言 线程VS进程 POSIX线程库的使用 线程创建 线程等待 线程分离 线程状态 可结合态线程实例 分离态线程实例 线程退出 线程的同步与互斥 同步互斥的概念 互斥锁&#xff08;互斥&#xff09; 互斥锁的使用步骤 总结说明 信号量 信号量的使用步骤 条件变…

数据包如何游走于 Iptables 规则之间?

在前文《Linux路由三大件》中&#xff0c;我们提到了 iptables 可以修改数据包的特征从而影响其路由。这个功能无论是传统场景下的 防火墙&#xff0c;还是云原生场景下的 服务路由&#xff08;k8s service&#xff09;、网络策略(calico network policy) 等都有依赖。 虽然业…

7.逻辑结构VS物理结构

第四章 文件管理 7.逻辑结构VS物理结构 ​   fopen这个函数做的事情就是打开了“test.txt”这个文件&#xff0c;并且“w”说明是以“写”的方式打开的&#xff0c;以“写”的方式打开才能往这个文件里写入数据&#xff0c;如果文件打开了那么fp这个指针就可以指向和这个文件…

Eclipse如何设置快捷键

在eclopse设置注释行和取消注释行 // 打开eclipse&#xff0c;依次打开&#xff1a;Window -> Preferences -> General -> Key&#xff0c;

数据结构--关键路径

数据结构–关键路径 AOE⽹ 在 带权有向图 \color{red}带权有向图 带权有向图中&#xff0c;以 顶点表示事件 \color{red}顶点表示事件 顶点表示事件&#xff0c;以 有向边表示活动 \color{red}有向边表示活动 有向边表示活动&#xff0c;以 边上的权值表示完成该活动的开销 \…