spring调度注解@Scheduled方式(含分布式)

 更新时间:2024年11月20日 17:19:25   作者:sjzwangxufeng  
文章介绍了Java中任务调度的几种常见方法,包括JDK原生的Timer、ScheduledThreadPoolExecutor和Spring的@Scheduled注解,文章还讨论了如何在分布式环境中实现任务调度,并介绍了一些开源的分布式任务调度解决方案,如Quartz和XXL-JOB

简述

任务调度就是在给定的时间或固定频率,执行业务逻辑,是比较常见的功能需求。

解决方案有jdk原生的Timer、ScheduledThreadPoolExecutor等,这些类常适用于一些内嵌的业务逻辑场景。

本文主要介绍注解@Scheduled,以上都是单进程解决方案,经过适当改造,也可以适用于分布式场景,可以满足大多数调度业务场景,具体实现思路下面会做简单叙述。

配置

开启

项目开启调度功能,需要先添加注解@EnableScheduling,否则调度注解@Scheduled就不起作用。

线程池

既然是任务运行,就会涉及线程处理,如果有不同类型的任务,也会出现并行处理,对线程的合理管理,就离不开线程池,以下是线程池配置整理

(1) 不配置(默认)

如果不做任何配置处理,spring-boot 会默认自动构建一个ThreadPoolTaskScheduler线程池类bean, 来管理这些运行任务的线程,默认线程池的具体参数值,可参考TaskSchedulingProperties类定义的默认值,如下:

// pool
private int size = 1;

// thread
private String threadNamePrefix = "scheduling-";

通过源码知道,这个默认线程池,内部实际由jdk的ScheduledThreadPoolExecutor类处理,该类采用无限容量队列,这也就限制了它的最大线程数不会超过1个,如果有耗时的并行任务,就不能满足要求,通常情况下,需要根据业务场景重新配置这些参数。

(2) spring配置

spring-boot项目已提供TaskSchedulingAutoConfiguration类,由它自动加载线程池配置参数,并构建ThreadPoolTaskScheduler线程池类bean,以下是约定的配置项:

spring:
  task:
    scheduling:
      threadNamePrefix: my-scheduler-task-
      pool:
        size: 3

线程池的大小,依据配置调度注解@Scheduled任务的数量,原则上有几种任务就需要几个线程,否则就会出现相互影响,长耗时任务占用线程,导致短耗时任务不能正常运行。

(3) java代码配置

调度任务不像@Async异常处理,它只有一个线程池,一般情况不用这种配置方式,以下是简单例子。

@Configuration
public class ScheduleConfig {
	
    private static final String THREAD_NAME_PREFIX = "my-scheduler-task-";	

    @Bean("myTaskScheduler")
    public ThreadPoolTaskScheduler getThreadPoolTaskScheduler() {
    	ThreadPoolTaskScheduler result = new ThreadPoolTaskScheduler();
    	result.setThreadNamePrefix(THREAD_NAME_PREFIX);
    	result.setPoolSize(3);
    	return result;
    }
}

调度规则

@Scheduled包含参数:

  • cron:定时任务,按cron表达式规则,定时运行任务,例如,每5分钟运行一次: 0/5 * * * * ?
  • fixedDelay:按固定间隔执行,就是两个相邻任务,前一个任务结束到下一个任务开始的间隔时间,单位: 毫秒。
  • fixedRate:按固定频率执行任务,单位: 毫秒。
  • initialDelay:系统启动后,延时多长时间运行第一次任务,单位: 毫秒。

其中:cron, fixedDelay, fixedRate 配置参数,只能三选一。

分布式

现在系统大多在分布式环境部署,就需要考虑多实例部署如何协调执行任务问题,以下是常见的解决方案,以及个人的思考。

第三方

