说说Spring定时任务如何大规模企业级运用
Spring 定时任务简介
Cloud Native
定时任务是业务应用开发中非常普遍存在的场景(如:每分钟扫描超时支付的订单,每小时清理一次数据库历史数据,每天统计前一天的数据并生成报表等等),解决方案很多,Spring 框架提供了一种通过注解来配置定时任务的解决方案,接入非常的简单,仅需如下两步:
// 添加定时任务启动注解
public class SpringSchedulerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringSchedulerApplication.class, args);
}
}
@Component
public class SpringScheduledProcessor {
/**
* 通过Cron表达式指定频率或指定时间
*/
@Scheduled(cron = "0/5 * * * * ?")
public void doSomethingByCron() {
System.out.println("do something");
}
/**
* 固定执行间隔时间
*/
@Scheduled(fixedDelay = 2000)
public void doSomethingByFixedDelay() {
System.out.println("do something");
}
/**
* 固定执行触发频率
*/
@Scheduled(fixedRate = 2000)
public void doSomethingByFixedRate() {
System.out.println("do something");
}
}
Spring 定时任务原理
Cloud Native
运行原理
Spring 定时任务核心逻辑主要在 spring-context 中的 scheduling 包中,其主要结构包括:
定时任务解析:通过 ScheduledTasksBeanDefinitionParser 对 XML 定义任务配置解析;也可通过 ScheduledAnnotationBeanPostProcessor对@Scheduled 注解进行任务解析(常见模式)。 定时任务注册登记:上述解析获得的 Task 任务配置会被注册登记至 ScheduledTaskRegistrar 中以备运行使用。 任务定时运行:完成所有任务注册登记后,会通过 TaskScheduler 正式地定时运行相关任务,底层通过 JDK 的 ScheduledExecutorService 运行任务。
DelegatingErrorHandlingRunnable:为业务方法运行异常进行包装处理,提供了自定义异常处理机制、解决 JDK 原生定时任务执行异常后任务失效问题。 ReschedulingRunnable:提供了扩展的定时模式支持,可支持基于 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)
@Async
public void test() {
System.out.println(DateUtil.now()+ " test.");
}
异常统一处理
public class DemoTaskSchedulerCustomizer implements TaskSchedulerCustomizer {
public void customize(ThreadPoolTaskScheduler taskScheduler) {
taskScheduler.setErrorHandler(new DemoErrorHandler());
}
private class DemoErrorHandler implements ErrorHandler {
public void handleError(Throwable throwable) {
System.out.println("异常统一处理.");
}
}
}
原生 Spring 定时任务在企业中遇到的问题
Cloud Native
任务重复执行
Spring 定时任务,只要有注解就会执行,在分布式场景下,所有机器代码一致,会导致同一个任务在多台机器上重复执行。一般的解决方案是抢锁触发,分布式锁实现形式可采用 DB、ZK、Redis 等方式。
示例代码如下:
public class MyTask {
/**
* 每分钟的第30秒跑一次
*/
"30 * * * * ?") (cron =
public void task1() throws Exception {
String lockName = "task1";
if (tryLock(lockName)) {
System.out.println("hello cron");
releaseLock(lockName);
} else {
return;
}
}
private boolean tryLock(String lockName) {
//TODO
return true;
}
private void releaseLock(String lockName) {
//TODO
}
}
无管控无运维
原生 Spring 定时任务没有控制台,无法动态的新增和修改定时任务,如果要修改定时任务的配置(比如每分钟跑一次改成每小时跑一次),必须修改代码重新发布应用。同时原生Spring定时任务也没有运维操作,不支持运行一次任务,任务失败了也不支持重跑任务。
任务的可视化动态配置 任务执行运行详细信息的可视化查看 任务执行日志、执行调用链、调度触发的可视化查询分析 业务应用间任务信息配置权限隔离
无业务失败通知能力
对于完整企业级定时任务运用方案中,报警通知能力必不可少,任务跑失败了需要及时通知到用户,否则可能产生故障。
无在线排查分析能力
定时任务在运行过程中会存在各种各样的问题,比如:执行失败、执行耗时、执行卡住等,这些都需要在后期实际运维去定位快速分析。在对应分析过程中没有高效在线排查能力的话将遇到很多棘手的问题:
集群中任务对应时间点是跑在哪个机器上无从可知 需要在大量的业务应用日志中去检索对应时点的定时任务执行日志,需要自行对接日志服务改善 如果任务涉及多个跨服务调用,无法定位执行异常点或执行耗时点,需要自建全链路追踪来支持
阿里云 Spring 定时任务企业级解决方案
Cloud Native
如何接入
对于 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
接入优势
白屏管控和运维
可视化在线排查问题
丰富的报警通知
其他优势
无改造成本的平台接入方案。 无需额外独立运维调度服务平台或其他第三方组件服务。 任务运行在集群环境中具备稳定高可靠支持,规避了原生框架存在的重复执行问题,具备故障自动转移能力。 在企业内多个团队可共享一套平台使用,通过命名空间和应用分组实现各团队任务配置数据隔离及环境隔离。
总结
Cloud Native
本文主要从 Spring 定时任务的运行机制进行剖析阐述,并对如何扩展框架原生能力以满足企业级生产环境运行定时任务所需各种场景提出了相应的建议,用户可作参考构建自己内部定时任务方案。同时就阿里云上提供的任务调度服务如何接入 Spring 定时任务的运行进行讲解,并简单展示了接入后所带来的企业级能力。最后欢迎有定时任务业务需求用户可先通过基础免费额度体验感受云上服务带来便捷。
附录
[1] spring scheduling 使用手册:
https://docs.spring.io/spring-framework/docs/5.2.x/spring-framework-reference/integration.html#scheduling-enable-annotation-support
[2] spring任务接入手册:
https://help.aliyun.com/document_detail/450857.html
往期推荐
已超1000万行代码,Java再次输给了Kotlin...
刚标准化就被废弃,谷歌:不爱了
微信扫码关注该文公众号作者