You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在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

火山引擎 最新活动