SpringBoot整合ShedLock分布式锁指南
还在为Spring Boot定时任务在分布式环境下的并发执行问题发愁吗?本文为你带来ShedLock的整合教程,一个轻量级的分布式锁解决方案,专注于解决定时任务的并发执行。ShedLock通过共享存储(如数据库、Redis)实现锁的创建和管理,确保同一时刻只有一个实例在执行定时任务。本文将详细介绍如何在Spring Boot项目中引入shedlock-spring和对应的存储依赖,配置@EnableScheduling和@EnableSchedulerLock,并创建LockProvider Bean。同时,针对数据库存储,还将提供创建shedlock表的SQL示例。通过在定时任务方法上添加@SchedulerLock注解,轻松设置锁的参数,如lockAtMostFor和lockAtLeastFor,防止任务重复执行和死锁。相比Redisson和ZooKeeper等通用锁方案,ShedLock更轻量且专注定时任务场景,是Spring Boot应用中处理分布式定时任务的理想选择。
ShedLock通过共享存储实现分布式定时任务锁。1. 引入shedlock-spring和对应存储依赖如JDBC或Redis;2. 配置@EnableScheduling和@EnableSchedulerLock并创建LockProvider Bean;3. 若用数据库需手动创建shedlock表;4. 在定时任务方法上添加@SchedulerLock注解设置锁参数。其原理基于存储的原子操作,通过记录锁状态确保任务不并发执行。相比Redisson和ZooKeeper等通用锁方案,ShedLock更轻量且专注定时任务场景。使用时需注意lockAtMostFor与任务时间匹配、保障任务幂等性、合理配置存储性能、避免锁名称冲突等问题。

在Spring Boot应用里处理定时任务,如果多个实例同时跑,那数据一致性就成了大麻烦。ShedLock提供了一个相对轻量级的解决方案,它通过在共享存储(比如数据库、Redis)上创建和管理锁,确保在分布式环境下,同一个定时任务在任意时刻只有一个实例在执行。它不是一个通用的分布式锁框架,而是专注于解决定时任务的并发执行问题,用起来很顺手,侵入性也低。

解决方案
要让Spring Boot和ShedLock携手工作,其实步骤并不复杂。

首先,得把必要的依赖加到你的pom.xml里。这通常包括shedlock-spring和shedlock-provider-jdbc-template(如果你用关系型数据库的话,比如MySQL)。如果用Redis,那就是shedlock-provider-redis-spring。
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId>
<version>5.x.x</version> <!-- 使用最新稳定版本 -->
</dependency>
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-jdbc-template</artifactId>
<version>5.x.x</version> <!-- 和shedlock-spring版本保持一致 -->
</dependency>
<!-- 如果你用的是Redis,替换上面的JDBC依赖 -->
<!--
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-redis-spring</artifactId>
<version>5.x.x</version>
</dependency>
-->接着,配置ShedLock。最常见的方式是在Spring Boot的配置类上加@EnableScheduling和@EnableSchedulerLock。@EnableSchedulerLock需要你提供一个LockProvider bean,告诉ShedLock去哪里存取锁信息。

