SpringBoot教程(二十一) | SpringBoot实现定时任务

news2024/9/20 9:10:33

SpringBoot教程(二十一) | SpringBoot实现定时任务

  • 单点定时任务
    • 方式一:使用@Scheduled+@EnableScheduling注解
      • 巨坑(@Scheduled任务都用了同一个线程去执行,导致定时任务存在堵塞)
        • 解决办法一:添加自定义的ThreadPoolTaskScheduler配置(为调度配置多个线程)
        • 解决办法二(建议用这个):使用异步包裹定时任务
  • 分布式定时任务
    • 方式一:使用elastic-job
      • 1、引入相关依赖
      • 2、application.yml中配置注册中心和作业调度
      • 3、job实例
      • ElasticJob-UI监控平台

参考文章:
【1】IDEA SpringBoot实现定时任务(保姆级教程,超详细!!!)
【2】SpringBoot整合分布式任务调度Elastic-Job

单点定时任务

方式一:使用@Scheduled+@EnableScheduling注解

在Spring Boot项目中使用@Scheduled注解实现定时任务时,你通常不需要额外导入特定的依赖,
因为@Scheduled是Spring框架的核心功能之一,并且Spring Boot会自动配置与调度相关的组件。

@EnableScheduling:Spring框架提供的一个注解,用于启用基于注解的定时任务调度功能。当在Spring的配置类(如使用@Configuration注解的类)上使用@EnableScheduling注解时,Spring会自动配置一个任务调度器(TaskScheduler),负责管理所有带有@Scheduled注解的方法。

@Scheduled:Spring框架中用于定时任务调度的注解。它允许开发者将一个方法标记为定时任务,并配置任务的执行时间间隔或Cron表达式,从而实现在指定时间或按照指定周期自动执行该方法的功能。 除了配置Cron表达式外,还可以通过fixedRate和fixedDelay两种方式设置定时任务,这两种方式可以自行了解。

@Slf4j
@Component
@EnableScheduling
public class DemoTask {
 
    // 每5秒执行一次
    @Scheduled(cron = "0/5 * * * * ? ")
    public void testSchedule1() {
        log.info("第 1 个定时任务"+"当前执行任务的线程号ID===>{}", Thread.currentThread().getId()); // 日志输出
    }

    // 每10秒执行一次
    @Scheduled(cron = "0/10 * * * * ? ")
    public void testSchedule2() {
        log.info("第 2 个定时任务"+"当前执行任务的线程号ID===>{}", Thread.currentThread().getId()); // 日志输出
    }
 
}

项目启动后
在这里插入图片描述
是成功且正常执行的
当我把阻塞sleep加进去以后

@Slf4j
@Component
@EnableScheduling
public class DemoTask {
 
    // 每10秒执行一次
    @Scheduled(cron = "0/10 * * * * ? ")
    public void testSchedule1() {
         Thread.sleep(10000);//休眠10秒
        log.info("第 1 个定时任务"+"当前执行任务的线程号ID===>{}", Thread.currentThread().getId()); // 日志输出
    }

    // 每10秒执行一次
    @Scheduled(cron = "0/10 * * * * ? ")
    public void testSchedule2() {
        log.info("第 2 个定时任务"+"当前执行任务的线程号ID===>{}", Thread.currentThread().getId()); // 日志输出
    }
 
}

在这里插入图片描述

会发现怎么定时任务2变成间隔20秒执行一次了
是因为会执行这个定时任务使用的线程号ID都是同一个,任务1堵塞了10秒导致影响了后面任务2的执行(说明都用了同一个线程去执行定时任务的,简直巨坑!!!)。

巨坑(@Scheduled任务都用了同一个线程去执行,导致定时任务存在堵塞)

解决办法一:添加自定义的ThreadPoolTaskScheduler配置(为调度配置多个线程)

默认情况下,Spring会尝试在Spring应用上下文中查找一个名为taskScheduler的bean,这个bean必须是TaskScheduler接口的实现。
一旦找到了这个bean,Spring就会使用它来调度所有@Scheduled注解标记的方法。
所以Bean的name不能随便乱写

import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;  
  
@Configuration  
public class SchedulerConfig {  
     
    //这个bean名字不要乱改,否则会不生效
    @Bean(name = "taskScheduler")  
    public ThreadPoolTaskScheduler taskScheduler() {  
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();  
        // 设置线程池的核心大小  
        scheduler.setPoolSize(10);  
        // 其他设置,如线程名称前缀等(可选)  
        scheduler.setThreadNamePrefix("wocao-task-");  
        // 初始化调度器  
        scheduler.initialize();  
        return scheduler;  
    }  
}

