Spring项目多实例部署ShedLock分布式定时任务锁框架介绍

 更新时间:2026年07月06日 09:22:55   作者:隔壁阿布都  
spring shedlock用于在分布式服务的情况下执行定时任务,例如定时删除数据库中的一些数据,做数据迁移等等操作,这篇文章主要介绍了Spring项目多实例部署ShedLock分布式定时任务锁框架介绍的相关资料,需要的朋友可以参考下

前言

net.javacrumbs.shedlock.core.SchedulerLockShedLock 分布式定时任务锁框架的核心注解(并非普通类),专门用于解决 Spring 项目多实例部署时,@Scheduled 定时任务重复执行的问题。

一、核心用途

1. 解决的核心痛点

Spring 原生的 @Scheduled 是单机定时任务。当应用分布式部署(多节点/多副本)时,每个节点都会独立触发定时任务,导致:

  • 数据重复插入/计算
  • 数据库并发写冲突
  • 重复发送消息、通知
  • 资源浪费与业务逻辑异常

@SchedulerLock 的作用就是给定时任务加分布式互斥锁同一时刻,全局只有一个节点能执行该定时任务,其余节点直接跳过本次执行

2. 框架定位

  • 不是分布式调度中心(区别于 XXL-Job、Elastic-Job),不负责任务触发、分片、日志监控,只做「任务互斥锁」。
  • 完全兼容 Spring 原生 @Scheduled,仅通过注解增强,业务代码侵入极低。
  • 支持多种锁存储介质:MySQL、Redis、MongoDB、ZooKeeper 等。

3. 核心原理

  1. 通过 Spring AOP 拦截标注了 @SchedulerLock 的定时方法;
  2. 任务触发时,通过 LockProvider 向公共存储(DB/Redis)抢占同名锁;
  3. 抢到锁:执行业务逻辑,执行完成后自动释放锁;
  4. 抢不到锁:直接跳过本次任务,不阻塞、不等待;
  5. 内置锁超时机制:即使执行节点宕机,超过设定时间后锁自动释放,避免永久死锁。

二、注解参数详解

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SchedulerLock {
    // 【必填】锁的全局唯一名称
    String name();

    // 锁最大持有时间(毫秒数,旧版写法)
    long lockAtMostFor() default -1;
    // 锁最大持有时间(ISO-8601字符串,推荐写法)
    String lockAtMostForString() default "";

    // 锁最小持有时间(毫秒数,旧版写法)
    long lockAtLeastFor() default -1;
    // 锁最小持有时间(ISO-8601字符串,推荐写法)
    String lockAtLeastForString() default "";
}

参数说明与配置建议

参数作用配置建议示例
name锁的唯一标识,同名任务全局互斥每个定时任务必须唯一,建议用「业务名_任务名」order_auto_cancel_task
lockAtMostForString锁的最长持有时间,超时自动释放必须大于任务实际最大执行时长,建议设为预估耗时的2~3倍,防止宕机死锁PT10M = 10分钟
lockAtLeastForString锁的最短持有时间,任务执行完后仍会持有锁针对执行快、频率高的任务,避免任务瞬间执行完后,其他节点在同一周期内重复抢到锁PT30S = 30秒

ISO-8601 持续时间格式常用写法:PT30S(30秒)、PT5M(5分钟)、PT1H(1小时)、PT2H30M(2小时30分)。

三、完整使用步骤(Spring Boot 环境)

ShedLock 依赖外部存储实现分布式锁,生产环境最常用两种方案:MySQL/JDBC(无额外中间件)Redis

方案1:基于 MySQL / JDBC(最通用)

步骤1:引入依赖

<!-- Maven -->
<dependency>
    <groupId>net.javacrumbs.shedlock</groupId>
    <artifactId>shedlock-spring</artifactId>
    <version>4.44.0</version>
</dependency>
<dependency>
    <groupId>net.javacrumbs.shedlock</groupId>
    <artifactId>shedlock-provider-jdbc-template</artifactId>
    <version>4.44.0</version>