目前第三方的开源方案,有早期比较经典的Quartz,近几年版本迭代不太活跃,也有后起之秀XXL-JOB 版本迭代比较活跃,也是目前很多公司推崇的解决方案,对任务的管理、监控、日志等功能比较齐全,可以参考其官方,这里就不再多述。

自处理

尽管上面开源的第三方解决方案,已经足够成熟、完善,但相对来说,还是有些重,对于一些系统规模不是很大,一些简单的任务调度需求,完全可以进行简单改造来满足这些任务调度功能。

尽管简单,它一样可以很实用、很健壮,以下是2种借助redis的处理思路。

(1) @Scheduled为主,redis为辅

通过@Scheduled注解的调度任务,在分布式环境运行,一个明显的问题,就是同一个任务,可能会在多个机器同时并发执行,如何避免,很自然就想到通过redis分布式锁处理,来避免任务并发执行,锁定时间可以设置0.75个执行周期,以下是伪码

	@Scheduled(fixedDelay = 60000, initialDelay = 1000)
	public void task1() {
		
		// 锁定
		boolean isLock = redisLock.lock("my-task-1", 60000 * 0.75);
		if (!isLock) return;
		
		// 任务逻辑
		doSomething();
	}

可以看出,这种方式,任务周期误差比较大,比较粗糙,特点就是逻辑简单,适用于精度要求较低的场景。

(2) redis为主,@Scheduled为辅

由于通过@Scheduled来配置执行周期,在分布式环境,很难保证周期的精度,这时候可以把@Scheduled仅作为尝试申请执行的一个定时扫描任务,真实的执行周期由redis的过期时间来管理,这种方式,任务周期精度就会好很多,以下是伪码:

按固定频率执行:

	/*
	 * redis为主,@Scheduled为辅(按固定频率执行任务)
	 * 
	 * note:
	 * a. @Scheduled注解中fixedDelay,该参数仅作为尝试申请执行任务, 通常可以设置小些。
	 * b. 任务执行周期或间隔,值为redisLock锁定的时间。
	 * 
	 */
	@Scheduled(fixedDelay = 5000, initialDelay = 1000)
	public void task2() {
		
		// 锁定
		boolean isLock = redisLock.lock("my-task-2", 真实任务周期);
		if (!isLock) return;
		
		// 任务逻辑
		doSomething();
		
	}

按固定间隔执行:

	/*
	 * redis为主,@Scheduled为辅(按固定间隔执行)
	 * 
	 * note:
	 * a. @Scheduled注解中fixedDelay,该参数仅作为尝试申请执行任务, 通常可以设置小些。
	 * b. 任务执行周期或间隔,值为redisLock锁定的时间。
	 * 
	 */
	@Scheduled(fixedDelay = 5000, initialDelay = 1000)
	public void task3() {
		
		// 锁定1: 避免任务并行
		boolean isLock = redisLock.lock("my-task-3", 真实任务间隔);
		if (!isLock) return;
		
		// 任务逻辑
		doSomething();
		
		// 锁定2: 间隔时间
		redisLock.expire("my-task-3", 真实任务间隔);
		
	}

按cron表达式执行:可通过注解@Scheduled参数fixedDelay,来调整周期精度。

	/*
	 * redis为主,@Scheduled为辅(cron表达)
	 * 
	 * note:
	 * a. @Scheduled注解中fixedDelay,该参数仅作为尝试申请执行任务, 通常可以设置小些。
	 * b. 任务执行周期或间隔,值为redisLock锁定的时间。
	 * c. 由CronHelper解析cron表达式,计算下一次运行间隔时间
	 */
	@Scheduled(fixedDelay = 5000, initialDelay = 1000)
	public void task4()  {
		
		// 锁定
		boolean isLock = redisLock.lock("my-task-4", CronHelper.getNextDelayTime());
		if (!isLock) return;
		
		// 任务逻辑
		doSomething();		
	}