效果如下:

定时任务1不会堵塞定时任务2了,且 定时任务 都是10秒钟执行一次,不会存在堵塞延迟

在这里插入图片描述

解决办法二(建议用这个):使用异步包裹定时任务

(1)首先配置自定义线程池

@Configuration
public class DemoTheadPoolConfig {

    @Bean(name = "taskExecutor")
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //设置核心线程数
        executor.setCorePoolSize(10);
        //设置最大线程数
        executor.setMaxPoolSize(20);
        //缓冲队列200:用来缓冲执行任务的队列
        executor.setQueueCapacity(200);
        //线程活路时间 60 秒
        executor.setKeepAliveSeconds(60);
        //线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
        executor.setThreadNamePrefix("demo-thread-");
        //设置拒绝策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.setWaitForTasksToCompleteOnShutdown(true);
        return executor;
    }
}

(2) 执行异步操作

方法一:使用工具类CompletableFuture.runAsync

CompletableFuture.runAsync 是 Java 8 引入的一个非常有用的工具,用于异步执行任务
runAsync 方法用于启动一个异步任务,这个任务不返回结果(即返回类型为 void)。

基本用法

runAsync 方法有两种形式:

  1. 无参数版本:使用系统默认的 ForkJoinPool 来异步执行任务。

    CompletableFuture.runAsync(() -> {
        // 这里是异步执行的任务
        System.out.println("异步任务执行中:" + Thread.currentThread().getName());
    });
    

    在这个例子中,runAsync 接收一个 Runnable 函数式接口的实现(即一个不接受参数且不返回结果的 run 方法),并在一个默认的线程池中异步执行这个任务。

  2. 带 Executor 参数版本:允许你指定一个自定义的 Executor 来执行异步任务。

    Executor executor = Executors.newFixedThreadPool(5); // 创建一个固定大小的线程池
    CompletableFuture.runAsync(() -> {
        // 这里是异步执行的任务
        System.out.println("异步任务执行中,使用自定义线程池:" + Thread.currentThread().getName());
    }, executor);
    

    在这个例子中,runAsync 除了接收一个 Runnable 任务外,还接收一个 Executor 参数,允许你控制异步任务的执行线程。

@Slf4j
@Component
@EnableScheduling
public class DemoTask {

    @Autowired
    @Qualifier("taskExecutor") // 确保使用我们自定义的线程池
    private TaskExecutor taskExecutor; // 注入 TaskExecutor
 