</dependency>
// Gradle
implementation 'net.javacrumbs.shedlock:shedlock-spring:4.44.0'
implementation 'net.javacrumbs.shedlock:shedlock-provider-jdbc-template:4.44.0'

步骤2:创建锁表

在业务数据库中执行建表语句,表名和字段固定:

CREATE TABLE shedlock (
    name       VARCHAR(64)  NOT NULL COMMENT '锁名称,主键',
    lock_until TIMESTAMP(3) NOT NULL COMMENT '锁过期时间',
    locked_at  TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '加锁时间',
    locked_by  VARCHAR(255) NOT NULL COMMENT '持有锁的节点标识',
    PRIMARY KEY (name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='ShedLock分布式定时任务锁表';

步骤3:配置类开启 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.scheduling.annotation.EnableScheduling;

import javax.sql.DataSource;

@Configuration
@EnableScheduling // 开启Spring原生定时任务
@EnableSchedulerLock(defaultLockAtMostFor = "PT10M") // 全局默认最大锁时长
public class ShedLockConfig {

    /**
     * 配置JDBC锁提供者
     */
    @Bean
    public LockProvider lockProvider(DataSource dataSource) {
        return new JdbcTemplateLockProvider(dataSource);
    }
}

步骤4:在定时方法上添加注解

import net.javacrumbs.shedlock.core.SchedulerLock;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class OrderTask {

    /**
     * 每5分钟执行一次:自动取消超时未支付订单
     */
    @Scheduled(cron = "0 */5 * * * ?")
    @SchedulerLock(
            name = "order_auto_cancel_task",
            lockAtMostForString = "PT15M",  // 最多持有15分钟,宕机也会自动释放
            lockAtLeastForString = "PT1M"   // 最少持有1分钟,避免执行太快导致重复
    )
    public void cancelTimeoutOrder() {
        // 业务逻辑:取消超时订单
        System.out.println("执行超时订单取消任务");
    }
}

方案2:基于 Redis 实现

适合项目已接入 Redis、不想新增数据库表的场景,业务代码完全不变,仅替换依赖和配置。

替换依赖

<dependency>
    <groupId>net.javacrumbs.shedlock</groupId>
    <artifactId>shedlock-provider-redis-lettuce</artifactId>
    <version>4.44.0</version>
</dependency>

替换 LockProvider 配置

@Bean
public LockProvider lockProvider(RedisConnectionFactory redisConnectionFactory) {
    return new LettuceLockProvider(redisConnectionFactory);
}

四、多节点执行流程

以双节点部署、5分钟执行一次的订单取消任务为例:

  1. 到触发时间点,节点A、节点B同时触发 cancelTimeoutOrder 方法;
  2. ShedLock 切面拦截,两个节点同时向 MySQL 的 shedlock 表写入/更新 order_auto_cancel_task 记录;
  3. 节点A写入成功,抢到锁,执行业务逻辑;
  4. 节点B写入失败(主键冲突),直接跳过本次任务,不执行业务逻辑;
  5. 节点A执行完成,更新 lock_until 为当前时间,释放锁;
  6. 若节点A执行中途宕机,15分钟后 lock_until 到期,锁自动失效,下一轮其他节点可正常抢占。

五、避坑与注意事项

  1. 方法必须是 public:Spring AOP 基于代理实现,private/protected 方法无法被拦截,注解会失效。
  2. 锁名必须全局唯一:不同任务不能使用相同 name,否则会互相阻塞。
  3. 集群时间必须同步:锁的过期判断依赖系统时间,节点时间差过大可能导致锁逻辑异常。
  4. 同类内部调用不生效:同一个类中通过 this.xxx() 调用带注解的方法,不走代理,锁不生效。
  5. 仅适配 @Scheduled:该注解专门为定时任务设计,普通业务方法不生效;普通分布式锁请使用 Redisson 等框架。
  6. 抢锁失败直接跳过:ShedLock 不会阻塞等待锁,也不会重试;需要重试请自行实现。

六、优缺点总结

优点

  • 侵入性极低:仅加一行注解,原有定时任务代码无需改造
  • 自动防死锁:超时自动释放机制,避免节点宕机导致任务永久挂起
  • 多存储兼容:可灵活切换 MySQL、Redis、Mongo 等存储
  • 轻量无额外服务:不需要独立部署调度中心,运维成本低

缺点

  • 功能单一:只解决重复执行问题,不支持任务分片、动态调整、失败告警、日志管理
  • 无重试机制:抢锁失败直接跳过,不适合必须执行的核心任务
  • 依赖外部存储:存储中间件故障会导致所有定时任务失效

七、适用场景

  • 中小型项目,多实例部署,需要快速解决定时任务重复执行问题
  • 已有 Spring @Scheduled 任务,不想引入重量级调度中心
  • 数据清理、状态同步、报表生成等对执行精度要求不极端的定时任务

到此这篇关于Spring项目多实例部署ShedLock分布式定时任务锁框架介绍的文章就介绍到这了,更多相关ShedLock分布式定时任务锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java服务器宕机的解决方法论

    Java服务器宕机的解决方法论

    这篇文章主要介绍了Java服务器宕机的解决方法,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • Java多线程实现聊天客户端和服务器

    Java多线程实现聊天客户端和服务器

    这篇文章主要为大家详细介绍了Java多线程聊天客户端和服务器实现代码,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-10-10
  • 不使用Math.random方法生成随机数(随机数生成器)

    不使用Math.random方法生成随机数(随机数生成器)

    不调用Math.random方法产生自己的随机数,现代计算机运行速度很快,在主线程等待一定毫秒数时,其他线程就会执行run方法中的while循环,一般会执行数十万次
    2014-01-01
  • java多线程JUC常用辅助类详解

    java多线程JUC常用辅助类详解

    这篇文章主要为大家介绍了java多线程及并发编程中JUC常用辅助类,文中附含详细示例代码,有需要的朋友可以借鉴参考下,希望能够有所帮助
    2021-09-09
  • 用Maven插件生成Mybatis代码的实现方法

    用Maven插件生成Mybatis代码的实现方法

    本文主要介绍 Maven插件生成Mybatis代码,现在做开发的朋友有好多用Maven 来管理代码,这里给大家举个例子,有需要的同学可以看下
    2016-07-07
  • Idea安装Eslint插件提示:Plugin NativeScript was not installed的问题

    Idea安装Eslint插件提示:Plugin NativeScript was not installed的问题

    这篇文章主要介绍了Idea安装Eslint插件提示:Plugin NativeScript was not installed的问题,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-10-10
  • MyBatis-Plus整合金仓数据库KingbaseES的实战指南

    MyBatis-Plus整合金仓数据库KingbaseES的实战指南

    随着数字中国建设的深入推进,国产数据库在关键业务系统中扮演着越来越重要的角色,本文将通过一个电商系统的实战案例,深入探讨MyBatis-Plus整合KingbaseES的详细步骤吧
    2025-10-10
  • java 中Buffer源码的分析

    java 中Buffer源码的分析

    这篇文章主要介绍了java 中Buffer源码的分析的相关资料,需要的朋友可以参考下
    2017-06-06
  • Java虚拟线程原理与实践指南(含实际案例)

    Java虚拟线程原理与实践指南(含实际案例)

    Java虚拟线程是一种轻量级线程,由JVM管理而非操作系统,这篇文章主要介绍了Java虚拟线程原理与实践指南的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2026-06-06
  • Java中精确的浮点运算操作示例

    Java中精确的浮点运算操作示例

    这篇文章主要介绍了Java中精确的浮点运算操作方法,结合具体实例形式分析了java浮点数运算的相关函数、使用技巧与注意事项,需要的朋友可以参考下
    2017-06-06

最新评论