0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

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

OSC开源社区 来源:阿里云云原生 作者:姚辉(千习) 2022-11-04 09:36 次阅读

Spring 定时任务简介

Cloud Native

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

1. 在启动类上添加注解@EnableScheduling


@SpringBootApplication
@EnableScheduling  // 添加定时任务启动注解
public class SpringSchedulerApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringSchedulerApplication.class, args);
    }
}
2. 开发定时任务 Bean 并配置相应的定时注解@Scheduled

@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 运行任务。

a6912d5a-5b87-11ed-a3b6-dac502259ad0.jpg

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

a6a9892c-5b87-11ed-a3b6-dac502259ad0.jpg

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

DelegatingErrorHandlingRunnable:为业务方法运行异常进行包装处理,提供了自定义异常处理机制、解决 JDK 原生定时任务执行异常后任务失效问题。

ReschedulingRunnable:提供了扩展的定时模式支持,可支持基于 Trigger 接口自定义实现获取下次触发时间定时调度,默认提供的 Cron 定时通过此方式进行扩展实现。

定时模式

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

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

a6c1e63e-5b87-11ed-a3b6-dac502259ad0.jpg

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

a6d5c654-5b87-11ed-a3b6-dac502259ad0.jpg

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.");
}

异常统一处理

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


@Component
public class DemoTaskSchedulerCustomizer implements TaskSchedulerCustomizer {
    @Override
    public void customize(ThreadPoolTaskScheduler taskScheduler) {
        taskScheduler.setErrorHandler(new DemoErrorHandler());
    }


    private class DemoErrorHandler implements ErrorHandler {
        @Override
        public void handleError(Throwable throwable) {
            System.out.println("异常统一处理.");
        }
    }
}

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

Cloud Native

任务重复执行

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

a6f6147c-5b87-11ed-a3b6-dac502259ad0.jpg

示例代码如下:


@Component
@EnableScheduling
public class MyTask {
    /**
     * 每分钟的第30秒跑一次
     */
    @Scheduled(cron = "30 * * * * ?")
    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
    }
}
如上图所示,当任务触发时 3 个 server 会对任务抢锁,仅获得任务锁的 server 才能执行对应任务业务逻辑。当前的这个设计,仔细一点的同学可以发现,其实还是有可能导致任务重复执行的。比如任务执行的非常快,A 这台机器抢到锁,执行完任务后很快就释放锁了。B 这台机器后抢锁,还是会抢到锁,再执行一遍任务。

无管控无运维

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

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

任务的可视化动态配置

任务执行运行详细信息的可视化查看

任务执行日志、执行调用链、调度触发的可视化查询分析

业务应用间任务信息配置权限隔离

无业务失败通知能力

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

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

a70ba49a-5b87-11ed-a3b6-dac502259ad0.jpg

无在线排查分析能力

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

集群中任务对应时间点是跑在哪个机器上无从可知

需要在大量的业务应用日志中去检索对应时点的定时任务执行日志,需要自行对接日志服务改善

如果任务涉及多个跨服务调用,无法定位执行异常点或执行耗时点,需要自建全链路追踪来支持

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

Cloud Native

接下来主要讲下如何利用公有云上任务调度 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

接入优势

白屏管控和运维

提供白屏控制台可以动态新增、修改、启用、禁用任务,支持运行一次、原地重跑、重刷数据、停止任务、标记成功等运维操作。 a7287b56-5b87-11ed-a3b6-dac502259ad0.pnga7406996-5b87-11ed-a3b6-dac502259ad0.pnga75ad128-5b87-11ed-a3b6-dac502259ad0.png

可视化在线排查问题

支持执行记录查看、执行业务日志查询、执行全链路追踪。
a772929a-5b87-11ed-a3b6-dac502259ad0.pnga78a0f42-5b87-11ed-a3b6-dac502259ad0.png

丰富的报警通知

SchedulerX 提供丰富的报警通知能力,支持短信、电话、邮件、webhook 报警,支持报警联系人组和报警历史,可白屏动态配置。 a79fd7d2-5b87-11ed-a3b6-dac502259ad0.pnga7df0cc2-5b87-11ed-a3b6-dac502259ad0.png

其他优势

无改造成本的平台接入方案。

无需额外独立运维调度服务平台或其他第三方组件服务。

任务运行在集群环境中具备稳定高可靠支持,规避了原生框架存在的重复执行问题,具备故障自动转移能力。

在企业内多个团队可共享一套平台使用,通过命名空间和应用分组实现各团队任务配置数据隔离及环境隔离。

总结

Cloud Native

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

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • spring
    +关注

    关注

    0

    文章

    333

    浏览量

    14161

原文标题:说说Spring定时任务如何大规模企业级运用