    // 每10秒执行一次
    @Scheduled(cron = "0/10 * * * * ? ")
    public void testSchedule1() {
        CompletableFuture.runAsync(() -> {
            try {
                Thread.sleep(10000); // 休眠10秒,模拟业务场景执行时间
                log.info("第 1 个定时任务"+"当前执行任务的线程号ID===>{}", Thread.currentThread().getId()); // 日志输出
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, taskExecutor);
    }

    // 每10秒执行一次
    @Scheduled(cron = "0/10 * * * * ? ")
    public void testSchedule2() {
        CompletableFuture.runAsync(() -> {
            try {
                log.info("第 2 个定时任务"+"当前执行任务的线程号ID===>{}", Thread.currentThread().getId()); // 日志输出
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, taskExecutor);
    }
 
}

效果如下

定时任务1不会堵塞定时任务2了,且 定时任务 都是10秒钟执行一次,不会存在堵塞延迟

在这里插入图片描述

方法二:使用@Async+@EnableAsync

@EnableAsync:开启异步任务
@Async:给希望异步执行的方法标注
一般使用@Async都会指定自定义的线程池
在此处的例子应该写成这样@Async(value = “TaskExecutor”)

@Slf4j
@Component
@EnableAsync
@EnableScheduling
public class DemoTask {
 
    // 每10秒执行一次
    @Async("taskExecutor")//指定自定义的线程池
    @Scheduled(cron = "0/10 * * * * ? ")
    public void testSchedule1() {
        try {
            Thread.sleep(10000); // 休眠10秒,模拟业务场景执行时间
            log.info("第 1 个定时任务"+"当前执行任务的线程号ID===>{}", Thread.currentThread().getId()); // 日志输出
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

   // 每10秒执行一次
    @Async("taskExecutor")//指定自定义的线程池
    @Scheduled(cron = "0/10 * * * * ? ")
    public void testSchedule2() {
        try {
            log.info("第 2 个定时任务"+"当前执行任务的线程号ID===>{}", Thread.currentThread().getId()); // 日志输出
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

效果如下

定时任务1不会堵塞定时任务2了,且 定时任务 都是10秒钟执行一次,不会存在堵塞延迟

在这里插入图片描述

分布式定时任务

方式一:使用elastic-job

elastic-job是当当网基于quartz 二次开发的弹性分布式任务调度系统,功能丰富强大,采用zookeeper实现分布式协调,实现任务高可用以及分片。
Elastic-Job是一个分布式调度的解决方案,由当当网开源,它由两个相互独立的子项目Elastic-Job-Lite和Elastic-Job-Cloud组成,使用Elastic-Job可以快速实现分布式任务调度。

zk在elastic-job的作用

Elastic-Job依赖ZooKeeper完成对执行任务信息的存储(如任务名称、任务参与实例、任务执行策略等);
Elastic-Job依赖ZooKeeper实现选举机制,在任务执行实例数量变化时(如在快速上手中的启动新实例或停止实例),会触发选举机制来决定让哪个实例去执行该任务。

我这边主要讲SpringBoot,所以肯定会采用场景启动器starter的

什么是场景启动器?
也就是spring-boot-starter- 开头的依赖,
以下是一些常见的Spring Boot场景启动器示例:
(1)spring-boot-starter-data-jpa:包含Spring Data JPA和Hibernate的依赖,用于简化数据库访问和JPA(Java Persistence API)的使用。
(2)spring-boot-starter-data-mongodb:包含Spring Data MongoDB的依赖,用于简化MongoDB数据库的访问。
(3)spring-boot-starter-security:包含Spring Security的依赖,用于添加安全功能,如用户认证和授权。
(4)… … … …

使用Spring Boot的场景启动器确实可以让您避免手动添加所需的依赖,因为它已经为您预先定义并包含了这些依赖。这样,您就可以更专注于业务逻辑的实现,而不是花费大量时间在依赖管理和配置上了。

1、引入相关依赖

<!-- 引入版本的时候要充分考虑自己的Zookeeper Server版本, 建议与其保持一致 -->
<!-- 例如 版本使用的是 zk 3.6.x, Zookeeper Server 也是3.6.x, 如果使用3.4.x则会报错 -->
<dependency>
    <groupId>org.apache.shardingsphere.elasticjob</groupId>
    <artifactId>elasticjob-lite-spring-boot-starter</artifactId>
    <version>3.0.2</version>
</dependency>

2、application.yml中配置注册中心和作业调度

server:
  port: 9999

#elasticjob配置
elasticjob:
  # 注册中心配置
  reg-center:
    # 连接 ZooKeeper 服务器的列表, 包括 IP 地址和端口号,多个地址用逗号分隔
    server-lists: 127.0.0.1:2188
    # ZooKeeper 的命名空间
    namespace: elastic-job-spring
    # 等待重试的间隔时间的初始毫秒数
    base-sleep-time-milliseconds: 1000
    # 等待重试的间隔时间的最大毫秒数
    maxSleepTimeMilliseconds: 3000
    # 最大重试次数
    maxRetries: 3
    # 会话超时毫秒数
    sessionTimeoutMilliseconds: 60000
    # 连接超时毫秒数
    connectionTimeoutMilliseconds: 15000
  # 作业配置, 更多的配置参考官网
  jobs:
    springJob: # job名
      elasticJobClass: com.harvey.demo.job.SpringBootJob
      cron: 0/5 * * * * ?
      shardingTotalCount: 2
      shardingItemParameters: 0=Beijing,1=Shanghai
    springTestJob:
      elasticJobClass: com.harvey.demo.job.SpringRunnerJob
      cron: 0/10 * * * * ?
      shardingTotalCount: 3
      shardingItemParameters: 0=Beijing,1=Shanghai,2=Guangzhou

3、job实例

第一个job:

@Component
public class SpringBootJob implements SimpleJob {

    @Override
    public void execute(ShardingContext shardingContext) {
        System.out.println(this.getClass() + ":分片总数是: [{" + shardingContext.getShardingTotalCount() + "}]; 当前分片项是: [{" + shardingContext.getShardingItem() + "}]");
        //分片参数,(0=text,1=image,2=video,参数就是text、image...)
        String shardingParameter = shardingContext.getShardingParameter();
        //任务参数, 配置是name=test就是name=test
        String jobParameter = shardingContext.getJobParameter();
        System.out.println("current sharding item " + shardingContext.getShardingItem() + ", shardingParameter = " + shardingParameter + ", jobParameter:" + jobParameter);
    }
}

第二个job:

@Component
public class SpringRunnerJob implements SimpleJob {

    @Override
    public void execute(ShardingContext shardingContext) {
        System.out.println(this.getClass() + ":分片总数是: [{" + shardingContext.getShardingTotalCount() + "}]; 当前分片项是: [{" + shardingContext.getShardingItem() + "}]");
        //分片参数,(0=text,1=image,2=video,参数就是text、image...)
        String shardingParameter = shardingContext.getShardingParameter();
        //任务参数
        String jobParameter = shardingContext.getJobParameter();
        System.out.println("current sharding item " + shardingContext.getShardingItem() + ", shardingParameter = " + shardingParameter + ", jobParameter:" + jobParameter);
    }
}

启动项目,可以看到相关的日志。

ElasticJob-UI监控平台

适用于【场景启动器starter】方式开发的elastic-job。

下载ElasticJob-UI

https://shardingsphere.apache.org/elasticjob/current/cn/downloads/

在这里插入图片描述
解压后在bin目录双击启动
(本人是在windows环境)
在这里插入图片描述

访问控制台

启动后游览器访问(默认端口是8088):http://127.0.0.1:8088/#/login 用户名/密码 root/root
登录成功后,链接上注册中心,链接成功后便可以进行任务的管理。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

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

相关文章

linux监控命令

在 Linux 中&#xff0c;有许多命令可以用于监控系统的性能和状态。以下是一些常用的监控命令及其用途&#xff1a; 1. top​ 和 htop​ top ​top​ 命令显示当前系统中运行的进程列表及其资源使用情况。 top​​ ‍ htop ​htop​ 是 top​ 命令的增强版&#xff0c;提…

使用 Spring Event 解耦代码

​ 博客主页: 南来_北往 系列专栏&#xff1a;Spring Boot实战 前言 在Spring框架中&#xff0c;事件机制扮演着至关重要的角色&#xff0c;它不仅促进了组件间的互动&#xff0c;还在提高系统灵活性方面迈出了重要步伐。相较于常规的方法调用&#xff0c;这种机制显著提…

前端组件库汇总

文章目录 一、前端组件库1. ElementUI(基于 Vue 2.0 的桌面端组件库)2. Element Plus(基于 Vue 3,面向设计师和开发者的组件库)3. Vue DevUI(一个基于 DevUI Design 的 Vue3 组件库)4. vant(轻量、可定制的移动端 Vue 组件库)5. Ant Design(助力设计开发者「更灵活」…

使用Spring Boot整合ip2region获取客户端IP地理位置信息

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

Spring之@Import注解

1. 前言 Import 注解 在 Spring 中占据重要地位&#xff0c;是 Spring 的一个重要扩展点。这篇博文我们以案例、源码、应用相结合&#xff0c;来系统的学习一下这个注解 2. 案例演示 2.1 代码准备 2.1.1 创建配置类 AppConfig ComponentScan("com.ys") public c…

实战项目:贪吃蛇游戏的实现(上)

前言 Hello, 今天我们来一起完成一个实战项目&#xff1a;贪吃蛇。 相信大家都不会对这个游戏感到陌生&#xff0c;贪吃蛇游戏是久负盛名的游戏&#xff0c;他和俄罗斯方块&#xff0c;扫雷游戏等游戏位列世界经典游戏之列。这次我们旨在通过实战项目贪吃蛇的实现&#xff0c…

opencv2.4.9源码在Windows下VS2019的编译

1、opencv2.4.9解压后根目录下建立build文件夹 2、采用CMake-gui进行编译 记得把上面两个√去掉&#xff0c;用老版本的opencv再用cuda完全没有意义&#xff0c;我们只是验证算法用。 把这个√也去掉。 重新Configure和Generate&#xff0c;如下图&#xff1a; 然后&#xff1…

大模型汇总:文心一言大模型、腾讯混元大模型、通义千问大模型、字节豆包大模型、智普清言大模型、KIMI 大模型、紫东太初大模型、讯飞星火大模型

文心一言大模型 作为百度自主研发的大型语言模型&#xff0c;具有显著的特点、广泛的应用场景以及独特的优势。以下是对文心一言特点、应用、优势的详细介绍&#xff1a; 特点&#xff1a;知识增强&#xff1a; 文心一言通过持续学习技术&#xff0c;不断吸收海量数据和知识…

Postman接口测试基础教程--2024最新版

文章目录 一、Postman 简介二、Postman 功能概览三、Postman 安装说明1. 下载与安装2. 界面导航说明3. 发送第一个请求 四、Postman 基础功能1. 常见类型的接口请求查询参数的接口请求表单类型的接口请求上传文件的表单请求JSON 类型的接口请求 2. 接口响应数据解析3. 接口管理…

基于GA遗传算法的拱桥静载试验车辆最优布载matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于GA遗传算法的拱桥静载试验车辆最优布载matlab仿真。主要是为了实现桥梁静载试验自动化布载&#xff08;确定车辆位置使得满足加载效率ηq的要求&#xff0c;0…

初级python代码编程学习---- 简单记事本小程序

简单记事本小程序 提供一个基于Python的简单记事本小程序示例。这个小程序将允许用户添加、查看和删除记事条。 # Simple Note Pad Mini-Application # 记事本数据结构 notes [] def add_note(note): """添加一条新的记事条""" …

可重用性功能 在 Jira 中扩展测试管理

在当今动态的软件开发环境中&#xff0c;高效且可扩展的测试管理对于确保软件产品的质量和可靠性至关重要。Jira 是一种流行的项目管理工具&#xff0c;它通过与Zephyr Scale的集成为测试管理提供了强大的解决方案。 在这篇博文中&#xff0c;我们将探讨可重用性功能在测试管理…

信创教育:培养未来科技创新的生力军

随着全球数字化转型的加速&#xff0c;信息技术应用创新&#xff08;简称“信创”&#xff09;产业作为推动国家信息技术自主可控和产业升级的关键领域&#xff0c;正迎来前所未有的发展机遇。信创教育&#xff0c;作为培养未来科技创新生力军的重要阵地&#xff0c;其重要性和…

智慧卫生间环境传感器有哪些?智慧卫生间的特点@卓振思众

随着科技的进步和人们对生活品质的要求不断提高&#xff0c;智慧卫生间作为现代化设施的代表&#xff0c;越来越受到关注。智慧卫生间不仅仅是在外观设计上做文章&#xff0c;更在于其背后强大的智能系统&#xff0c;特别是环境传感器的应用&#xff0c;让厕所的管理和使用变得…

第36课 Scratch入门篇:画正多边形

画正多边形 故事背景: 绘制一个正多边形! 程序原理: 我们前面学习了绘制三角形(3 条边,旋转 120 度),正方形(4 条边,旋转 90 度),我们可以归纳一个公式,针对正多边形,有n条边,那么旋转的角度就是 360/n。通过程序实现就是 开始编程 1、隐藏预设的猫咪角色,…

【C#】 接口 继承

简介 继承是面向对象编程的核心特性之一&#xff0c;它允许我们创建一个类&#xff08;称为子类&#xff09;来继承另一个类&#xff08;称为基类&#xff09;的属性和方法。 作用 这样&#xff0c;我们可以重用代码&#xff0c;减少重复&#xff0c;并使我们的代码更加模块…

无限金币版《废土世界》安卓手机游戏下载,游戏分享

《废土世界》&#xff08;JunkWorld&#xff09;是由IRONHIDE游戏工作室开发的一款塔防游戏&#xff0c;它将玩家带入一个荒凉、贫瘠的后末日世界&#xff0c;玩家需要带领一队拾荒者穿越沙漠和放射性沼泽&#xff0c;进行生存战斗。游戏以其战略深度和丰富的塔防元素为特色&am…

RabbitMQ高级特性 - 消息和队列TTL、死信队列

文章目录 消息和队列TTL概述实战开发 死信队列概述实战开发 消息和队列TTL 概述 a&#xff09;TTL&#xff08;Time To Live 过期时间&#xff09;&#xff0c;RabbitMQ 可以对消息和队列设置 TTL. 当消息到达存活时间之后&#xff0c;还没有被消费&#xff0c;就会被自动清除…

fmql之Linux移植

先了解以下linux移植的大致流程&#xff0c;以及需要的资料、软件等。 《领航者ZYNQ之嵌入式Linux开发指南_V2.0》第十八章 Linux内核移植 (amobbs.com 阿莫电子技术论坛) 前言 复旦微&#xff08;他人经验&#xff09; 复旦微fmql的操作指南来了&#xff1a; 复旦微 FMQL L…

算法:排序(前言)

所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。排序算法&#xff0c;就是如何使得记录按照要求排列的方法。排序算法在很多领域得到相当地重视&#xff0c;尤其是在大量数据的处理方面。一个优秀的…