如何在Spring Boot集群中实现定时任务仅执行一次?ShedLock失效求助
解决ShedLock在Tomcat集群中未生效的问题
看起来你踩了ShedLock新手最容易犯的一个坑——只引入了核心依赖,但没配置锁存储后端!ShedLock本身只是个调度锁协调框架,需要借助外部存储(比如数据库、Redis)来记录锁状态,才能在集群里实现任务互斥。下面一步步帮你排查修复:
1. 补全锁存储依赖
你目前只加了shedlock-spring,这是核心协调依赖,但必须搭配具体的存储实现。以最常用的关系型数据库为例,需要额外引入:
<dependency> <groupId>net.javacrumbs.shedlock</groupId> <artifactId>shedlock-provider-jdbc-template</artifactId> <version>0.18.2</version> </dependency>
如果用Redis做存储,就换成:
<dependency> <groupId>net.javacrumbs.shedlock</groupId> <artifactId>shedlock-provider-redis-spring</artifactId> <version>0.18.2</version> </dependency>
注意版本要和shedlock-spring严格保持一致。
2. 初始化锁存储(数据库场景)
如果用数据库,必须手动创建shedlock表(以MySQL为例),这是ShedLock记录锁状态的核心表:
CREATE TABLE shedlock( name VARCHAR(64) NOT NULL, lock_until TIMESTAMP(3) NOT NULL, locked_at TIMESTAMP(3) NOT NULL, locked_by VARCHAR(255) NOT NULL, PRIMARY KEY (name) );
Redis场景不需要手动创建结构,直接用即可。
3. 配置ShedLock核心Bean
在Spring Boot配置类里,需要配置锁提供者,并开启ShedLock的注解支持:
数据库场景示例
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.jdbc.core.JdbcTemplate; @Configuration @EnableSchedulerLock(defaultLockAtMostFor = "PT30M") // 默认锁最长持有时间,防止节点挂掉锁一直占用 public class ShedLockConfig { @Bean public LockProvider lockProvider(JdbcTemplate jdbcTemplate) { return new JdbcTemplateLockProvider(jdbcTemplate); } }
Redis场景示例
import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.provider.redis.spring.RedisLockProvider; import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.core.StringRedisTemplate; @Configuration @EnableSchedulerLock(defaultLockAtMostFor = "PT30M") public class ShedLockConfig { @Bean public LockProvider lockProvider(StringRedisTemplate stringRedisTemplate) { return new RedisLockProvider(stringRedisTemplate); } }
@EnableSchedulerLock是关键,它会让ShedLock拦截Spring的定时任务,实现锁逻辑。
4. 给定时任务加锁注解
在你的定时任务方法上,必须添加@SchedulerLock注解,指定唯一锁名称和锁持有时间:
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @Component public class ClusterScheduledTask { @Scheduled(cron = "0 0 X * * ?") // 你的定时时间X @SchedulerLock( name = "cluster_task_unique_lock", // 集群内同一个任务必须用相同的name lockAtMostFor = "PT10M", // 锁最长持有时间,避免节点挂掉锁无法释放 lockAtLeastFor = "PT5M" // 锁最短持有时间,防止任务执行太快导致重复触发 ) public void runClusterTask() { // 你的任务逻辑 } }
5. 额外排查点
- 确认你的Spring Boot应用已经开启了调度支持:启动类或配置类上要加
@EnableScheduling - 检查存储连接:所有集群节点必须能访问同一个数据库/Redis实例
- 查看日志:开启DEBUG级别日志,搜索
shedlock关键词,看是否有锁获取失败、存储连接错误的信息 - 版本兼容性:0.18.2是比较老的版本,如果你的Spring Boot版本在2.4+,可能存在兼容性问题,可以尝试升级到ShedLock的稳定新版本
内容的提问来源于stack exchange,提问作者Droide




