乐趣区

关于阿里云:说说-Spring-定时任务如何大规模企业级运用

作者: 姚辉(千习)

Spring 定时工作简介

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

  1. 在启动类上增加注解 @EnableScheduling
@SpringBootApplication
@EnableScheduling  // 增加定时工作启动注解
public class SpringSchedulerApplication {public static void main(String[] args) {SpringApplication.run(SpringSchedulerApplication.class, args);
    }
}
  1. 开发定时工作 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 定时工作原理

运行原理

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)
@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 定时工作在企业中遇到的问题

工作反复执行

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

示例代码如下:

@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 定时工作不反对报警告诉能力,如果要自研,能够参考上一章节中《异样对立解决》对工作失败的信息进行收集,构建相应的异样解决机制(包含对接各类报警平台进行异样音讯告诉解决,定义异样等级和类别进行不同的告诉策略),而后进行定时工作报警告诉。

无在线排查剖析能力

定时工作在运行过程中会存在各种各样的问题,比方:执行失败、执行耗时、执行卡住等,这些都须要在前期理论运维去定位疾速剖析。在对应剖析过程中没有高效在线排查能力的话将遇到很多辣手的问题:

  • 集群中工作对应工夫点是跑在哪个机器上无从可知
  • 须要在大量的业务利用日志中去检索对应时点的定时工作执行日志,须要自行对接日志服务改善
  • 如果工作波及多个跨服务调用,无奈定位执行异样点或执行耗时点,须要自建全链路追踪来反对

阿里云 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 定时工作的运行进行解说,并简略展现了接入后所带来的企业级能力。最初欢送有定时工作业务需要用户可先通过根底收费额度体验感触云上服务带来便捷。

附录

[1] spring scheduling 使用手册:

https://docs.spring.io/spring…

[2] spring 工作接入手册:

https://help.aliyun.com/docum…

退出移动版