SpringBoot整合ShedLock,解决定时任务防止重复执行的问题
在分布式系统中,尤其是涉及到定时任务的场景下,任务的重复执行是一个常见问题。例如,多个服务节点同时执行同一个定时任务,可能会导致数据重复处理或者资源争用。ShedLock 是一个解决分布式环境中定时任务重复执行问题的库,它通过使用数据库锁机制确保只有一个节点执行特定的定时任务。
ShedLock 的工作原理
ShedLock 通过对任务加锁来避免多个实例(多个节点)执行同一个定时任务。它依赖于数据库表来存储任务的锁信息。每次执行任务之前,ShedLock 会检查数据库中是否有其他节点已经获得锁。如果没有,它会设置锁并执行任务;如果有,任务会被跳过。
- 添加依赖
在 Spring Boot 项目中集成 ShedLock,需要在 pom.xml 中添加相关依赖。
<dependencies>
<!-- ShedLock核心库 -->
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId>
<version>6.2.0</version> <!-- 最新版本 -->
</dependency>
<!-- ShedLock数据库存储实现(JPA) -->
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-jpa</artifactId>
<version>6.2.0</version> <!-- 最新版本 -->
</dependency>
<!-- Spring Boot JPA 支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- 选择合适的数据库,例如 H2 或 MySQL -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
- 创建数据库表
在使用 ShedLock 时,需要创建一个数据库表来存储锁的状态。以下是示例的 SQL 脚本,适用于 MySQL 或其他关系型数据库。
CREATE TABLE shedlock (
name VARCHAR(64) PRIMARY KEY,
lock_until TIMESTAMP NOT NULL,
locked_at TIMESTAMP NOT NULL,
locked_by VARCHAR(255) NOT NULL
);
- 配置 ShedLock 数据源
然后,在 Spring Boot 配置类中启用 ShedLock,配置数据库存储和锁提供者。
配置类 ShedLockConfig.java:
import net.javacrumbs.shedlock.core.LockProvider;
import net.javacrumbs.shedlock.provider.jparepository.JpaLockProvider;
import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import javax.persistence.EntityManager;
import javax.sql.DataSource;
@Configuration
@EnableSchedulerLock(defaultLockAtMostFor = "PT30M") // 默认任务最长执行时间 30 分钟
@EnableJpaRepositories("net.javacrumbs.shedlock.provider.jparepository") // 启用 JPA 存储库
public class ShedLockConfig {
@Bean
public LockProvider lockProvider(EntityManager entityManager) {
return new JpaLockProvider(entityManager); // 使用 JPA 实现的锁提供者
}
}
- 创建定时任务
接下来,你可以通过 @Scheduled 注解定义定时任务,并使用 @SchedulerLock 注解来确保任务不会在多个节点上同时执行。
示例:定时任务 ScheduledTasks.java:
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class ScheduledTasks {
// 每分钟执行一次任务,使用 ShedLock 确保任务不会被重复执行
@Scheduled(cron = "0 * * * * ?")
@SchedulerLock(name = "exampleTask", lockAtMostFor = "PT30S", lockAtLeastFor = "PT30S")
public void exampleTask() {
System.out.println("Executing example task...");
// 任务逻辑,例如更新数据、发送通知等
}
}
在上述代码中,@SchedulerLock 注解通过 name 参数来标识任务名称,确保相同名称的任务只会由一个节点执行。lockAtMostFor 和 lockAtLeastFor 分别表示任务最多和最少保持锁定的时间,防止任务执行时间过短或过长。
lockAtMostFor:设置任务执行时,最多持有锁的时间。如果任务执行时间超过该时间,锁将被释放。
lockAtLeastFor:设置任务至少持有锁的时间,防止任务执行过快而过早释放锁。
5. 启用 Spring Boot 定时任务功能
确保启用了 Spring Boot 的定时任务功能。你可以在 application.properties 或 application.yml 中配置相关属性,或者在启动类中启用 @EnableScheduling。
application.properties 配置:
spring.scheduling.enabled=true
或者在主启动类上使用 @EnableScheduling 注解启用定时任务:
主启动类 SpringBootApplication.java:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class SpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootApplication.class, args);
}
}
- 工作原理
锁定任务:当定时任务被触发时,ShedLock 会尝试获取锁。如果任务已经有锁,其他节点会跳过该任务的执行。
分布式锁:通过数据库表,ShedLock 确保分布式环境中多个服务实例不会同时执行相同的任务,避免了重复执行的问题。
防止任务阻塞:如果任务执行时间过长,ShedLock 会根据配置自动释放锁,防止任务被阻塞。 - 其他配置
你可以通过配置 defaultLockAtMostFor 和 defaultLockAtLeastFor 来全局设置锁的过期时间。defaultLockAtMostFor 表示全局配置的任务最长执行时间,defaultLockAtLeastFor 则表示全局配置的任务最短执行时间。
@EnableSchedulerLock(defaultLockAtMostFor = "PT30M", defaultLockAtLeastFor = "PT30S")
这样,所有使用 @SchedulerLock 注解的任务都将遵循这些全局设置,除非你在注解中显式地覆盖它们。
在 ShedLock 6.2.0 中,集成方式没有太大变化,但提供了更强的配置灵活性和优化。在分布式系统中,ShedLock 可以确保定时任务只会被一个实例执行,有效避免了重复执行的问题。通过数据库存储锁,ShedLock 使得定时任务管理更加可靠,适用于高并发和分布式的应用场景。
通过上述步骤,你可以轻松地在 Spring Boot 项目中使用 ShedLock 来管理定时任务,确保任务不会被重复执行,提升系统的稳定性和性能。