SpringBoot - ShedLock:分布式任务锁从入门到实战

在这里插入图片描述


概述

在分布式系统中,定时任务的重复执行是一个常见而棘手的问题。当多个服务实例同时运行同一个定时任务时,如果没有适当的协调机制,就会导致数据不一致、资源浪费甚至更严重的业务问题。幸运的是,ShedLock 为我们提供了一个优雅的解决方案,它是一个开源库,旨在帮助我们轻松地在分布式环境中实现分布式锁,确保定时任务只被执行一次。

Github

https://github.com/lukas-krecan/ShedLock

什么是 ShedLock?

ShedLock 是一个轻量级的 Java 库,它通过利用外部存储(如数据库、Redis、ZooKeeper 等)来协调多个应用实例之间的定时任务执行。它的核心思想是:在任务执行前尝试获取一个锁,如果获取成功则执行任务,否则跳过。这样就保证了在给定时间内,无论有多少个服务实例,只有一个实例能够成功执行该任务。

ShedLock 的工作原理

ShedLock 的工作原理非常直观:

  1. 定义锁: 在需要加锁的定时任务方法上添加 @SchedulerLock 注解,并指定一个唯一的 name。这个 name 将作为锁的标识。
  2. 尝试获取锁: 当定时任务被触发时,ShedLock 会根据 name 尝试在配置的外部存储中创建一个锁记录。
  3. 成功获取锁: 如果创建成功,则表示当前实例获得了锁,任务开始执行。ShedLock 会在锁记录中记录执行时间、锁定时间等信息。
  4. 失败获取锁: 如果创建失败(即其他实例已经获取了该锁),则当前实例会跳过任务执行。
  5. 释放锁(可选): 任务执行完成后,ShedLock 会根据配置自动释放锁(通过删除或更新锁记录)。你也可以配置锁在一定时间后自动过期,防止死锁。

ShedLock 的主要特点

  • 易于集成: ShedLock 可以与 Spring Scheduler、QuartZ 等主流的定时任务框架无缝集成。
  • 多种存储支持: 支持多种后端存储,包括但不限于:
    • JDBC: 最常用也是最推荐的方式,利用数据库表的唯一性约束实现锁。
    • Redis: 适用于对性能要求较高,且已经使用 Redis 的项目。
    • ZooKeeper: 基于 ZooKeeper 的分布式协调能力实现锁。
    • MongoDB: 同样可以利用其文档特性实现锁。
  • 灵活的配置: 可以配置锁的名称、锁定时间、最小执行间隔等。
  • 幂等性支持: 即使任务在执行过程中发生故障,ShedLock 也能保证在下一个调度周期内重新获取锁并尝试执行,从而实现任务的幂等性。
  • 健康检查: 可以配置在获取锁失败时抛出异常,以便进行监控和告警。

如何使用 ShedLock (Redis 存储)

1. 添加依赖

首先,在 pom.xml 文件中添加 ShedLock 的 Spring 和 Redis 依赖:

<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-redis-spring</artifactId>
    <version>5.x.x</version> </dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

最新版本为 6.x 以上(需要 JDK 17+ 对应 Spring 6+)

2. 配置 ShedLock

在 Spring Boot 应用中,创建一个配置类来启用 ShedLock,并配置 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.connection.RedisConnectionFactory;
import org.springframework.scheduling.annotation.EnableScheduling;

@Configuration
@EnableScheduling // 启用 Spring 的定时任务
@EnableSchedulerLock(defaultLockAtMostFor = "PT30S") // 启用 ShedLock,并设置默认锁最长持有时间为30秒
public class ShedLockConfig {

    @Bean
    public LockProvider lockProvider(RedisConnectionFactory connectionFactory) {
        return new RedisLockProvider(connectionFactory);
    }
}

@EnableSchedulerLock(defaultLockAtMostFor = "PT30S") 配置了默认的锁最长持有时间为 30 秒(ISO 8601 Duration 格式)。这意味着即使任务失败,锁也会在 30 秒后自动释放,防止死锁。

注意: 确保 application.propertiesapplication.yml 中已经配置了 Redis 连接信息,例如:

spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=

3. 在定时任务中使用 ShedLock

在定时任务方法上添加 @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 = "myRedisTaskName", lockAtMostFor = "PT5M", lockAtLeastFor = "PT1M")
    public void executeMyTask() {
        // 只有获取到锁的实例才能执行到这里
        System.out.println("Executing my task at: " + System.currentTimeMillis());
        // 模拟任务执行
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println("My task finished at: " + System.currentTimeMillis());
    }
}
  • name = "myRedisTaskName":这是锁的唯一标识符,ShedLock 会在 Redis 中以这个名称作为 Key 来操作。
  • lockAtMostFor = "PT5M":指定该锁最多持有 5 分钟。如果任务执行时间超过 5 分钟,锁也会自动释放。
  • lockAtLeastFor = "PT1M":指定该锁最少持有 1 分钟。即使任务很快完成,锁也会保持 1 分钟,避免在极短时间内重复执行。

ShedLock 的优势与适用场景

优势:

  • 简单易用: 通过注解即可轻松实现分布式锁。
  • 低侵入性: 无需修改现有的定时任务代码逻辑。
  • 可靠性高: 借助外部存储的原子操作,保证了锁的可靠性。
  • 高可扩展性: 支持多种后端存储,可以根据项目需求灵活选择。
  • 性能优越(Redis): 使用 Redis 作为存储,通常能提供比关系型数据库更高的锁操作性能,适用于对并发要求较高的场景。

适用场景:

  • 批量数据处理: 确保批量任务只被一个实例执行,避免数据重复处理。
  • 定时数据同步: 保证数据同步任务的唯一性,防止数据混乱。
  • 消息队列消费: 在消费者组模式下,防止消息被重复消费(虽然许多MQ自带幂等性)。
  • 资源清理: 确保资源清理任务只被执行一次,避免误删或重复操作。

适合场景:简单分布式环境下对定时任务做互斥执行。

不适合:

  • 对强一致性、高可用要求极高的任务调度(非完整调度系统): 某些复杂场景下推荐 Quartz/XxlJob 等分布式调度框架

  • 时间配置谨慎: lockAtMostFor 必须大于任务最长执行时间;lockAtLeastFor 用于防止频繁重试产生重复执行。

  • 时钟同步要求: 若集群节点时钟漂移大,可能锁判断失效。

总结

ShedLock 是一个非常实用的分布式锁解决方案,它能够有效地解决分布式系统中定时任务重复执行的问题,从而提升系统的稳定性和数据的一致性。通过简单配置和注解,就可以轻松地为你的定时任务加上一把“安全锁”,告别重复执行的烦恼!当性能和并发量是重要考虑因素时,选择 Redis 作为 ShedLock 的存储后端是一个非常明智的选择

如果你正在构建分布式系统,并且遇到了定时任务的并发执行问题,ShedLock 绝对值得你尝试!

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小小工匠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值