以上只伪码,可以看出改造成本比较少,也足够灵活,其中RedisLock可以参考前面整理的文章:分布式锁-java,至于CronHelper类,网上应该有类似资源,也不妨自己实现一下,应该比排序算法有趣的多。

再就是任务的运行,不能保证负载均衡,如果的确有这方面需求,通过redis队列也可以实现,逻辑也不会太复杂。

个人认为:

这种自处理方式,借助redis还是可以保障它的高可用性、并发性能,它的主要缺陷,就是代码语义不够清晰,在维护上,容易受注解@Scheduled定时参数影响,实际业务场景,尽量封装一下,提高可读性。

常见问题

(1) 线程池的大小,建议几种任务就几个线程,多了也浪费,如果太小,任务耗时长时,就会出现任务间干扰。

(2) 如果任务有严格的并行限制,可以通过分布式锁防护一下。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 一篇文章带你入门Java继承

    一篇文章带你入门Java继承

    这篇文章主要介绍了Java继承概念详细解读,涉及继承的概念,合成的语法等相关内容,具有一定借鉴价值,需要的朋友可以参考下
    2021-08-08
  • SpringBoot+slf4j线程池全链路调用日志跟踪问题及解决思路(二)

    SpringBoot+slf4j线程池全链路调用日志跟踪问题及解决思路(二)

    本文主要给大家介绍如何实现子线程中的traceId日志跟踪,本文通过封装Callable为例给大家介绍的非常详细,需要的朋友一起看看吧
    2021-05-05
  • Java基于循环递归回溯实现八皇后问题算法示例

    Java基于循环递归回溯实现八皇后问题算法示例

    这篇文章主要介绍了Java基于循环递归回溯实现八皇后问题算法,结合具体实例形式分析了java的遍历、递归、回溯等算法实现八皇后问题的具体步骤与相关操作技巧,需要的朋友可以参考下
    2017-06-06
  • mybatisPlus实现倒序拼接字符串

    mybatisPlus实现倒序拼接字符串

    这篇文章主要介绍了mybatisPlus实现倒序拼接字符串方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01
  • Java中的String、StringBuilder、StringBuffer三者的区别详解

    Java中的String、StringBuilder、StringBuffer三者的区别详解

    这篇文章主要介绍了Java中的String、StringBuilder、StringBuffer三者的区别详解,就是String,StringBuilder以及StringBuffer这三个类之间有什么区别呢,自己从网上搜索了一些资料,有所了解了之后在这里整理一下,便于大家观看,需要的朋友可以参考下
    2023-12-12
  • java  实现输出随机图片实例代码

    java 实现输出随机图片实例代码

    这篇文章主要介绍了java 实现输出随机图片实例代码的相关资料,需要的朋友可以参考下
    2017-06-06
  • Java ES(Elasticsearch) 中的and 和 or 查询

    Java ES(Elasticsearch) 中的and 和 or 查

    Elasticsearch 是一个分布式、高扩展、高实时的搜索与数据分析引擎,es中match查询中,查询字符串分词后,默认是or或者的关系,这篇文章主要介绍了ES 中的and 和 or 查询,需要的朋友可以参考下
    2022-11-11
  • spring cloud 阿波罗 apollo 本地开发环境搭建过程

    spring cloud 阿波罗 apollo 本地开发环境搭建过程

    Apollo(阿波罗)是携程框架部门研发的配置管理平台,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性
    2018-01-01
  • Java利用PDFBox实现PDF文档基本操作

    Java利用PDFBox实现PDF文档基本操作

    这篇文章主要为大家详细介绍了java如何利用PDFBox实现PDF文档基本操作,例如创建PDF文档、加载PDF文档、获取总页数等,需要的小伙伴可以参考下
    2023-11-11
  • MyBatis中的properties配置(推荐)

    MyBatis中的properties配置(推荐)

    这篇文章给大家介绍了MyBatis中的properties配置,非常不错,具有参考借鉴价值,需要的朋友参考下吧
    2017-12-12

最新评论