Spring 守时使命简介
Cloud Native
守时使命是事务运用开发中十分普遍存在的场景(如:每分钟扫描超时付出的订单,每小时整理一次数据库前史数据,每天计算前一天的数据并生成报表等等),解决计划很多,Spring 框架供给了一种经过注解来装备守时使命的解决计划,接入十分的简单,仅需如下两步:
- 在发动类上增加注解@EnableScheduling
@SpringBootApplication
@EnableScheduling // 增加守时使命发动注解
public class SpringSchedulerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringSchedulerApplication.class, args);
}
}
- 开发守时使命 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 运转使命。
事务逻辑会将被包装在 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 守时使命在企业中遇到的问题
Cloud Native
使命重复履行
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 守时使命企业级解决计划
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
接入优势
-
白屏管控和运维
供给白屏控制台能够动态新增、修正、启用、禁用使命,支撑运转一次、原地重跑、重刷数据、停止使命、符号成功等运维操作。
-
可视化在线排查问题
支撑履行记载检查、履行事务日志查询、履行全链路追寻。
-
丰厚的报警告诉
SchedulerX 供给丰厚的报警告诉才能,支撑短信、电话、邮件、webhook 报警,支撑报警联系人组和报警前史,可白屏动态装备。
-
其他优势
- 无改造本钱的渠道接入计划。
- 无需额定独立运维调度服务渠道或其他第三方组件服务。
- 使命运转在集群环境中具有稳定高牢靠支撑,规避了原生框架存在的重复履行问题,具有毛病主动搬运才能。
- 在企业内多个团队可同享一套渠道运用,经过命名空间和运用分组完成各团队使命装备数据阻隔及环境阻隔。
总结
Cloud Native
本文主要从 Spring 守时使命的运转机制进行剖析阐述,并对怎么扩展框架原生才能以满意企业级生产环境运转守时使命所需各种场景提出了相应的主张,用户可作参阅构建自己内部守时使命计划。一起就阿里云上供给的使命调度服务怎么接入 Spring 守时使命的运转进行讲解,并简单展现了接入后所带来的企业级才能。最终欢迎有守时使命事务需求用户可先经过基础免费额度体验感受云上服务带来便捷。