import net.javacrumbs.shedlock.core.LockProvider;
import net.javacrumbs.shedlock.provider.jdbc.template.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;
import javax.sql.DataSource;
@Configuration
@EnableScheduling // 别忘了开启Spring的定时任务
@EnableSchedulerLock(defaultLockAtMostFor = "10m") // 默认最长锁定10分钟
public class SchedulerConfig {
@Bean
public LockProvider lockProvider(DataSource dataSource) {
// 对于关系型数据库,需要一个JdbcTemplateLockProvider
// 注意:默认会使用名为'shedlock'的表,你可以自定义
return new JdbcTemplateLockProvider(
JdbcTemplateLockProvider.Configuration.builder()
.withJdbcTemplate(new JdbcTemplate(dataSource))
.using;// 确保表存在,ShedLock不会自动创建
);
}
// 如果是Redis,配置会是这样:
// @Bean
// public LockProvider lockProvider(RedisConnectionFactory connectionFactory) {
// return new RedisLockProvider(connectionFactory);
// }
}如果你用的是关系型数据库,还需要手动创建一张表来存储锁信息。ShedLock默认会找一张名为shedlock的表。这是MySQL的建表语句示例:
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)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;最后一步,在你的定时任务方法上加上@SchedulerLock注解。
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class MyScheduledTasks {
// 每分钟执行一次,但只允许一个实例执行
@Scheduled(cron = "0 * * * * ?")
@SchedulerLock(name = "myTaskName", // 锁的名称,必须唯一
lockAtMostFor = "PT30S", // 最长锁定30秒,防止任务崩溃导致锁不释放
lockAtLeastFor = "PT10S") // 最短锁定10秒,防止任务执行过快导致频繁竞争
public void executeMyTask() {
System.out.println("Executing myTaskName at " + System.currentTimeMillis());
// 模拟任务执行
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Finished myTaskName at " + System.currentTimeMillis());
}
}lockAtMostFor和lockAtLeastFor这两个参数特别重要。lockAtMostFor是个安全网,它定义了锁的最大持有时间,即使任务异常退出,锁也会在这个时间后自动释放,避免死锁。lockAtLeastFor则确保一个任务执行完成后,不会立即被其他实例抢占并再次执行,给系统一点喘息的机会,尤其是在任务执行非常快的情况下。
ShedLock分布式锁的工作原理是什么?
ShedLock的原理说起来,其实就是“谁先占坑谁赢”。它不搞那些复杂的共识算法,而是基于一个简单的事实:在分布式系统里,只要有一个地方能被所有节点共同访问到,并且这个地方能保证操作的原子性,那就可以拿来做锁。
具体到实现,ShedLock会依赖你配置的LockProvider。
比如,如果用关系型数据库作为存储:
当一个Spring Boot实例尝试执行被@SchedulerLock注解的定时任务时,ShedLock会尝试向shedlock表插入一条记录,或者更新现有记录的lock_until字段。这个操作是带条件的:它会检查name字段(也就是你的锁名称)是否已经存在,并且lock_until是否已经过期。
如果记录不存在,或者虽然存在但lock_until时间戳已经早于当前时间(意味着锁已过期),那么这个实例就能成功地插入或更新记录,并把lock_until设置为一个未来的时间点(当前时间 + lockAtMostFor)。这就相当于它成功“抢”到了锁。
如果记录存在且lock_until还没过期,那么这个实例就会放弃执行任务,因为它没能拿到锁。
locked_at记录了锁被获取的时间,locked_by记录了是哪个实例获取的锁(通常是hostname:pid)。这些信息对排查问题很有用。
任务执行完毕后,ShedLock会更新lock_until为locked_at + lockAtLeastFor,或者干脆让它过期,以便其他实例可以再次尝试获取。但更重要的是,lockAtMostFor确保了即使任务崩溃,锁最终也会自动释放。
如果是Redis,原理类似,通常会用SETNX(Set if Not Exists)命令来尝试设置一个带有过期时间的键。如果键设置成功,就表示获取到了锁;如果键已经存在,则表示锁已被其他实例持有。Redis的原子性操作为ShedLock提供了分布式锁的基础。
整个过程的核心在于存储介质提供的原子性操作和ShedLock对锁状态(lock_until)的判断。它非常适合定时任务这种“要么执行,要么不执行”的场景,因为它关注的是防止重复执行,而不是复杂的资源协调。
ShedLock与Redisson、ZooKeeper等其他分布式锁方案有何不同?
谈到分布式锁,ShedLock、Redisson和基于ZooKeeper(或Etcd)的方案,它们各有各的侧重点和适用场景,不能简单地说谁更好,只能说谁更适合你的具体问题。
ShedLock:
- 定位: 专门为定时任务设计。它的核心目标是确保一个定时任务在分布式部署下不会被多个实例同时执行。
- 特点:
- 轻量级,侵入性低: 集成非常简单,只需少量配置和注解。
- 依赖简单: 可以基于关系型数据库、MongoDB、Redis等多种存储,通常你现有的数据存储就能满足需求,无需引入新的复杂组件。
- 易于理解: 锁的逻辑相对直接,就是检查一个时间戳或一个key是否存在。
- 非通用锁: 它不是一个通用的分布式锁,不能用来锁业务逻辑中的任意共享资源。它的锁是基于任务名称的,而且主要通过防止任务重复启动来工作。
- 适用场景: 分布式定时任务,如数据同步、报表生成、批量处理等,这些任务本身通常是幂等的,或者可以容忍偶尔的重试。
Redisson(基于Redis):
- 定位: 基于Redis的通用分布式锁框架,提供了多种分布式对象和服务,包括分布式锁。
- 特点:
- 功能丰富: 不仅仅是锁,还有分布式集合、队列、信号量、AtomicLong等。
- 高级锁特性: 支持可重入锁、公平锁、读写锁、联锁(MultiLock)等,功能非常强大。
- 看门狗机制: 默认提供自动续期功能,防止业务执行时间过长导致锁过期。
- 依赖Redis: 必须依赖Redis集群,对Redis的可用性和性能有较高要求。
- 相对复杂: 引入的依赖和概念比ShedLock多,配置也更灵活但可能稍显复杂。
- 适用场景: 需要在分布式环境中对任意共享资源进行精确控制的业务场景,如秒杀系统的库存扣减、共享配置的修改、分布式事务等。它能提供更细粒度的锁控制。
基于ZooKeeper/Etcd的分布式锁:
- 定位: 基于强一致性分布式协调服务(如ZooKeeper、Etcd)实现的通用分布式锁。
- 特点:
- 强一致性: 依赖底层ZAB协议或Raft协议,能够提供非常高的一致性保证。
- 高可靠性: 只要集群中多数节点存活,服务就可用。
- 羊群效应: 在高并发场景下,所有等待锁的客户端都监听同一个节点,可能导致“羊群效应”,性能不如Redis。虽然有优化方案(如创建顺序临时节点),但复杂度也随之增加。
- 复杂性: 引入ZooKeeper或Etcd集群本身就是一项不小的运维工作,客户端库(如Curator)也相对复杂。
- 持久性: 锁通常是临时节点,客户端断开连接锁自动释放。
- 适用场景: 对锁的强一致性要求极高、对性能敏感度相对较低,且已经部署了ZooKeeper/Etcd集群的场景。例如,分布式配置中心、选主、服务发现等。
总结一下: ShedLock是“小而美”,专注于解决定时任务的分布式并发问题,简单直接,适合绝大多数定时任务场景。 Redisson是“大而全”,提供了丰富的分布式锁类型和功能,适合需要对业务逻辑中的共享资源进行复杂、通用锁控制的场景。 ZooKeeper/Etcd方案是“稳而重”,提供最高级别的强一致性保证,但引入和维护成本也最高,适合对一致性有极致要求的核心协调服务。
在选择时,通常会从最简单、最适合的方案开始。如果只是解决定时任务的并发,ShedLock往往是最佳选择。如果业务中需要更通用的、更复杂的分布式锁,Redisson会是很好的下一步。而ZooKeeper/Etcd方案,则通常在已有基础设施且对一致性有最高要求时才考虑。
在Spring Boot中使用ShedLock时常见的坑和注意事项有哪些?
虽然ShedLock用起来很方便,但实际部署和运行中,还是会遇到一些“小插曲”或者需要注意的地方。
1. lockAtMostFor 的陷阱与重要性
这是ShedLock里一个非常关键的参数。它定义了任务获取锁后,最长可以持有锁的时间。
坑: 如果你把lockAtMostFor设置得比你的任务实际执行时间短,那么任务还没执行完,锁可能就已经被ShedLock自动释放了。这时,其他实例就有可能再次获取到锁并执行任务,导致重复执行。这往往发生在任务执行时间波动大,或者你对任务执行时间预估不足时。
注意: 务必将lockAtMostFor设置得比你的任务最长预期执行时间要长一些,留出足够的余量。这是防止死锁的关键,也是ShedLock的“安全网”。宁可设置长一点,也不要设置太短。
2. lockAtLeastFor 的作用与误解
这个参数确保锁至少被持有指定的时间。
坑: 有些人可能误以为它能防止任务执行过快导致频繁竞争。虽然有这个作用,但更重要的是,它能防止一个任务刚执行完,锁就被立即释放,然后其他实例立刻抢到锁并再次执行,尤其是在任务执行速度极快时。
注意: 合理设置lockAtLeastFor,可以避免某些极端情况下任务的“抖动”或过于频繁的调度。但它不是强制任务必须执行这么久,只是保证锁的最小持有时间。
3. 数据库(或存储)的可用性与性能 ShedLock的稳定运行高度依赖其底层存储的可用性和性能。 坑: 如果你用数据库作为锁存储,一旦数据库连接池耗尽、网络抖动或数据库本身出现故障,ShedLock将无法获取或释放锁,导致定时任务无法正常启动或持续运行。锁表成为瓶颈时,也可能影响任务启动效率。 注意:
- 确保数据库连接池配置合理,能够应对并发请求。
- 监控数据库的性能,特别是锁表的读写情况。
- 考虑数据库的高可用方案,避免单点故障。
- 如果任务量巨大且频繁,考虑使用Redis作为锁存储,它通常能提供更高的并发和更低的延迟。
4. 任务的幂等性
即使有了ShedLock,也不能完全忽视任务的幂等性。
坑: 虽然ShedLock努力保证任务不重复执行,但在极端情况下(比如网络瞬断导致锁状态更新失败,或者任务执行过程中应用崩溃,lockAtMostFor还没到),任务仍然有小概率被重复触发。
注意: 任何定时任务,特别是涉及数据修改的,都应该尽可能设计成幂等操作。这意味着即使任务被执行多次,其对系统的影响也应该与执行一次相同。这是分布式系统设计的黄金法则。
5. 事务边界与锁的冲突
如果你在定时任务方法内部有事务,需要注意事务和锁的交互。
坑: 如果你把@SchedulerLock放在一个被事务注解(如@Transactional)的方法上,并且锁的获取和释放逻辑被包含在事务中,那么一旦事务回滚,锁的状态也可能回滚,导致锁没有被正确释放。
注意: 理想情况下,锁的获取和释放应该在业务事务之外。ShedLock通常在方法调用前获取锁,方法结束后释放锁,这通常与Spring的事务管理层级不同。但如果你的业务逻辑非常复杂,并且锁的获取和业务数据更新需要强一致性,可能需要更复杂的协调。不过对于ShedLock这种主要解决“任务不重复启动”的场景,这通常不是大问题。
6. 时钟同步问题(次要但值得一提) 虽然ShedLock主要依赖数据库或Redis的时间戳,而不是系统时间,所以时钟不同步的影响相对较小。 注意: 在分布式系统中,确保所有服务器的时钟同步(例如通过NTP服务)仍然是一个好习惯,可以避免许多难以诊断的问题,尽管对ShedLock本身影响不大。
7. 测试分布式锁的挑战 测试ShedLock这种分布式锁,单机测试往往不够。 坑: 仅在单机上测试,你可能永远发现不了并发执行的问题。 注意: 务必在多实例、多节点的环境中进行充分的集成测试,模拟网络延迟、节点宕机等异常情况,验证ShedLock是否能正确地防止任务重复执行。这可能需要搭建一个简易的容器化环境(如Docker Compose)来模拟多实例部署。
8. 锁名称的唯一性@SchedulerLock注解中的name参数是锁的唯一标识。
注意: 确保每个需要独立控制的定时任务都有一个唯一的name。如果你有两个不同的定时任务,但它们使用了相同的name,那么它们会互相阻塞,导致只有一个任务能执行。这通常不是你想要的。
理解这些注意事项,能让你在使用ShedLock时少走很多弯路,让你的分布式定时任务跑得更稳健。
终于介绍完啦!小伙伴们,这篇关于《SpringBoot整合ShedLock分布式锁指南》的介绍应该让你收获多多了吧!欢迎大家收藏或分享给更多需要学习的朋友吧~golang学习网公众号也会发布文章相关知识,快来关注吧!
Windows10磁盘占用高怎么解决
- 上一篇
- Windows10磁盘占用高怎么解决
- 下一篇
- ZephirPHP扩展部署与配置全解析
-
- 文章 · java教程 | 37秒前 |
- Java正则表达式:字符串匹配与替换技巧
- 183浏览 收藏
-
- 文章 · java教程 | 22分钟前 |
- Java处理外部接口异常的正确方法
- 288浏览 收藏
-
- 文章 · java教程 | 27分钟前 | 多线程 reentrantlock 性能开销 公平锁 FIFO原则
- Java公平锁实现与ReentrantLock使用详解
- 271浏览 收藏
-
- 文章 · java教程 | 31分钟前 |
- Java文件未找到异常排查方法
- 484浏览 收藏
-
- 文章 · java教程 | 43分钟前 |
- Java开发图书推荐系统实战教程解析
- 278浏览 收藏
-
- 文章 · java教程 | 1小时前 | codePointAt Unicode编码 Java字符整数转换 补充字符 char类型
- Java字符与整数转换技巧
- 310浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- 卸载旧Java,安装最新版步骤
- 244浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- Java开发记账报表工具教程
- 342浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- Java数组去重i==j逻辑解析
- 486浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- Java处理IOException子类的正确方式
- 288浏览 收藏
-
- 文章 · java教程 | 2小时前 |
- 懒加载线程安全实现解析
- 171浏览 收藏
-
- 文章 · java教程 | 2小时前 |
- Java代理模式原理与应用解析
- 287浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3179次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3390次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3418次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4525次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 3798次使用
-
- 提升Java功能开发效率的有力工具:微服务架构
- 2023-10-06 501浏览
-
- 掌握Java海康SDK二次开发的必备技巧
- 2023-10-01 501浏览
-
- 如何使用java实现桶排序算法
- 2023-10-03 501浏览
-
- Java开发实战经验:如何优化开发逻辑
- 2023-10-31 501浏览
-
- 如何使用Java中的Math.max()方法比较两个数的大小?
- 2023-11-18 501浏览

