说说Spring定时任务如何大规模企业级运用

本文涉及的产品
云解析 DNS,旗舰版 1个月
日志服务 SLS,月写入数据量 50GB 1个月
全局流量管理 GTM,标准版 1个月
简介: 聊下java体系中Spring提供的定时任务方案的原理及其企业化运用过程中的一些问题,如何让现有的spring定时任务满足企业级运行需要。

Spring定时任务简介

定时任务是业务应用开发中非常普遍存在的场景(如:每分钟扫描超时支付的订单,每小时清理一次数据库历史数据,每天统计前一天的数据并生成报表等等),解决方案很多,Spring框架提供了一种通过注解来配置定时任务的解决方案,接入非常的简单,仅需如下两步:

  1. 在启动类上添加注解@EnableScheduling
@SpringBootApplication@EnableScheduling// 添加定时任务启动注解publicclassSpringSchedulerApplication {
publicstaticvoidmain(String[] args) {
SpringApplication.run(SpringSchedulerApplication.class, args);
    }
}
  1. 开发定时任务Bean并配置相应的定时注解@Scheduled
@ComponentpublicclassSpringScheduledProcessor {
/*** 通过Cron表达式指定频率或指定时间*/@Scheduled(cron="0/5 * * * * ?")
publicvoiddoSomethingByCron() {
System.out.println("do something");
    }
/*** 固定执行间隔时间*/@Scheduled(fixedDelay=2000)
publicvoiddoSomethingByFixedDelay() {
System.out.println("do something");
    }
/*** 固定执行触发频率*/@Scheduled(fixedRate=2000)
publicvoiddoSomethingByFixedRate() {
System.out.println("do something");
    } 
}

Spring定时任务原理

运行原理

Spring定时任务核心逻辑主要在spring-context中的scheduling包中,其主要结构包括:

  • 定时任务解析:通过ScheduledTasksBeanDefinitionParser对XML定义任务配置解析;也可通过ScheduledAnnotationBeanPostProcessor对@Scheduled注解进行任务解析(常见模式)。
  • 定时任务注册登记:上述解析获得的Task任务配置会被注册登记至ScheduledTaskRegistrar中以备运行使用。
  • 任务定时运行:完成所有任务注册登记后,会通过TaskScheduler正式地定时运行相关任务,底层通过JDK的ScheduledExecutorService运行任务。

业务逻辑会将被包装在ScheduledMethodRunnable类中,其他包含了待执行的目标业务对象Bean和业务方法,该Runnable对象在运行时会被提交至ScheduledExecutorService调度线程池完成任务的定时运行。

从上图可以看到真正要运行的业务逻辑ScheduledMethodRunnable会被ReschedulingRunnable、DelegatingErrorHandlingRunnable做了代理扩展,这两层代理扩展具有如下意义

  • DelegatingErrorHandlingRunnable:为业务方法运行异常进行包装处理,提供了自定义异常处理机制、解决JDK原生定时任务执行异常后任务失效问题。
  • ReschedulingRunnable:提供了扩展的定时模式支持,可支持基于Trigger接口自定义实现获取下次触发时间定时调度,默认提供的CRON定时通过此方式进行扩展实现。

定时模式

Spring定时任务Task类的模式主要可分为两类:IntervalTask和TriggerTask。前者表示固定频率间隔执行,后者则采用Trigger触发器模式实现定时调度,cron表达式配置为该模式实现。

  • FixedDelay:按固定延迟频率执行,任务下一次触发时间=上一次执行结束时间+Delay延迟时间。


  • FixedRate:按固定频率触发执行,任务下一次触发时间=上一次触发时间+Delay延迟时间。如果上一次执行方法不结束会阻塞下一次任务执行。

  • Cron表达式:按Cron表达式计算下一次触发时间,任务下一次触发时间=cron(上一次执行结束时间)。

进阶扩展

线程池运行

默认配置下底层运行的线程池为单线程,单线程的运行模型在任务量较多且触发频率较高的情况下,一旦某个任务发生阻塞会导致所有后续定时任务运行阻断,这对业务运行带来严重隐患。常见可采用如下方式:

  • 配置定时执行线程池:常见基于配置Spring Boot配置(spring.task.scheduling.pool.size=线程数),线程数大小取决于任务数及调度频率合理配置。
  • 配置异步任务:在spring context中的scheduling模块下提供了@EnableAsync和@Async,可用于开启任务异步执行,实现定时调度线程池非阻塞运行。该模式下存在一些不足之处:异常处理需要走异步调用的AsyncUncaughtExceptionHandler异常处理接口实现,同步/异步定时任务异常处理机制不统一,另外异步模式增加了业务应用的线程开销。
@Scheduled(fixedDelay=2000)
@Asyncpublicvoidtest() {
System.out.println(DateUtil.now()+" test.");
}

异常统一处理

