Movatterモバイル変換


[0]ホーム

URL:


SpringBoot 集成 ShedLock,完美解决定时任务重复执行难题

原创于 2025-03-09 11:00:00 发布·1.3k 阅读
· 16
· 21·
CC 4.0 BY-SA版权
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。
文章标签:

#spring boot

一、背景

在分布式系统中,定时任务的执行是常见需求,例如生成定期报表、清理过期数据或执行系统维护脚本。然而,随着微服务架构的普及,应用往往部署为多个实例,这带来了一个问题:如何确保同一时间仅有一个实例执行特定任务。如果未能妥善处理,将导致重复执行任务,可能造成资源浪费、数据不一致等问题,甚至对业务逻辑造成严重影响。

ShedLock 是一种轻量级解决方案,它通过使用锁机制,确保在分布式环境中只有一个实例能够执行某一任务。它既避免了重复执行任务的风险,又保持了系统的高可用性和一致性。

二、ShedLock的典型应用场景

分布式微服务:多个服务实例部署在不同节点时,确保定时任务不会被多个实例同时执行。

高可用保障:即使某些实例发生宕机或重启,任务调度依然能够按预期运行。

避免资源竞争:在负载均衡环境下,防止不同实例对同一任务的竞争执行。

ShedLock的官方文档地址如下:

https://gitee.com/jyycool/ShedLock/#usage

三、引入依赖

特别说明当前Spring Boot的版本为2.7.6,需要引入下述依赖:

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.6</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.bc</groupId><artifactId>demo</artifactId><version>0.0.1-SNAPSHOT</version><name>demo</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.hibernate</groupId><artifactId>hibernate-validator</artifactId><version>6.0.1.Final</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.0</version><scope>provided</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>2.7.6</version></dependency><dependency><groupId>net.javacrumbs.shedlock</groupId><artifactId>shedlock-spring</artifactId><version>4.1.0</version></dependency><dependency><groupId>net.javacrumbs.shedlock</groupId><artifactId>shedlock-provider-redis-spring</artifactId><version>4.1.0</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

四、配置文件

在resources目录下新建一个名为application.yml的文件,其配置信息如下所示:

server:  port: 8849spring:  application:    name: demo  redis:    host: 127.0.0.1    port: 6379

五、启用ShedLock 

在应用的主类中,添加 ShedLock 和定时任务的相关注解:

import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.scheduling.annotation.EnableScheduling;@SpringBootApplication@EnableScheduling@EnableSchedulerLock(defaultLockAtMostFor ="PT30S")public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}}

六、构建定时任务 

通过 @SchedulerLock 和 @Scheduled 注解来定义任务及其调度规则:

import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Component;@Componentpublic class ScheduledTask {    private static final Logger logger = LoggerFactory.getLogger(ScheduledTask.class);    @Scheduled(cron = "0/10 * * * * ?")    @SchedulerLock(name = "scheduledTaskName", lockAtLeastFor = "PT5S", lockAtMostFor = "PT30S")    public void scheduledTask() {        logger.info("Scheduled task is running...");    }}

@SchedulerLock注解主要参数如下:

name:锁的名称,必须保证唯一

lockAtLeastFor:成功执行任务的节点所能拥有的独占锁的最短时间,其主要目的是任务执行时间可能很短,执行后如果发上释放锁,可能会造成多个节点执行。

lockAtMostFor:成功执行任务的节点所能拥有的独占锁的最长时间,设置的值要保证比定时任务正常执行完成的时间大一些,此属性保证了如果task节点突然宕机,也能在超过设定值时释放任务锁; 

七、构建配置类

import net.javacrumbs.shedlock.core.LockProvider;import net.javacrumbs.shedlock.provider.redis.spring.RedisLockProvider;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.core.RedisTemplate;import javax.annotation.Resource;@Configurationpublic class ScheduledLockConfig {    @Resource    RedisTemplate redisTemplate;    @Bean    public LockProvider lockProvider() {        return new RedisLockProvider(redisTemplate.getConnectionFactory());    }}

