业务需求:
项目部署要求做Nginx负载均衡、,多节点情况下,容易造成单个定时任务重复执行。于是需要对定时任务模块进行优化、保证定时任务执行的唯一性。
技术选择
因为定时任务也比较简单,执行频率固定,技术选择上就不对定时任务做自定义页面可配置了。直接上定时任务cron表达式执行。此处根据业务需求选择 Scheduled 与 SchedulerLock锁进行保证唯一性。
实现方式
话不多说,直接上代码:
- 引入依赖
pom文件中添加依赖:
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-jdbc-template</artifactId>
<version>3.0.1</version>
</dependency>
- 创建定时任务锁需要的表
-- 创建定时任务表
CREATE TABLE shedlock(
name VARCHAR(64) ,
lock_until TIMESTAMP(3) NULL,
locked_at TIMESTAMP(3) NULL,
locked_by VARCHAR(255),
PRIMARY KEY (name)
)
- 自定义设置分布式定时任务注册器以及数据库锁设置
package com.uniceu.admin.webservice.config;
import net.javacrumbs.shedlock.core.LockProvider;
import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;
import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import javax.sql.DataSource;
/**
* @author wangzibo
* @date 2023年11月27日 8:43
* @Description 分布式定时任务配置
*/
@Configuration
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "PT30S")
public class ScheduledLockConfig implements SchedulingConfigurer {
@Bean
public LockProvider lockProvider(DataSource dataSource) {
return new JdbcTemplateLockProvider(dataSource);
}
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
scheduledTaskRegistrar.setScheduler(taskScheduler());
}
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(64);
return taskScheduler;
}
}
注意此处引入的@EnableSchedulerLock 注解defaultLockAtMostFor属性 指定在执行节点结束时保留锁的默认时间、此处默认时间可根据需求来。后续业务定时任务模块会根据接口需要覆盖。
4. 定时任务实现
验证:
注意若想保住同一个定时任务只执行一次,需保证 @SchedulerLock(name = “fiveMLock”) 注解name属性是唯一的。多节点下需保证执行的业务接口name属性值一致即可!
package com.uniceu.admin.webservice.config;
import com.uniceu.core.utils.DateUtils;
import net.javacrumbs.shedlock.core.SchedulerLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* @author wangzibo
* @date
* @Description
*/
@Component
public class DmI6000KpiJob{
/**
* 1分钟执行一次记录操作
*/
@Scheduled(cron = "0 */1 * * * ?")
@SchedulerLock(name = "fiveMLock",lockAtMostFor = 7 * 60 * 1000, lockAtLeastFor =1 * 60 * 1000)
public void fiveMJobI600Sys(){
System.out.println("====================定时任务 一 抢到锁啦!!!!!");
}
/**
* 1分钟执行一次记录操作
*/
@Scheduled(cron = "0 */1 * * * ?")
@SchedulerLock(name = "fiveMLock",lockAtMostFor = 7 * 60 * 1000, lockAtLeastFor =1 * 60 * 1000)
public void fiveMJobs(){
System.out.println("====================定时任务 二 抢到锁啦!!!!!");
}
}
lockAtMostFor = 7 * 60 * 1000, 属性:最多等待时长、单位毫秒
lockAtLeastFor =1 * 60 * 1000 属性:最少等待时长、单位毫秒
根据业务场景自定义。此处设置了会覆盖默认的时长。
常见问题
- 导包依赖导错,导包是 import net.javacrumbs.shedlock.core.SchedulerLock; 注意不要导错!
- 导包版本过高与springboot版本冲突
- 数据库用户权限不足,无法将当前执行的定时任务信息写入定时任务表。(定时任务表数据会自动写入和清除)
- 未添加@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = “PT30S”)
注解,导致不执行。 - @Component 该注解失效(在启动类上添加@ComponentScan(basePackages = { “定时任务类所在包”}))