SpringBoot整合ShedLock分布式锁详解
还在为Spring Boot应用中定时任务的并发执行问题头疼吗?本文为你带来ShedLock分布式锁的详细教程,ShedLock通过共享存储(如数据库、Redis)实现定时任务的互斥执行,确保在分布式环境下任务不被重复执行。本文将深入讲解如何在Spring Boot项目中整合ShedLock,包括引入依赖、配置`@EnableScheduling`和`@EnableSchedulerLock`,以及使用`@SchedulerLock`注解。同时,还将剖析ShedLock的工作原理,对比其与Redisson、ZooKeeper等通用锁方案的差异,并提供使用ShedLock时需要注意的关键点,如`lockAtMostFor`与任务时间匹配、任务幂等性保障及存储性能优化等,助你轻松解决分布式定时任务的并发难题。
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学习网公众号下多多回复交流;文中若有不正之处,也希望回复留言以告知!

- 上一篇
- Golang错误处理机制与核心思想解析

- 下一篇
- Java邮件SSL配置全攻略
-
- 文章 · java教程 | 8分钟前 |
- Java反射修改final字段技巧
- 467浏览 收藏
-
- 文章 · java教程 | 15分钟前 |
- Java多线程三种创建方式详解
- 293浏览 收藏
-
- 文章 · java教程 | 46分钟前 | 内存泄漏 引用 threadlocal 堆快照 MAT
- Java内存泄漏定位与解决技巧
- 358浏览 收藏
-
- 文章 · java教程 | 52分钟前 |
- 空对象模式:优雅应对NullPointerException
- 301浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- Java性能优化方法与实用技巧
- 493浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- Java反射修改final字段技巧
- 232浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- SpringCloudAuthService配置问题解决方法
- 319浏览 收藏
-
- 文章 · java教程 | 1小时前 | 时区 java8 SimpleDateFormat DateTimeFormatter 日期时间格式化与解析
- Java日期时间格式化与解析方法有哪些
- 422浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- 量子密钥怎么用?Java实现QKD协议教程
- 410浏览 收藏
-
- 文章 · java教程 | 2小时前 |
- Android高效滚动表格与列表技巧
- 459浏览 收藏
-
- 文章 · java教程 | 2小时前 |
- SpringBoot多数据源分库分表教程
- 433浏览 收藏
-
- 文章 · java教程 | 2小时前 |
- MyBatisforeach标签用法与实战教程
- 369浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 508次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 484次学习
-
- 免费AI认证证书
- 科大讯飞AI大学堂推出免费大模型工程师认证,助力您掌握AI技能,提升职场竞争力。体系化学习,实战项目,权威认证,助您成为企业级大模型应用人才。
- 32次使用
-
- 茅茅虫AIGC检测
- 茅茅虫AIGC检测,湖南茅茅虫科技有限公司倾力打造,运用NLP技术精准识别AI生成文本,提供论文、专著等学术文本的AIGC检测服务。支持多种格式,生成可视化报告,保障您的学术诚信和内容质量。
- 161次使用
-
- 赛林匹克平台(Challympics)
- 探索赛林匹克平台Challympics,一个聚焦人工智能、算力算法、量子计算等前沿技术的赛事聚合平台。连接产学研用,助力科技创新与产业升级。
- 220次使用
-
- 笔格AIPPT
- SEO 笔格AIPPT是135编辑器推出的AI智能PPT制作平台,依托DeepSeek大模型,实现智能大纲生成、一键PPT生成、AI文字优化、图像生成等功能。免费试用,提升PPT制作效率,适用于商务演示、教育培训等多种场景。
- 181次使用
-
- 稿定PPT
- 告别PPT制作难题!稿定PPT提供海量模板、AI智能生成、在线协作,助您轻松制作专业演示文稿。职场办公、教育学习、企业服务全覆盖,降本增效,释放创意!
- 169次使用
-
- 提升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浏览