八、测试

构建两个实例,一个端口号为88,另一个为8080,同时启动两个实例。观察日志输出。每个任务执行间隔为 10 秒,但日志表明在任何时间点只有一个实例执行任务。

2025-02-08 10:35:10.993 [scheduling-1] INFO  [com.example.demo.schedule.ScheduledTask:17] - Scheduled task is running...2025-02-08 10:35:20.004 [scheduling-1] INFO  [com.example.demo.schedule.ScheduledTask:17] - Scheduled task is running...2025-02-08 10:35:50.019 [scheduling-1] INFO  [com.example.demo.schedule.ScheduledTask:17] - Scheduled task is running...2025-02-08 10:36:00.017 [scheduling-1] INFO  [com.example.demo.schedule.ScheduledTask:17] - Scheduled task is running...
2025-02-08 10:35:30.036 [scheduling-1] INFO  [com.example.demo.schedule.ScheduledTask:17] - Scheduled task is running...2025-02-08 10:35:40.024 [scheduling-1] INFO  [com.example.demo.schedule.ScheduledTask:17] - Scheduled task is running...2025-02-08 10:36:10.013 [scheduling-1] INFO  [com.example.demo.schedule.ScheduledTask:17] - Scheduled task is running...

通过引入 Redis 作为锁的存储后端,系统不仅实现了任务执行的唯一性,还能在高并发和负载均衡的环境下保持稳定。

以下是本文解决的核心问题:

  1. 避免了任务的重复执行,提升了系统的一致性和可靠性。

  2. 通过 ShedLock 与 Redis 的结合,实现了任务锁的分布式存储,确保了高可用性。

  3. 在任务执行失败或锁超时时,系统能自动释放锁,避免了资源占用问题。

在实际业务中,ShedLock 可用于报表生成、批量任务处理、资源清理等场景。其易用性和可扩展性,使其成为处理分布式任务的重要工具。

九、个人思考

通过redisson中的分布式锁进行硬编码也是可以实现上述类似功能,需要注意的是合理评估任务执行时间和锁持有的时间。

import lombok.extern.slf4j.Slf4j;import org.redisson.api.RLock;import org.redisson.api.RedissonClient;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Component;import java.util.concurrent.TimeUnit;@Component@Slf4jpublic class ScheduledTask {    @Autowired    private RedissonClient redissonClient;    private final String key = "alarm_notification_key";    @Scheduled(cron = "0 0 10 * * ?")    public void alarmNotification() throws Exception {        RLock lock = redissonClient.getLock(key);        final int lockExpireTime = 10;        final int lockWaitTime = 2;        if (lock.tryLock(lockWaitTime, lockExpireTime, TimeUnit.SECONDS)) {            try {                log.info("告警通知检测");                Thread.sleep(7000);                /**                 * 业务处理                 */            } catch (Exception e) {                log.error("告警通知处理出现异常:", e);            } finally {                if (lock.isLocked() && lock.isHeldByCurrentThread()) {                    lock.unlock();                }            }        }    }}
关注博主即可阅读全文
确定要放弃本次机会?
福利倒计时
::

立减 ¥

普通VIP年卡可用
立即使用
参与评论您还未登录,请先登录后发表或查看评论

博客等级

码龄8年
782
原创
1289
点赞
2663
收藏
933
粉丝
关注
私信

TA的精选

查看更多

大家在看

TA的历史创作历程

分类专栏

展开全部收起

上一篇:
循环嵌套优化,代码耗时从 13721ms 优化到了 862ms
下一篇:
MyBatis 拦截器,带你轻松搞定数据脱敏!

目录

展开全部

收起

目录

展开全部

收起

上一篇:
循环嵌套优化,代码耗时从 13721ms 优化到了 862ms
下一篇:
MyBatis 拦截器,带你轻松搞定数据脱敏!

目录

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
被折叠的  条评论为什么被折叠?到【灌水乐园】发言
查看更多评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额0

抵扣说明:

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

余额充值

[8]ページ先頭

©2009-2025 Movatter.jp