前言少叙
在目前的项目中,定时任务已经成为异步执行相对优秀的一种解决方式。比如说异步发消息和邮件,异步获取外部数据的状态并实时更新数据库内容...
本人在项目开发中遇到两个实际问题,也对应的找到了解决方式,在此分享,仅供参考。
问题一:定时任务的开关问题
场景:
目前大多数项目采用的都是分布式技术,应用包需要部署在不同的服务器上,那就导致定时任务会在两台以上服务器同时执行,这对实际的应用场景造成了巨大威胁,比如说发消息重复发送,数据重复更新,会造成一定程度的资源浪费。当然,我们可以在项目编译打包的过程中,可以打两次,一次开启定时任务,一次关闭(注释掉)定时任务的相关代码。但是,这对项目后期的迭代极不友好,每次都需要编译两次。我们都知道(反正我知道),目前项目在启动的时候会指定外部的配置文件(yml或者其它),那么可不可以将定时任务加上一个开关,通过不同服务器的不同启动配置文件来确保定时任务的执行唯一性这个问题呢?废话都讲到这了,那肯定是可以的。
解决方式:
利用@ConditionalOnProperty注解将定时任务实例禁止注入到容器中
我发现springboot的注解真的很强大,要啥有啥,无论什么方式,问题能被解决的才是好方法!
最直观上代码!!!
定时任务类
@Component
//启用定时任务
@EnableScheduling
//配置文件读取是否启用此配置 实现是通过havingValue与配置文件中的值对比,返回为true则配置类生效,反之失效
@ConditionalOnProperty(prefix = "switch", name = "enabled", havingValue = "true")
public class AppIndexTask {
@Scheduled(cron = "0/2 * * * * ?")
public void task() {
try {
System.out.println(="定时任务开关测试");
} catch (Exception e) {
e.printStackTrace();
}
}
}
配置文件
switch:
enabled: true/false
@ConditionalOnProperty(prefix = "前缀", name = "具体的参数", havingValue = "一个匹配规则")
只有当havingValue中的值与配置文件中的value相同,才会允许将注释的类注入到容器中,我们都知道spring最大的特点就是IOC(控制反转)和AOP(面向切面),扯这个蛋的目的就是想说,定时任务如果没有被注入到容器中,自然而然就失效的。当然,感兴趣@ConditionalOnProperty注解的小伙伴自行深入研究一下吧。
问题二:定时任务的堵塞问题
场景:
我们都知道(反正我知道)定时任务是一个单线程执行器,多个定时任务会顺序执行,随着项目的的定时任务越来越多,越来越多,会导致一个问题,明明我设置的执行时间是整点,偏偏延后了几分钟,甚至都不被执行。那么真像只有一个!就是上一个任务由于某种原因还没有执完,啊!!!那定时任务的意义何在,这不又死翘翘了,又会导致一大堆问题,比如说消息呢,消息呢!!!(万年不变的例子),会导致项目不安全,就是不安全!!!
解决方式:
如果将单线程执行编程多线程执行定时任务,会不会堵塞问题就迎刃而解了呢,那么接下来,有请线程池大人闪亮登场!!!
直接上代码
线程池配置类
@Configuration
@EnableAsync
public class AsyncConfig {
/*
此处成员变量应该使用@Value从配置中读取
*/
private int corePoolSize = 10;
private int maxPoolSize = 200;
private int queueCapacity = 10;
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
executor.initialize();
return executor;
}
}
定时任务类
@Component
//启用定时任务
@EnableScheduling
@Async
public class AppIndexTask2 {
@Scheduled(cron = "0/2 * * * * ?")
public void task() {
try {
// 整个UUID当做一次任务流水号
String uuid = UUID.randomUUID().toString().replace("-", "");
System.out.println(Thread.currentThread().getName() + "定时任务" + uuid + "执行开始");
Thread.sleep(10000);
System.out.println(Thread.currentThread().getName() + "定时任务" + uuid + "执行结束");
} catch (Exception e) {
e.printStackTrace();
}
}
}
如上代码所示,新建一个线程池的配置类,这里的参数根据实际情况设置大小,在配置类中使用@EnableAsync注解开启异步,并在定时任务的声明类中加入@Async运行开启异步,如代码所示,这里模拟一个任务执行需要10000ms也就是10s,但是定时任务没执行一次是2s,如果不加入异步执行,那么会一直堵塞状态,会导致任务执行不及时问题一大堆,加入异步的使用,所有问题就迎刃而解了,当然,现实场景中不会有堵塞么久的方法,只是为了能更直观的展现该方式解决堵塞问题。
如下执行结果就知道,定时任务在多线程执行
taskExecutor-1定时任务2dc5f079026746f7bab7a79f1168d971执行开始
taskExecutor-2定时任务75c55681e38a4759997533cc38155e72执行开始
taskExecutor-3定时任务8fc5c07c5153418e98cc83caf6277554执行开始
taskExecutor-4定时任务9bcd161b91b1410da99ea9636363ef84执行开始
taskExecutor-5定时任务4b19e080720643c3a66ee24c0fb9583d执行开始
taskExecutor-6定时任务35ea316a15c94a9b93ec0d6d4607f0cd执行开始
taskExecutor-1定时任务2dc5f079026746f7bab7a79f1168d971执行结束
taskExecutor-2定时任务75c55681e38a4759997533cc38155e72执行结束
如果对您有帮助,请双击屏幕扣666,多谢各位!