定时任务运行可设置统一异常处理,基于ErrorHandler接口开发对应异常处理实现类。对应的异常实现处理类需要注入到核心的ThreadPoolTaskScheduler中,用户可以通过自定义TaskSchedulerCustomizer方式来实现ErrorHandler自定义异常处理Bean注入至ThreadPoolTaskScheduler中。

@ComponentpublicclassDemoTaskSchedulerCustomizerimplementsTaskSchedulerCustomizer {
@Overridepublicvoidcustomize(ThreadPoolTaskSchedulertaskScheduler) {
taskScheduler.setErrorHandler(newDemoErrorHandler());
    }
privateclassDemoErrorHandlerimplementsErrorHandler {
@OverridepublicvoidhandleError(Throwablethrowable) {
System.out.println("异常统一处理.");
        }
    }
}

原生Spring定时任务在企业中遇到的问题

任务重复执行

Spring定时任务,只要有注解就会执行,在分布式场景下,所有机器代码一致,会导致同一个任务在多台机器上重复执行。一般的解决方案是抢锁触发,分布式锁实现形式可采用DB、ZK、Redis等方式。

示例代码如下

@Component@EnableSchedulingpublicclassMyTask {
/*** 每分钟的第30秒跑一次*/@Scheduled(cron="30 * * * * ?")
publicvoidtask1() throwsException {
StringlockName="task1";
if (tryLock(lockName)) {
System.out.println("hello cron");
releaseLock(lockName);
        } else {
return;
        }
    }
privatebooleantryLock(StringlockName) {
//TODOreturntrue;
    }
privatevoidreleaseLock(StringlockName) {
//TODO    }
}

如上图所示,当任务触发时3个server会对任务抢锁,仅获得任务锁的server才能执行对应任务业务逻辑。当前的这个设计,仔细一点的同学可以发现,其实还是有可能导致任务重复执行的。比如任务执行的非常快,A这台机器抢到锁,执行完任务后很快就释放锁了。B这台机器后抢锁,还是会抢到锁,再执行一遍任务。

无管控无运维

原生Spring定时任务没有控制台,无法动态的新增和修改定时任务,如果要修改定时任务的配置(比如每分钟跑一次改成每小时跑一次),必须修改代码重新发布应用。同时原生Spring定时任务也没有运维操作,不支持运行一次任务,任务失败了也不支持重跑任务。

如果要自研的可视化控制台来实现整套任务可视化管控体系,需要一定的前后端研发成本和服务部署成本投入。对于需要自建的用户而言,可参考以下需求功能进行自有平台建设:

  • 任务的可视化动态配置
  • 任务执行运行详细信息的可视化查看
  • 任务执行日志、执行调用链、调度触发的可视化查询分析
  • 业务应用间任务信息配置权限隔离

无业务失败通知能力

对于完整企业级定时任务运用方案中,报警通知能力必不可少,任务跑失败了需要及时通知到用户,否则可能产生故障。

原生Spring定时任务不支持报警通知能力,如果要自研,可以参考上一章节中《异常统一处理》对任务失败的信息进行收集,构建相应的异常处理机制(包括对接各类报警平台进行异常消息通知处理,定义异常等级和类别进行不同的通知策略),然后进行定时任务报警通知。

无在线排查分析能力

定时任务在运行过程中会存在各种各样的问题,比如:执行失败、执行耗时、执行卡住等,这些都需要在后期实际运维去定位快速分析。在对应分析过程中没有高效在线排查能力的话将遇到很多棘手的问题

  • 集群中任务对应时间点是跑在哪个机器上无从可知
  • 需要在大量的业务应用日志中去检索对应时点的定时任务执行日志,需要自行对接日志服务改善
  • 如果任务涉及多个跨服务调用,无法定位执行异常点或执行耗时点,需要自建全链路追踪来支持

阿里云Spring定时任务企业级解决方案

接下来主要讲下如何利用公有云上任务调度SchedulerX轻松接入基于Spring开发的定时任务。前面聊了基于Spring原生功能在使用过程中面临的问题及需要自行处理解决的相关方案,可以看到仅针对企业级最基础的运用场景下就需要花费较多的改造投入及相关服务后续运维投入。通过接入SchedulerX任务调度平台,原本Spring定时任务使用者可无缝且0改造获得企业级运用所需能力,同时降低了自研部署运维定时服务相关组件的技术成本。

如何接入

对于SchedulerX新用户而言接入仅需三步(参考附件接入手册)

  • 依赖SchedulerX的Spring Boot版SDK完成调度平台接入(版本>=1.7.2,老用户仅升级SDK版本即可)
  • 配置文件添加配置项,配置开启后Spring定时调度器将不运行相关任务(未配置情况下,不会主动接管原Spring定时任务运行,在配置开启前不会影响原本定时任务业务运行)
# 配置表示由SchedulerX接管Spring定时任务运行
spring.schedulerx2.task.scheduling.scheduler=schedulerx
  • 控制台上在对应应用分组下创建任务配置定时触发。也可以选择开启自动同步任务配置方式(可选)
# 自动同步Spring定时任务至调度平台,无需单独手动创建(默认不开启)
spring.schedulerx2.task.scheduling.sync=true

接入优势

白屏管控和运维

提供白屏控制台可以动态新增、修改、启用、禁用任务,支持运行一次、原地重跑、重刷数据、停止任务、标记成功等运维操作。

可视化在线排查问题

支持执行记录查看、执行业务日志查询、执行全链路追踪。

丰富的报警通知

Schedulerx提供丰富的报警通知能力,支持短信、电话、邮件、webhook报警,支持报警联系人组和报警历史,可白屏动态配置。

其他优势

  • 无改造成本的平台接入方案。
  • 无需额外独立运维调度服务平台或其他第三方组件服务。
  • 任务运行在集群环境中具备稳定高可靠支持,规避了原生框架存在的重复执行问题,具备故障自动转移能力。
  • 在企业内多个团队可共享一套平台使用,通过命名空间和应用分组实现各团队任务配置数据隔离及环境隔离。

总结

本文主要从Spring定时任务的运行机制进行剖析阐述,并对如何扩展框架原生能力以满足企业级生产环境运行定时任务所需各种场景提出了相应的建议,用户可作参考构建自己内部定时任务方案。同时就阿里云上提供的任务调度服务如何接入Spring定时任务的运行进行讲解,并简单展示了接入后所带来的企业级能力。最后欢迎有定时任务业务需求用户可先通过基础免费额度体验感受云上服务带来便捷。

附录


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
10天前
|
XML 安全 Java
|
4月前
|
资源调度 Java 调度
Spring Cloud Alibaba 集成分布式定时任务调度功能
定时任务在企业应用中至关重要,常用于异步数据处理、自动化运维等场景。在单体应用中,利用Java的`java.util.Timer`或Spring的`@Scheduled`即可轻松实现。然而,进入微服务架构后,任务可能因多节点并发执行而重复。Spring Cloud Alibaba为此发布了Scheduling模块,提供轻量级、高可用的分布式定时任务解决方案,支持防重复执行、分片运行等功能,并可通过`spring-cloud-starter-alibaba-schedulerx`快速集成。用户可选择基于阿里云SchedulerX托管服务或采用本地开源方案(如ShedLock)
136 1
|
2月前
|
Java BI 调度
Java Spring的定时任务的配置和使用
遵循上述步骤,你就可以在Spring应用中轻松地配置和使用定时任务,满足各种定时处理需求。
154 1
|
2月前
|
存储 Java API
简单两步,Spring Boot 写死的定时任务也能动态设置:技术干货分享
【10月更文挑战第4天】在Spring Boot开发中,定时任务通常通过@Scheduled注解来实现,这种方式简单直接,但存在一个显著的限制:任务的执行时间或频率在编译时就已经确定,无法在运行时动态调整。然而,在实际工作中,我们往往需要根据业务需求或外部条件的变化来动态调整定时任务的执行计划。本文将分享一个简单两步的解决方案,让你的Spring Boot应用中的定时任务也能动态设置,从而满足更灵活的业务需求。
134 4
|
5月前
|
资源调度 Java 调度
Spring Cloud Alibaba 集成分布式定时任务调度功能
Spring Cloud Alibaba 发布了 Scheduling 任务调度模块 [#3732]提供了一套开源、轻量级、高可用的定时任务解决方案,帮助您快速开发微服务体系下的分布式定时任务。
15025 35
|
4月前
|
Java 开发者 Spring
Spring Boot实战宝典:揭秘定时任务的幕后英雄,让业务处理如流水般顺畅,轻松驾驭时间管理艺术!
【8月更文挑战第29天】在现代应用开发中,定时任务如数据备份、报告生成等至关重要。Spring Boot作为流行的Java框架,凭借其强大的集成能力和简洁的配置方式,为开发者提供了高效的定时任务解决方案。本文详细介绍了如何在Spring Boot项目中启用定时任务支持、编写定时任务方法,并通过实战案例展示了其在业务场景中的应用,同时提供了注意事项以确保任务的正确执行。
55 0
|
4月前
|
Dubbo Java 调度
揭秘!Spring Cloud Alibaba的超级力量——如何轻松驾驭分布式定时任务调度?
【8月更文挑战第20天】在现代微服务架构中,Spring Cloud Alibaba通过集成分布式定时任务调度功能解决了一致性和可靠性挑战。它利用TimerX实现任务的分布式编排与调度,并通过`@SchedulerLock`确保任务不被重复执行。示例代码展示了如何配置定时任务及其分布式锁,以实现每5秒仅由一个节点执行任务,适合构建高可用的微服务系统。
72 0
|
5月前
|
SQL Java 调度
实时计算 Flink版产品使用问题之使用Spring Boot启动Flink处理任务时,使用Spring Boot的@Scheduled注解进行定时任务调度,出现内存占用过高,该怎么办
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
5月前
|
XML Java Linux
Spring Task 定时任务没有定时执行是为什么?
Spring Task 定时任务没有定时执行是为什么?
77 2