文章出处:【微信号:OSC开源社区,微信公众号:OSC开源社区】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    SAS走进企业级存储应用

    SAS走进企业级存储应用串行SCSI(SAS)的出现已经有几年了。2005年,在主要的接口技术中,由于OEM服务器制造商和系统集成商开始提供串行SCSI解决方案,企业级存储市场将会显现革命性的进展
    发表于 11-13 21:58

    关于stm32系统定时任务的问题

    在用stm32做一个飞控程序时。需要用到上面这种系统循环定时任务,有一个问题:在System_Task_Loop函数里面,有1ms 、2ms、5ms的循环任务,但是他的这些定时是怎么得到的?是随便
    发表于 10-10 23:43

    Linux系统定时任务Crond

    会定期(默认每分钟检查一次)检查系统中是否有要执行的任务工作,如果有,便会根据其预先设定的定时任务规则自动执行该定时任务工作,这个crond定时任务服务就相当于我们平时早起使用的闹钟一
    发表于 07-05 06:22

    linux的循环定时任务

    linux循环定时任务
    发表于 05-20 14:59

    ucos iii定时任务有什么用?

    ucos iii 的定时任务有什么用,通过定时任务定时与普通的调用系统定时函数定时有什么区别?
    发表于 10-07 06:16

    定时任务的发展史是怎么样的

    定时任务是互联网行业里最常用的服务之一,本文给大家介绍定时任务在我司的发展历程。 linux系统中一般使用crontab命令来实现,在Java世界里,使用最广泛的就是quartz
    发表于 07-18 17:38 0次下载
    <b class='flag-5'>定时任务</b>的发展史是怎么样的

    SpringBoot如何实现动态增删启停定时任务

    spring boot项目中,可以通过 @EnableScheduling 注解和@Scheduled注解实现定时任务,也可以通过SchedulingConfigurer接口来实现定时任务。但是
    的头像 发表于 09-24 09:49 2601次阅读
    SpringBoot如何实现动态增删启停<b class='flag-5'>定时任务</b>

    Python定时任务的实现方式

    在日常工作中,我们常常会用到需要周期性执行的任务,一种方式是采用 Linux 系统自带的 crond 结合命令行实现。另外一种方式是直接使用Python。接下来整理的是常见的Python定时任务
    的头像 发表于 10-08 15:20 3226次阅读

    解析Golang定时任务库gron设计和原理

    正巧,最近看到了 gron 这个开源项目,它是用 Golang 实现一个并发安全的定时任务库。实现非常简单精巧,代码量也不多。今天我们就来一起结合源码看一下,怎样基于 Golang 的能力做出来一个【定时任务库】。
    的头像 发表于 12-15 13:57 950次阅读

    求一种SpringBoot定时任务动态管理通用解决方案

    SpringBoot的定时任务的加强工具,实现对SpringBoot原生的定时任务进行动态管理,完全兼容原生@Scheduled注解,无需对原本的定时任务进行修改
    的头像 发表于 02-03 09:49 551次阅读

    SpringBoot如何实现定时任务(上)

    SpringBoot创建定时任务的方式很简单,主要有两种方式:一、基于注解的方式(@Scheduled)二、数据库动态配置。实际开发中,第一种需要在代码中写死表达式,如果修改起来,又得重启会显得很麻烦;所以我们往往会采取第二种方式,可以直接从数据库中读取定时任务的指定执行
    的头像 发表于 04-07 14:51 1030次阅读
    SpringBoot如何实现<b class='flag-5'>定时任务</b>(上)

    Spring Boot中整合两种定时任务的方法

    Spring + SpringMVC 环境中,一般来说,要实现定时任务,我们有两中方案,一种是使用 Spring 自带的定时任务处理器 @Scheduled 注解,另一种就是使用第
    的头像 发表于 04-07 14:55 1243次阅读
    <b class='flag-5'>Spring</b> Boot中整合两种<b class='flag-5'>定时任务</b>的方法

    Spring Boot中如何使用定时任务

    本文介绍在 Spring Boot 中如何使用定时任务,使用非常简单,就不做过多说明了。
    的头像 发表于 04-12 10:56 782次阅读

    Linux如何使用cron进行定时任务的操作

    按计划执行命令对于计算机来说非常重要,因为假如我亲自去执行一些任务的话,可能会因为多方面因素不能按时执行,所以定时任务就显得非常重要了! cron就是一个能够执行定时任务的命令,其实该命令本身不难,下面小编带您详细了解!
    的头像 发表于 05-12 16:27 1804次阅读

    python定时任务实践

    由于程序需求,监测配置变化需要设置定时任务,每分钟执行一次,对任务持久化要求不高,不需要时可以关闭定时任务
    的头像 发表于 05-20 17:53 785次阅读
    python<b class='flag-5'>定时任务</b>实践