下面说说Spring的定时任务如何在企业大规模使用,每天统计前一天的数据并生成报表等),解决方案有很多,Spring框架提供了配置定时任务的方案,通过注解,接入很简单,只需要下面两步:在启动类上添加注解@EnableScheduling@SpringBootApplication@EnableScheduling//添加定时任务启动注解publicclassSpringSchedulerApplication{publicstaticvoidmain(String[]args){SpringApplication.run(SpringSchedulerApplication.class,args);}}复制代码开发定时任务Bean并配置相应的定时注解@Scheduled@ComponentpublicclassSpringScheduledProcessor{/***通过Cron表达式指定频率或时间*/@Scheduled(cron="0/5****?")publicvoiddoSomethingByCron(){System.out.println("dosomething");}/***固定执行间隔*/@Scheduled(fixedDelay=2000)publicvoiddoSomethingByFixedDelay(){System.out.println("dosomething");}/***固定执行触发频率*/@Scheduled(fixedRate=2000)publicvoiddoSomethingByFixedRate(){System.out.println("dosomething");}}复制代码Spring定时任务原理云原生运行原理Spring定时任务核心逻辑主要在spring-context中的调度包中,其主要结构包括:调度任务分析:通过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(上次执行结束时间)。AdvancedExtendedThreadPoolOperation默认配置下,底层运行的线程池是单线程的。单线程运行模型任务量大,触发频率高。一旦一个任务被阻塞,所有后续的计划任务都会运行。阻塞,给业务运营带来严重的隐患。通常可以采用以下方法:配置定时执行线程池:一般根据SpringBoot配置的配置(spring.task.scheduling.pool.size=线程数),线程数取决于线程数任务和调度频率的合理配置。配置异步任务:在spring上下文的调度模块下提供了@EnableAsync和@Async,可以用来开启任务的异步执行,实现调度线程池的非阻塞运行。这种模式存在一些不足:异常处理需要通过异步调用的AsyncUncaughtExceptionHandler异常处理接口实现,同步/异步定时任务的异常处理机制不统一。另外,异步模式增加了业务应用的线程开销。@Scheduled(fixedDelay=2000)@Asyncpublicvoidtest(){System.out.println(DateUtil.now()+"test.");}拷贝代码中异常的统一处理scheduled运行时可以设置异常的统一处理任务,基于ErrorHandler接口开发相应的异常处理实现类。需要在核心ThreadPoolTask??Scheduler中注入相应的异常实现处理类。用户可以通过自定义TaskSchedulerCustomizer,将ErrorHandler自定义异常处理bean实现到ThreadPoolTask??Scheduler中。@ComponentpublicclassDemoTaskSchedulerCustomizer实现TaskSchedulerCustomizer{@Overridepublicvoidcustomize(ThreadPoolTask??SchedulertaskScheduler){taskScheduler.setErrorHandler(newDemoErrorHandler());}privateclassDemoErrorHandler实现ErrorHandler{@OverridepublicvoidhandleError(Throwablethrowable){System.out.println("异常统一处理。");}}}复制代码NativeSpring定时任务在企业中遇到的问题CloudNative任务重复执行Spring定时任务,只要有注解就会执行。在分布式场景下,所有的机器码都是一致的。会导致同一个任务在多台机器上重复执行。一般的解决方案是触发抢锁,分布式锁的实现形式可以使用DB、ZK、Redis等方式。示例代码如下:@Component@EnableSchedulingpublicclassMyTask{/***每30秒运行一次*/@Scheduled(cron="30****?")publicvoidtask1()throwsException{StringlockName="task1";if(tryLock(lockName)){System.out.println("hellocron");释放锁(锁名);}else{返回;}}privatebooleantryLock(StringlockName){//TODOreturntrue;}privatevoidreleaseLock(StringlockName){//TODO}}复制代码如上图所示,当任务触发时,三台服务器会抢到任务锁,只有获得任务锁的服务器才能执行对应的任务业务逻辑。目前的设计,细心一点的同学可以发现,其实可能会导致任务重复执行。例如,任务执行得非常快。机器A抢到锁,执行任务后很快就释放了锁。B抢到本机上的锁后,还是会抢到锁,重新执行任务。无控制,无运维。原生的Spring定时任务没有控制台,不能动态添加和修改定时任务。如果要修改计划任务的配置(例如,每分钟运行一次而不是每小时运行一次),则必须修改代码并重新发布应用程序。同时,原生的Spring定时任务没有运维操作,不支持运行任务,也不支持任务失败后重新运行任务。如果要使用自研的可视化控制台来实现一套完整的任务可视化管控系统,需要一定的前后端研发成本和业务部署成本的投入。对于需要自建的用户,可参考以下所需功能自建平台:任务可视化、动态配置、任务执行及操作详情、任务执行日志可视化查看、执行调用链、调度触发器、可视化业务应用间查询分析任务信息配置权限隔离无业务故障通知能力对于一个完整的企业级定时任务应用解决方案,告警通知能力是必不可少的。如果任务失败,需要及时通知用户,否则可能会失败。原生Spring定时任务不支持告警通知能力。如果想自己开发,可以参考上一章的《异常统一处理》,收集任务失败信息,建立相应的异常处理机制(包括对接各种告警平台进行异常消息通知处理,定义异常级别和类别针对不同的通知策略),然后执行定时任务告警通知。没有在线排查分析能力的定时任务在运行过程中会出现各种问题,比如:执行失败、执行耗时、执行卡死等,这些都需要在后期的实际运维中快速分析.如果在相应的分析过程中没有高效的在线排查能力,就会遇到很多棘手的问题:无法获知集群中任务对应时间点运行在哪台机器上,需要检索调度从大量业务应用日志中对应时间点的任务执行日志,需要自己对接日志服务来完善。如果任务涉及多个跨服务调用,无法定位执行异常点或执行耗时点,则需要构建全链路追踪来支持阿里云Spring。企业级定时任务解决方案CloudNativeNext主要讲一下如何使用公有云上的SchedulerX基于Spring开发方便的接入定时任务。讲了在使用Spring原生功能过程中遇到的问题以及需要自己解决的相关解决方案。由此可见,仅针对企业级最基础的应用场景,将需要更多的投入进行相关服务的改造和后续运维。通过接入SchedulerX任务调度平台,原有Spring定时任务的用户可以无缝、0改造获得企业级应用所需的能力,同时降低自研部署的技术成本运维定时服务相关组件。如何接入SchedulerX新用户,只需三步接入(参考附件接入手册):依赖SchedulerX的SpringBoot版本SDK完成调度平台接入(版本>=1.7.2,老用户只需要升级SDK版本即可(可选)在配置文件中添加配置项,开启配置后,SpringScheduler不会运行相关任务(如果不配置,不会主动接管原来的Spring调度任务运行,不会影响配置开启前原有的定时任务业务运行)Configuration表示SchedulerX将接管Spring定时任务运行spring.schedulerx2.task.scheduling.scheduler=schedulerx复制控制台代码,在对应的应用组下创建任务配置定时触发器,也可以选择开启自动同步任务配置方式(可选)自动将Spring定时任务同步到调度平台,无需手动单独创建(默认不开启))spring.schedulerx2.task.scheduling.sync=true复制代码接入优势白屏控制和运维提供白屏控制台,可以动态添加、修改、启用、禁用任务,支持运维操作如运行一次,原地重跑,重新刷新数据,停止任务,标记成功。可视化在线排查,支持执行记录查看、业务日志查询、全链路跟踪。丰富的告警通知SchedulerX提供丰富的告警通知能力,支持短信、电话、邮件、webhook告警,支持告警联系人组和告警历史,可动态配置白屏。其他优势无需改造成本的平台接入解决方案。不需要额外的独立运维调度服务平台或其他第三方组件服务。任务运行在稳定、高可靠支持的集群环境中,避免了原生框架存在的重复执行问题,具备故障自动转移能力。企业内的多个团队可以共享一套平台使用,通过命名空间和应用分组,实现每个团队的任务配置数据隔离和环境隔离。总结CloudNative本文主要对Spring定时任务的运行机制进行了分析和讲解,并针对如何扩展框架的原生能力提出了相应的建议,以满足企业级生产环境运行定时任务所需的各种场景。用户可以将其作为参考构建自己的内部定时任务方案。同时阐述了阿里云提供的任务调度服务如何对接Spring定时任务的运行,并简单演示了对接带来的企业级能力。最后,欢迎有计划任务业务需求的用户通过基础免费配额体验抢先体验云服务带来的便利。
