精华内容
下载资源
问答
  • Spring多定时任务@Scheduled执行阻塞问题

    万次阅读 多人点赞 2019-04-24 17:52:27
    一. 问题描述   最近项目中发现一个问题,计划每日凌晨4:40执行一个定时...后来查了下,原来这种定时方式默认是单线程执行的,恰好我这里有多定时任务,并且其中个在4:40之前的定时任务比较耗时,导致4:40的...

    一. 问题描述
      最近项目中发现一个问题,计划每日凌晨4:40执行一个定时任务,使用注解方式: @Scheduled(cron = “0 40 4 * * ?”),cron表达式明显没有问题,但是这个定时任务总是不按时执行,有时候得等到8点多,有时候9点多才执行。后来查了下,原来这种定时方式默认是单线程执行的,恰好我这里有多个定时任务,并且其中有个在4:40之前的定时任务比较耗时,导致4:40的任务只能等待之前的任务执行完成才能够触发,所以要自己手动把定时任务设置成多线程的方式才行。

    二. 场景复现
      项目描述:使用Springboot进行开发
      设置两个定时任务,每5s执行一次,并打印出其执行情况
      代码如下:

    @Component
    @Log4j2
    public class ScheduledTask {
        @Scheduled(cron = "0/5 * * * * ?")
        public void task1() throws InterruptedException {
            log.info("I am task11111111, current thread: {}", Thread.currentThread());
            while (true) {
                //模拟耗时任务,阻塞10s
                Thread.sleep(10000);
                break;
            }
        }
    
        @Scheduled(cron = "0/5 * * * * ?")
        public void task2() {
            log.info("I am task22222222, current thread: {}", Thread.currentThread());
        }
    }
    
    

      执行结果如下:

    2019-04-24 17:11:15.008  INFO 16868 --- [   scheduling-1] com.example.demo.task.ScheduledTask      : I am task22222222, current thread: Thread[scheduling-1,5,main]
    2019-04-24 17:11:15.009  INFO 16868 --- [   scheduling-1] com.example.demo.task.ScheduledTask      : I am task11111111, current thread: Thread[scheduling-1,5,main]
    2019-04-24 17:11:25.009  INFO 16868 --- [   scheduling-1] com.example.demo.task.ScheduledTask      : I am task22222222, current thread: Thread[scheduling-1,5,main]
    2019-04-24 17:11:30.002  INFO 16868 --- [   scheduling-1] com.example.demo.task.ScheduledTask      : I am task22222222, current thread: Thread[scheduling-1,5,main]
    2019-04-24 17:11:30.003  INFO 16868 --- [   scheduling-1] com.example.demo.task.ScheduledTask      : I am task11111111, current thread: Thread[scheduling-1,5,main]
    2019-04-24 17:11:40.004  INFO 16868 --- [   scheduling-1] com.example.demo.task.ScheduledTask      : I am task22222222, current thread: Thread[scheduling-1,5,main]
    

      由结果可见,task1与task2由同一个线程Thread[scheduling-1,5,main]执行,也即该定时任务默认使用单线程,并且由于task1阻塞了10s,导致本应5s执行一次的定时任务10s才执行一次。

    三. 解决方案
      网上有多种解决方案,以下列举两种
      方案一:使用@Async注解实现异步任务
        这种方式比较简单,在定时任务上加上@Async注解,注意:需启动类配合加上 @EnableAsync才会生效
      代码如下:

    @Component
    @Log4j2
    public class ScheduledTask {
        @Async
        @Scheduled(cron = "0/5 * * * * ?")
        public void task1() throws InterruptedException {
            log.info("I am task11111111, current thread: {}", Thread.currentThread());
            while (true) {
                //模拟耗时任务,阻塞10s
                Thread.sleep(10000);
                break;
            }
        }
    
        @Async
        @Scheduled(cron = "0/5 * * * * ?")
        public void task2() {
            log.info("I am task22222222, current thread: {}", Thread.currentThread());
        }
    }
    

      运行结果:

    2019-04-24 17:03:00.024  INFO 2152 --- [         task-1] com.example.demo.task.ScheduledTask      : I am task22222222, current thread: Thread[task-1,5,main]
    2019-04-24 17:03:00.024  INFO 2152 --- [         task-2] com.example.demo.task.ScheduledTask      : I am task11111111, current thread: Thread[task-2,5,main]
    2019-04-24 17:03:05.001  INFO 2152 --- [         task-3] com.example.demo.task.ScheduledTask      : I am task11111111, current thread: Thread[task-3,5,main]
    2019-04-24 17:03:05.001  INFO 2152 --- [         task-4] com.example.demo.task.ScheduledTask      : I am task22222222, current thread: Thread[task-4,5,main]
    2019-04-24 17:03:10.002  INFO 2152 --- [         task-5] com.example.demo.task.ScheduledTask      : I am task22222222, current thread: Thread[task-5,5,main]
    2019-04-24 17:03:10.003  INFO 2152 --- [         task-6] com.example.demo.task.ScheduledTask      : I am task11111111, current thread: Thread[task-6,5,main]
    

      由运行日志可见,定时每5s执行一次已生效,且每次任务使用的线程不一样,也即实现了多线程执行定时任务,不会出现任务等待现象。此方式据说默认线程池大小为100,要是任务不多的话有点大材小用了,所以我觉得第二种方式比较好。
      方案二:手动设置定时任务的线程池大小
      定时任务代码部分还原,不使用@Async注解,新增启动代码配置:

    @Configuration
    public class AppConfig implements SchedulingConfigurer {
        @Bean
        public Executor taskExecutor() {
        	//指定定时任务线程数量,可根据需求自行调节
            return Executors.newScheduledThreadPool(3);
        }
    
        @Override
        public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
            scheduledTaskRegistrar.setScheduler(taskExecutor());
        }
    }
    

      运行结果如下:

    2019-04-24 17:26:15.008  INFO 2164 --- [pool-1-thread-2] com.example.demo.task.ScheduledTask      : I am task22222222, current thread: Thread[pool-1-thread-2,5,main]
    2019-04-24 17:26:15.008  INFO 2164 --- [pool-1-thread-1] com.example.demo.task.ScheduledTask      : I am task11111111, current thread: Thread[pool-1-thread-1,5,main]
    2019-04-24 17:26:20.002  INFO 2164 --- [pool-1-thread-2] com.example.demo.task.ScheduledTask      : I am task22222222, current thread: Thread[pool-1-thread-2,5,main]
    2019-04-24 17:26:25.001  INFO 2164 --- [pool-1-thread-2] com.example.demo.task.ScheduledTask      : I am task22222222, current thread: Thread[pool-1-thread-2,5,main]
    2019-04-24 17:26:30.001  INFO 2164 --- [pool-1-thread-1] com.example.demo.task.ScheduledTask      : I am task11111111, current thread: Thread[pool-1-thread-1,5,main]
    2019-04-24 17:26:30.001  INFO 2164 --- [pool-1-thread-3] com.example.demo.task.ScheduledTask      : I am task22222222, current thread: Thread[pool-1-thread-3,5,main]
    2019-04-24 17:26:35.001  INFO 2164 --- [pool-1-thread-3] com.example.demo.task.ScheduledTask      : I am task22222222, current thread: Thread[pool-1-thread-3,5,main]
    

      由结果可见,第二种方式也实现了多线程任务调度。
    四. 总结
      两种方式各有优缺点:

    比较 方案一 方案二
    优点 注解方式使用简单,代码量少 配置灵活,线程数可控
    缺点 线程数不可控,可能存在资源浪费 需要增加编码

      留个坑,从日志上看@Async方式针对同一任务也是异步的,也即task1每5s会执行一次,但是方式二貌似对同一个任务不会生效,task1执行的时候需等待上一次执行结束才会触发,并没有每5s执行一次。关于这个现象,下次再琢磨…

    参考链接:https://segmentfault.com/a/1190000015267976

    展开全文
  • 前面的章节,用户通过绑定手机号的注册为会员,并可以补充完个人...常见的定时任务的解决方案以下几种: 右半部分基于 Java 或 Spring 框架即可支持定时任务的开发运行,左侧部分需要引入第三方框架支持。针对不...

    前面的章节,用户通过绑定手机号的注册为会员,并可以补充完个人信息,比如姓名、生日等信息,拿到用户的生日信息之后,就可以通过会员生日信息进行营销,此处就涉及到定时任务执行营销信息推送的问题。本篇就带你走入微服务下的定时任务的构建问题。

    定时任务选型

    常见的定时任务的解决方案有以下几种:

    右半部分基于 Java 或 Spring 框架即可支持定时任务的开发运行,左侧部分需要引入第三方框架支持。针对不同方案,作个简单介绍

    • XXL-JOB 是一个分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。任务调度与任务执行分离,功能很丰富,在多家公司商业产品中已有应用。官方地址:https://www.xuxueli.com/xxl-job/
    • Elastic-Job 是一个分布式调度解决方案,由两个相互独立的子项目 Elastic-Job-Lite 和 Elastic-Job-Cloud 组成。Elastic-Job-Lite 定位为轻量级无中心化解决方案,依赖 Zookeeper ,使用 jar 包的形式提供分布式任务的协调服务,之前是当当网 Java 应用框架 ddframe 框架中的一部分,后分离出来独立发展。
    • Quartz 算是定时任务领域的老牌框架了,出自 OpenSymphony 开源组织,完全由 Java 编写,提供内存作业存储和数据库作业存储两种方式。在分布式任务调度时,数据库作业存储在服务器关闭或重启时,任务信息都不会丢失,在集群环境有很好的可用性。
    • 淘宝出品的 TBSchedule 是一个简洁的分布式任务调度引擎,基于 Zookeeper 纯 Java 实现,调度与执行同样是分离的,调度端可以控制、监控任务执行状态,可以让任务能够被动态的分配到多个主机的 JVM 中的不同线程组中并行执行,保证任务能够不重复、不遗漏的执行。
    • Timer 和 TimerTask 是 Java 基础组件库的两个类,简单的任务尚可应用,但涉及到的复杂任务时,建议选择其它方案。
    • ScheduledExecutorService 在 ExecutorService 提供的功能之上再增加了延迟和定期执行任务的功能。虽然有定时执行的功能,但往往大家不选择它作为定时任务的选型方案。
    • [@EnableScheduling ]() 以注解的形式开启定时任务,依赖 Spring 框架,使用简单,无须 xml 配置。特别是使用 Spring Boot 框架时,更加方便。

    引入第三方分布式框架会增加项目复杂度,Timer、TimerTask 比较简单无法符合复杂的分布式定时任务,本次选择基于 注解的 [@EnableScheduling ]() 来开启我们的定时任务之旅。

    建立定时任务项目

    在 parking-project 父项目中新增基于 Spring Boot 的定时任务项目,命名为 parking-schedule-job,将基本的项目配置完毕,如端口、项目名称等等。

    新增项目启动类

    @SpringBootApplication
    @EnableScheduling
    public class ParkingScheduleJobApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ParkingScheduleJobApplication.class, args);
        }
    
    }
    

    新增任务执行类

    @Component
    @Slf4j
    public class UserBirthdayBasedPushTask {
    
      //每隔 5s 输出一次日志
        @Scheduled(cron = " 0/5 * * * * ?")
        public void scheduledTask() {
    
            log.info("Task running at = "  + LocalDateTime.now());
        }
    }
    

    一个简单的定时任务项目就此完成,启动项目,日志每隔 5s 输出一次。单实例执行没有问题,但仔细想想似乎不符合我们的预期:微服务架构环境下,进行横向扩展部署多实例时,每隔 5s 每个实例都会执行一次,重复执行会导致数据的混乱或糟糕的用户体验,比如本次基于会员生日推送营销短信时,用户会被短信轰炸,这肯定不是我们想看到的。即使部署了多代码实例,任务在同一时刻应当也只有任务执行才是符合正常逻辑的,而不能因为实例的增多,导致执行次数增多。

    分布式定时任务

    保证任务在同一时刻只有执行,就需要每个实例执行前拿到一个令牌,谁拥有令牌谁有执行任务,其它没有令牌的不能执行任务,通过数据库记录就可以达到这个目的。

    有小伙伴给出的是 A 方案,但有一个漏洞:当 select 指定记录后,再去 update 时,存在时间间隙,会导致多个实例同时执行任务,建议采用直接 update 的方案 B 更为可靠, update 更新到记录时会返回 1 ,否则是 0 。

    这种方案还需要编写数据更新操作方法,如果这些代码都不想写,有没有什么好办法?当然有,总会有"懒"程序员帮你省事,介绍一个组件 ShedLock,可以使我们的定时任务在同一时刻,最多执行一次。

    1、引入 ShedLock 相关的 jar ,这里依旧采用 MySQL 数据库的形式:

    <dependency>
           <groupId>net.javacrumbs.shedlock</groupId>
           <artifactId>shedlock-core</artifactId>
           <version>4.5.0</version>
    </dependency>
    <dependency>
            <groupId>net.javacrumbs.shedlock</groupId>
            <artifactId>shedlock-spring</artifactId>
            <version>4.5.0</version>
    </dependency>
    <dependency>
            <groupId>net.javacrumbs.shedlock</groupId>
            <artifactId>shedlock-provider-jdbc-template</artifactId>
            <version>4.5.0</version>
    </dependency>
    

    2、变更项目启动类,增加 [@EnableSchedulerLock ]() 注解,打开 ShedLock 获取锁的支持。

    @SpringBootApplication
    @EnableScheduling
    @EnableSchedulerLock(defaultLockAtMostFor = "30s")
    public class ParkingScheduleJobApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ParkingScheduleJobApplication.class, args);
        }
    
        @Bean
      //基于 Jdbc 的方式提供的锁机制
        public LockProvider lockProvider(DataSource dataSource) {
            return new JdbcTemplateLockProvider(dataSource);
        }
    
    }
    

    3、任务执行类的方法上,同样增加 [@SchedulerLock ]() 注解,并声明定时任务锁的名称,如果有多个定时任务,要确保名称的唯一性。

    4、新增名为 shedlock 的数据库,并新建 shedlock 数据表,表结构如下:

    CREATE TABLE shedlock(
          `NAME` varchar(64) NOT NULL DEFAULT '' COMMENT '任务名',
          `lock_until` timestamp(3) NULL DEFAULT NULL COMMENT '释放时间',
          `locked_at` timestamp(3) NULL DEFAULT NULL COMMENT '锁定时间',
          `locked_by` varchar(255) DEFAULT NULL COMMENT '锁定实例',
        PRIMARY KEY (name)
    )
    

    5、修改 application.properties 中数据库连接

    spring.datasource.driverClassName = com.mysql.cj.jdbc.Driver
    spring.datasource.url = jdbc:mysql://localhost:3306/shedlock?useUnicode=true&characterEncoding=utf-8
    spring.datasource.username = root
    spring.datasource.password = root
    

    6、完成以上步骤,基本配置已经完成,来测试一下,在多实例运行时,同一时刻是否只有一个实施在执行任务。

    //实例 1 的日志输出
    2020-03-07 21:20:45.007  INFO 67479 --- [   scheduling-1] c.m.p.s.j.t.UserBirthdayBasedPushTask    : Task running at = 2020-03-07T21:20:45.007
    2020-03-07 21:20:50.011  INFO 67479 --- [   scheduling-1] c.m.p.s.j.t.UserBirthdayBasedPushTask    : Task running at = 2020-03-07T21:20:50.011
    2020-03-07 21:21:15.009  INFO 67479 --- [   scheduling-1] c.m.p.s.j.t.UserBirthdayBasedPushTask    : Task running at = 2020-03-07T21:21:15.009
    2020-03-07 21:21:30.014  INFO 67479 --- [   scheduling-1] c.m.p.s.j.t.UserBirthdayBasedPushTask    : Task running at = 2020-03-07T21:21:30.014
    2020-03-07 21:21:40.008  INFO 67479 --- [   scheduling-1] c.m.p.s.j.t.UserBirthdayBasedPushTask    : Task running at = 2020-03-07T21:21:40.008
    
    //实例 2 的日志输出
    2020-03-07 21:21:20.011  INFO 67476 --- [   scheduling-1] c.m.p.s.j.t.UserBirthdayBasedPushTask    : Task running at = 2020-03-07T21:21:20.011
    2020-03-07 21:21:25.008  INFO 67476 --- [   scheduling-1] c.m.p.s.j.t.UserBirthdayBasedPushTask    : Task running at = 2020-03-07T21:21:25.008
    2020-03-07 21:21:30.006  INFO 67476 --- [   scheduling-1] c.m.p.s.j.t.UserBirthdayBasedPushTask    : Task running at = 2020-03-07T21:21:30.006
    2020-03-07 21:21:35.006  INFO 67476 --- [   scheduling-1] c.m.p.s.j.t.UserBirthdayBasedPushTask    : Task running at = 2020-03-07T21:21:35.006
    2020-03-07 21:21:45.008  INFO 67476 --- [   scheduling-1] c.m.p.s.j.t.UserBirthdayBasedPushTask    : Task running at = 2020-03-07T21:21:45.008
    

    可以看出每 5s 执行一次,是分布在两个实例中,同一时刻只有一个任务在执行,这与我们的预期是一致。数据库表记录(有两个定时任务的情况下):

    定时发送营销短信

    初步框架构建完成,现在填充据会员生日信息推送营销短信的功能。

    有小伙伴一听说定时任务,一定要找服务压力小的时间段来处理,索性放到凌晨。但凌晨让用户收到营销短信,真的好吗?所以还是要考虑产品的用户体验,不能盲目定时。

    前面服务调用章节我们已经学会了服务间的调用 ,这次是定时任务项目要调用会员服务里的方法,依旧采用 Feign 的方式进行。编写 MemberServiceClient 接口,与会员服务中的会员请求响应类保持一致

    @FeignClient(value = "member-service", fallback = MemberServiceFallback.class)
    public interface MemberServiceClient {
    
        @RequestMapping(value = "/member/list", method = RequestMethod.POST)
        public CommonResult<List<Member>> list() throws BusinessException;
    
        @RequestMapping(value = "/member/getMember", method = RequestMethod.POST)
        public CommonResult<Member> getMemberInfo(@RequestParam(value = "memberId") String memberId);
    
    }
    

    任务执行类编写业务逻辑,这里用到了 Member 实体,但这个实体是维护在会员服务中的,未对外公开。*对于一些公用类,可以抽取到一个公共项目中,供各项目间相互引用,而不是维护多份。*

    @Component
    @Slf4j
    public class UserBirthdayBasedPushTask {
    
        @Autowired
        MemberServiceClient memberService;
    
        @Scheduled(cron = " 0/5 * * * * ?")
        @SchedulerLock(name = "scheduledTaskName")
        public void scheduledTask() {
            CommonResult<List<Member>> members;
            try {
                members = memberService.list();
                List<Member> resp = members.getRespData();
    
                DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
                LocalDateTime time = LocalDateTime.now();
                String curTime = df.format(time);
                for (Member one : resp) {
            //当天生日的推送营销短信
                    if (curTime.equals(one.getBirth())) {
                        log.info(" send sms to  " + one.getPhone() );
                    }
                }
            } catch (BusinessException e) {
                log.error("catch exception " + e.getMessage());
            }
    
            log.info("Task running at = "  + LocalDateTime.now());
        }
    }
    

    启动会员服务、定时任务两个项目,测试业务逻辑的是否运行正常。定时任务执行时,发现出现异常:

    Caused by: org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of `com.mall.parking.common.bean.CommonResult` out of START_ARRAY token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `com.mall.parking.common.bean.CommonResult` out of START_ARRAY token at [Source: (PushbackInputStream); line: 1, column: 1]
    

    定位原因: CommonResult 对象中含有 Member List 对象集合,JSON 对象解析时的结构应该为 {},但返回值是[],肯定会解析异常。需要将 Feign 接口变更为原始的 JSON 字符串形式。

    //MemberServiceClient 接口方法变更为此
    @RequestMapping(value = "/member/list", method = RequestMethod.POST)
    public String list() throws BusinessException;
    

    任务执行类变更操作方式,如下

        @Scheduled(cron = " 0/5 * * * * ?")
        @SchedulerLock(name = "scheduledTaskName")
        public void scheduledTask() {
            try {
                String members = memberService.list();
                List<Member> array  = JSONArray.parseArray(members, Member.class);
    
                DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
                LocalDateTime time = LocalDateTime.now();
                String curTime = df.format(time);
                for (Member one : array) {
                    if (curTime.equals(one.getBirth())) {
                        log.info(" send sms to  " + one.getPhone() );
                    }
                }
            } catch (BusinessException e) {
                log.error("catch exception " + e.getMessage());
            }
    
            log.info("Task running at = "  + LocalDateTime.now());
        }
    

    再重新启动两个项目,测试任务已经可以正常执行。如果你的项目中还需要更多的定时任务的话,参照这种方式编写相应代码即可。

    本章节从定时任务入手,谈了几个定时任务的解决方案,接着引入分布式定时任务来完成我们的短信营销任务,完整的实施一次分布式定时任务。给你留下个动手题目吧:如果使用 elastic-job 组件,又当如何实现这个分布式定时任务呢?

    展开全文
  • 一、定时任务很多都是配置在配置文件中,但很多时候我们需要根据业务需要调整动态增删改定时任务,这里写出例子供大家参考。 (1)maven依赖 <dependency> <groupId>com.dangdang</groupId> ...

    一、定时任务很多都是配置在配置文件中,但很多时候我们需要根据业务需要调整动态增删改定时任务,这里写出例子供大家参考。
    (1)maven依赖

            <dependency>
                <groupId>com.dangdang</groupId>
                <artifactId>elastic-job-lite-core</artifactId>
                <version>2.1.5</version>
            </dependency>
            <dependency>
                <groupId>com.dangdang</groupId>
                <artifactId>elastic-job-lite-spring</artifactId>
                <version>2.1.5</version>
            </dependency>
    

    (2)application.properties文件设置

    elasticjob.serverlists=192.168.25.133:2181
    elasticjob.namespace=boot-job
    
    

    (3)初始化关于elastic的配置

    @Configuration
    @Data
    public class ElasticJobConfig {
    
        @Value("${elasticjob.serverlists}")
        private String serverLists;
    
        @Value("${elasticjob.namespace}")
        private String namespace;
    
        @Bean
        public ZookeeperConfiguration zConfig(){
            return new ZookeeperConfiguration(serverLists, namespace);
        }
    
        @Bean(initMethod = "init")
        public ZookeeperRegistryCenter registryCenter(ZookeeperConfiguration config){
            return new ZookeeperRegistryCenter(config);
        }
    
        @Bean
        public ElasticJobListener elasticJobListener(){
            return new ElasticJobListener(100, 100);
        }
    }
    
    

    (4)设置ElasticJobHandler的具体增删改的处理类

    @Component
    public class ElasticJobHandler {
        @Resource
        private ZookeeperRegistryCenter registryCenter;
    
        @Resource
        private ElasticJobListener elasticJobListener;
    
        private static LiteJobConfiguration.Builder simpleJobConfigBuilder(String jobName,
                                                                           Class<? extends SimpleJob> jobClass,
                                                                           int shardTotalCount,
                                                                           String cron,
                                                                           String id) {
            return LiteJobConfiguration.newBuilder(new SimpleJobConfiguration(
                    JobCoreConfiguration.newBuilder(jobName,cron,shardTotalCount).jobParameter(id).build(),jobClass.getCanonicalName()
            ));
        }
    
        /**
         * 添加一个定时任务
         */
        public void addJob(String jobName, SimpleJob jobInstance, String cron, Integer shardTotalCount, String id) {
            LiteJobConfiguration jobConfig = simpleJobConfigBuilder(jobName, jobInstance.getClass(), shardTotalCount, cron, id)
                    .overwrite(true).build();
            new SpringJobScheduler(jobInstance,registryCenter,jobConfig,elasticJobListener).init();
        }
    
        /**
         * 更新定时任务
         * @param jobName
         * @param cron
         */
        public void updateJob(String jobName, String cron) {
            JobRegistry.getInstance().getJobScheduleController(jobName).rescheduleJob(cron);
        }
    
        /**
         * 删除定时任务
         * @param jobName
         */
        public void removeJob(String jobName){
            JobRegistry.getInstance().getJobScheduleController(jobName).shutdown();
        }
    }
    

    (5)配置一下关于ElasticJobListener的监听器

    public class ElasticJobListener extends AbstractDistributeOnceElasticJobListener {
        public ElasticJobListener(long startedTimeoutMilliseconds, long completedTimeoutMilliseconds) {
            super(startedTimeoutMilliseconds,completedTimeoutMilliseconds);
        }
    
        @Override
        public void doBeforeJobExecutedAtLastStarted(ShardingContexts shardingContexts) {
    
        }
    
        @Override
        public void doAfterJobExecutedAtLastCompleted(ShardingContexts shardingContexts) {
    
        }
    }
    

    (6)这里写出cron定时任务工具类(此案例可以不需要此类)

    public class CronUtils {
    
        //private static final DateTimeFormatter SDF=DateTimeFormatter.ofPattern("ss mm HH dd MM ? yyyy");
        private static final ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
            @Override
            protected DateFormat initialValue() {
                return new SimpleDateFormat("ss mm HH dd MM ? yyyy");
            }
        };
    
        /**
         * eg "0 27 23 14 10 ? 2019"
         *
         * @param date
         * @return
         */
        public static String getCron(Date date) {
            if (Objects.isNull(date)) {
                return null;
            }
            return threadLocal.get().format(date);
        }
    
    
        public static void main(String[] args) {
            System.out.println(getCron(new Date()));
        }
    }
    

    (7)设置controller测试 效果

    @Controller
    @RequestMapping("/elastic/test")
    public class ElasticController {
    
        @Autowired
        private ElasticJobHandler jobHandler;
        @GetMapping("addJob")
        public void addJob(){
            jobHandler.addJob("111", new MyElasticJob(), "0 0/1 * * * ?", 1, "1");
            
        }
        @GetMapping("updateJob")
        public void updateJob(){
            jobHandler.updateJob("111","0 0/3 * * * ?");
           
        }
    
        @GetMapping("removeJob")
        public void removeJob(){
            jobHandler.removeJob("111");
        }
    
    }
    

    测试结果(分别执行完(任务的添加,修改,删除)的结果)

    执行第111@-@0@-@READY@-@192.168.25.1@-@28648任务2019-10-14 23:33:00
    执行第111@-@0@-@READY@-@192.168.25.1@-@28648任务2019-10-14 23:34:00
    执行第111@-@0@-@READY@-@192.168.25.1@-@28648任务2019-10-14 23:35:00
    执行第111@-@0@-@READY@-@192.168.25.1@-@28648任务2019-10-14 23:36:00
    执行第111@-@0@-@READY@-@192.168.25.1@-@28648任务2019-10-14 23:39:00
    
    执行第111@-@0@-@READY@-@192.168.25.1@-@28648任务2019-10-14 23:42:00
    2019-10-14 23:43:02.193  INFO 28648 --- [nio-8080-exec-9] org.quartz.core.QuartzScheduler          : Scheduler 111_$_NON_CLUSTERED shutting down.
    2019-10-14 23:43:02.193  INFO 28648 --- [nio-8080-exec-9] org.quartz.core.QuartzScheduler          : Scheduler 111_$_NON_CLUSTERED paused.
    2019-10-14 23:43:02.282  INFO 28648 --- [nio-8080-exec-9] org.quartz.core.QuartzScheduler          : Scheduler 111_$_NON_CLUSTERED shutdown complete.
    
    展开全文
  • 二、基于接口(SchedulingConfigurer) 前者相信大家都熟悉,但是实际使用中我们往往想从数据库中读取指定时间来动态执行定时任务,这时候基于接口的定时任务就派上用场了。 三、基于注解设定线程定时任务 一...

    目录

    一、静态:基于注解

    二、动态:基于接口

    三、多线程定时任务


    序言:

    使用SpringBoot创建定时任务非常简单,目前主要有以下三种创建方式:

    • 一、基于注解(@Scheduled)
    • 二、基于接口(SchedulingConfigurer) 前者相信大家都很熟悉,但是实际使用中我们往往想从数据库中读取指定时间来动态执行定时任务,这时候基于接口的定时任务就派上用场了。
    • 三、基于注解设定多线程定时任务

    一、静态:基于注解

    基于注解@Scheduled默认为单线程,开启多个任务时,任务的执行时机会受上一个任务执行时间的影响。

    1、创建定时器

    使用SpringBoot基于注解来创建定时任务非常简单,只需几行代码便可完成。 代码如下:

    @Configuration      //1.主要用于标记配置类,兼备Component的效果。
    @EnableScheduling   // 2.开启定时任务
    public class SaticScheduleTask {
        //3.添加定时任务
        @Scheduled(cron = "0/5 * * * * ?")
        //或直接指定时间间隔,例如:5秒
        //@Scheduled(fixedRate=5000)
        private void configureTasks() {
            System.err.println("执行静态定时任务时间: " + LocalDateTime.now());
        }
    }

    Cron表达式参数分别表示:

    (Cron具体意思和用法请看这篇文章:https://blog.csdn.net/u013987258/article/details/106690859

    • 秒(0~59) 例如0/5表示每5秒
    • 分(0~59)
    • 时(0~23)
    • 日(0~31)的某天,需计算
    • 月(0~11)
    • 周几( 可填1-7 或 SUN/MON/TUE/WED/THU/FRI/SAT)

    @Scheduled:除了支持灵活的参数表达式cron之外,还支持简单的延时操作,例如 fixedDelay ,fixedRate 填写相应的毫秒数即可。

    2、启动测试

    启动应用,可以看到控制台打印出如下信息:

     显然,使用@Scheduled 注解很方便,但缺点是当我们调整了执行周期的时候,需要重启应用才能生效,这多少有些不方便。为了达到实时生效的效果,可以使用接口来完成定时任务。

    二、动态:基于接口

    基于接口(SchedulingConfigurer)

    1、导入依赖包:

    <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>2.0.4.RELEASE</version>
        </parent>
    
        <dependencies>
            <dependency><!--添加Web依赖 -->
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency><!--添加MySql依赖 -->
                 <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
            <dependency><!--添加Mybatis依赖 配置mybatis的一些初始化的东西-->
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>1.3.1</version>
            </dependency>
            <dependency><!-- 添加mybatis依赖 -->
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.4.5</version>
                <scope>compile</scope>
            </dependency>
        </dependencies>

    2、添加数据库记录:

    开启本地数据库mysql,随便打开查询窗口,然后执行脚本内容,如下:

    DROP DATABASE IF EXISTS `socks`;
    CREATE DATABASE `socks`;
    USE `SOCKS`;
    DROP TABLE IF EXISTS `cron`;
    CREATE TABLE `cron`  (
      `cron_id` varchar(30) NOT NULL PRIMARY KEY,
      `cron` varchar(30) NOT NULL  
    );
    INSERT INTO `cron` VALUES ('1', '0/5 * * * * ?');

    然后在项目中的application.yml 添加数据源:

    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/socks
        username: root
        password: 123456

    3、创建定时器

    数据库准备好数据之后,我们编写定时任务,注意这里添加的是TriggerTask,目的是循环读取我们在数据库设置好的执行周期,以及执行相关定时任务的内容。
    具体代码如下:

    @Configuration      //1.主要用于标记配置类,兼备Component的效果。
    @EnableScheduling   // 2.开启定时任务
    public class DynamicScheduleTask implements SchedulingConfigurer {
    
        @Mapper
        public interface CronMapper {
            @Select("select cron from cron limit 1")
            public String getCron();
        }
    
        @Autowired      //注入mapper
        @SuppressWarnings("all")
        CronMapper cronMapper;
    
        /**
         * 执行定时任务.
         */
        @Override
        public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
    
            taskRegistrar.addTriggerTask(
                    //1.添加任务内容(Runnable)
                    () -> System.out.println("执行动态定时任务: " + LocalDateTime.now().toLocalTime()),
                    //2.设置执行周期(Trigger)
                    triggerContext -> {
                        //2.1 从数据库获取执行周期
                        String cron = cronMapper.getCron();
                        //2.2 合法性校验.
                        if (StringUtils.isEmpty(cron)) {
                            // Omitted Code ..
                        }
                        //2.3 返回执行周期(Date)
                        return new CronTrigger(cron).nextExecutionTime(triggerContext);
                    }
            );
        }
    
    }

    4、启动测试

    启动应用后,查看控制台,打印时间是我们预期的每10秒一次:

    然后打开Navicat ,将执行周期修改为每6秒执行一次,如图:

    查看控制台,发现执行周期已经改变,并且不需要我们重启应用,十分方便。如图:

    注意: 如果在数据库修改时格式出现错误,则定时任务会停止,即使重新修改正确;此时只能重新启动项目才能恢复。

    三、多线程定时任务

    基于注解设定多线程定时任务

    1、创建多线程定时任务

    //@Component注解用于对那些比较中立的类进行注释;
    //相对与在持久层、业务层和控制层分别采用 @Repository、@Service 和 @Controller 对分层中的类进行注释
    @Component
    @EnableScheduling   // 1.开启定时任务
    @EnableAsync        // 2.开启多线程
    public class MultithreadScheduleTask {
    
            @Async
            @Scheduled(fixedDelay = 1000)  //间隔1秒
            public void first() throws InterruptedException {
                System.out.println("第一个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName());
                System.out.println();
                Thread.sleep(1000 * 10);
            }
    
            @Async
            @Scheduled(fixedDelay = 2000)
            public void second() {
                System.out.println("第二个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "\r\n线程 : " + Thread.currentThread().getName());
                System.out.println();
            }
        }

    注: 这里的@Async注解很关键

    2、启动测试

    启动应用后,查看控制台:

    从控制台可以看出,第一个定时任务和第二个定时任务互不影响;

    并且,由于开启了多线程,第一个任务的执行时间也不受其本身执行时间的限制,所以需要注意可能会出现重复操作导致数据异常。

     

    展开全文
  • spring boot整合quartz实现定时任务

    千次阅读 2017-11-15 18:22:24
    最近收到了很多封邮件,都是想知道spring boot整合quartz如何实现多个定时任务的,由于本人生产上并没有使用到多个定时任务,这里给个实现的思路。 1、新建两个定时任务,如下: public class ScheduledJob ...
  • windows定时任务

    万次阅读 2018-01-26 09:58:54
    在windows系统中创建定时任务,与在linux中创建定时任务是不同的。因为平时会用到windows的定时任务,所以在这里做个记录,同事给小伙伴们一个参考。
  • 分布式定时任务对比

    万次阅读 多人点赞 2018-03-07 14:42:26
    1. 什么是分布式定时任务 把分散的,可靠性差的计划任务纳入统一的平台,并实现集群管理调度和分布式部署的一种定时任务的管理方式。叫做分布式定时任务。 2. 常见开源方案  elastic-job , xxl-job ,quartz ,...
  • Spring+Quartz框架实现定时任务(集群,分布式) ...在初期应用的访问量并不是那么大,一台服务器完全满足使用,但是随着用户量、业务量的逐日增加,应用中会有很多定时任务需要执行,一台服务器已经不能满足使用,
  • springboot定时任务

    千次阅读 2019-04-29 13:07:25
    一般来说,实际项目中,...如果我们将定时任务写在我们的项目中,就会面临一个麻烦,就是比如我们部署了3个实例,三个实例一启动,就会把定时任务都启动,那么在同一个时间点,定时任务会一起执行,也就是会执行3次...
  • SpringBoot定时任务

    千次阅读 2021-02-25 11:15:46
    文章目录玩转SpringBoot之定时任务详解序言一、静态:基于注解1、创建定时器2、启动测试二、动态:基于接口1、导入依赖包:2、添加数据库记录:3、创建定时器4、启动测试三、线程定时任务1、创建线程定时任务2、...
  • 全局定时任务-CronUtil 介绍 CronUtil通过一个全局的定时任务配置文件,实现统一的定时任务调度。 使用 1、配置文件 对于Maven项目,首先在src/main/resources/config下放入cron.setting文件(默认是这个路径...
  • 项目里面提供的时间是半小时或整点去执行Spring定时任务,查询数据库中哪些Schedule是满足要求的,然后去执行那些符合条件的任务。 一切功能表现正常,但是项目部署在服务器上后,用户反映在同一时间会收到两封...
  • golang 定时任务管理系统

    千次阅读 2018-01-30 10:41:46
     ·cron+,秒级定时,使任务执行更加灵活;  ·任务列表文件路径可以自定义,建议使用版本控制系统;  ·内置日志和监控系统,方便各位同学任意扩展;  ·平滑重加载配置文件,一旦配置文件变动,在不影响...
  • Python定时任务

    万次阅读 2015-12-09 10:23:29
    Python下实现定时任务的方式有很多种方式。 循环sleep: 这是一种最简单的方式,在循环里放入要执行的任务,然后sleep一段时间再执行。缺点是,不容易控制,而且sleep是个阻塞函数。 def timer(n): ''' 每n秒...
  • 分布式定时任务

    千次阅读 2019-08-02 16:46:10
    分布式定时任务 1,什么是分布式定时任务;2,为什么要采用分布式定时任务;3,怎么样设计实现一个分布式定时任务;4,当前比较流行的分布式定时任务框架; 1,什么是分布式定时任务: 首先,我们要了解计划...
  • 定时任务多机部署时的任务调度

    千次阅读 2019-01-14 10:57:40
    参加工作以来写过很多定时任务,总结下之前遇到印象较深的问题——任务在多服务器部署时任务重复执行或者由于数据被其他任务锁执行失败的问题。 解决方案: 让任务只在一台执行; 使用已框架的解决方案进行...
  • SpringBoot+Quartz实现定时任务

    千次阅读 2018-09-14 18:12:54
    通过Timer和Spring的scheduler实现定时任务可能会出现一旦定时任务中断后续的定时任务无法正常执行的问题,Quartz能够好的解决这一问题。 项目使用Maven管理,因此在使用之前需要在pom文件中添加依赖,如下:  ...
  • SpringBoot配置个Quartz定时任务

    千次阅读 2021-01-07 17:03:21
    很久之前使用过数据库的定时任务,如今换用了后端框架中的定时器,本篇介绍的就是Quartz,当然网上教程很多,在这里就简单配2个定时任务来保存回忆 一、导入依赖 来引入一些jar包,这里是采用maven引入,其实引入了...
  • Spring Boot 定时任务单线程和线程

    千次阅读 2019-03-15 17:19:41
    看到控制台输出的结果,所有的定时任务都是通过一个线程来处理的,我估计是在定时任务的配置中设定了一个SingleThreadScheduledExecutor,于是我看了源码,从ScheduledAnnotationBeanPostProcessor类开始一路找下去...
  • 网上也找了很多资料,但是没有什么合适的解决方案,很多文章提供的解决方案就是→重启项目!!!(我竟无言以对)。 然后呢,就自己想了个解决方案,当然只是完成了需求,在性能方面其实不好。希望更好解决方案的...
  • 分布式定时任务原理以及实现 一、单机指定时间执行定时任务实现方式 Timer运行机制 ScheduledThreadPoolExecutor的运行机制 原理图 Leader/Follower模式正在上传…重新上传取消 Timer和...
  • springboot定时任务@Scheduled执行

    千次阅读 2019-08-30 11:08:15
    在spring boot开发定时任务时遇到一个怪异的现象..我进行调试模式,在没有bug的情况下.执行了三 次才停止..如图: 原因是因为执行时间太短,在CronSequenceGenerator.class的next方法。 public Date next(Date ...
  • 如果将定时任务部署在一台服务器上,那么这个定时任务就是整个系统的单点,这台服务器出现故障的话会影响服务。对于可以冗余的任务(重复运行不影响服务),可以部署在台服务器上,让他们同时执行,这样就可以...
  • CentOS7的定时任务crond

    千次阅读 2018-05-01 10:30:43
    (一)定时任务简介 系统的定时任务并不难,它在我们的服务器上...但是在服务器上呢,我们很多的工作都不一定要人为手工的工作,而且一部分工作,像备份这样的工作,每天都需要进行,而且不能在上班的时候白天执...
  • Celery定时任务

    万次阅读 2016-03-20 16:59:52
    Celery定时任务配置启用Celery的定时任务需要设置CELERYBEAT_SCHEDULE 。 Celery的定时任务都由celery beat来进行调度。celery beat默认按照settings.py之中的时区时间来调度定时任务。创建定时任务一种创建定时...
  • 工程代码示例 : Spring Boot集成持久化Quartz定时任务管理和界面展示 工程地址 :https://github.com/tjfy1992/SpringBootQuartz 运行方法 Spring Boot工程已经集成了服务器。右键点击DemoApplication.java -&...
  • php定时任务

    千次阅读 2016-03-02 22:12:24
    php定时任务
  • Quartz定时任务

    千次阅读 2019-01-27 17:25:35
    1.任务job job就是想要实现的任务类,每一个job必须实现job接口,且实现接口中的 excute()方法。 2.触发器Trigger Trigger为你执行任务的触发器,可以设置特定时间执行该任务 Trigger主要包含SimpleTrigger和...
  • 使用linux下的crontab定时任务跑定时脚本 tags:定时任务 定时脚本 crontab linux定时脚本 linux 引言:应该许多人曾经好奇一些定时脚本是怎么做出来的。我们这次就来说一下定时脚本的那些事,其实网上教程...
  • 复制下方代码,在业务需要处调用(定时任务状态和执行周期被修改后),一定要在项目启动时后立刻执行一次库中全数据调用此方法,List<Cron> crons Cron中一定要业务类的包加类名...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 155,563
精华内容 62,225
关键字:

有很多定时任务