Spring Cloud Alibaba -- 分布式定时任务解决方案(轻量级、快速构建)(ShedLock 、@SchedulerLock )


一、 ShedLock简介

ShedLock 是一个用于 Java 和 Spring Boot 应用的开源分布式锁库,旨在确保在分布式环境下定时任务能够被安全地调度和执行。它的主要目的是防止在多节点环境中定时任务的重复执行,通过使用锁机制来实现这一目标。ShedLock 的设计哲学是轻量级和易于集成,特别适合那些不需要复杂调度逻辑的场景,但需要确保定时任务的执行不会发生竞态条件或资源冲突。

ShedLock 的特点
1、分布式锁:ShedLock 使用外部存储(如数据库或 Redis)来实现分布式锁,确保即使在多台服务器上运行相同的服务,同一时刻只有一个实例执行特定的定时任务。

2、轻量级:相对于像 Quartz 这样的全面调度框架,ShedLock 更专注于解决分布式定时任务的互斥执行问题,提供了简单的接口和较少的配置选项。

3、易用性:ShedLock 与 Spring Boot 的集成非常平滑,通过注解和自动配置简化了定时任务的管理,只需要使用@SchedulerLock即可。

4、Cron 和固定间隔支持:虽然 ShedLock 的调度功能相对简单,但它仍然支持 Cron 表达式和固定间隔的定时任务调度。

5、健壮性:ShedLock 能够处理任务失败的情况,提供重试机制,并且能够优雅地处理节点故障。


二、 @SchedulerLock

@SchedulerLock 注解是一个分布式锁的框架,结合@Scheduled 注解,可以保证任务同一时间,在多个节点上只会执行一次。该框架支持多种分布式锁的实现,比如Jdbc、Zookeeper、Redis等。
原理图如下:
在这里插入图片描述


三、基于Mysql方式使用步骤

1.建表

建表语句代码如下:

# 建表sql
CREATE TABLE shedlock(
	name VARCHAR(64) NOT NULL,
	lock_until TIMESTAMP(3) NOT NULL,
    locked_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
    locked_by VARCHAR(255) NOT NULL,
    PRIMARY KEY (name)
);

2.引入依赖

<!--    shedlock的依赖    -->
        <dependency>
            <groupId>net.javacrumbs.shedlock</groupId>
            <artifactId>shedlock-spring</artifactId>
            <version>4.23.0</version>
        </dependency>
        <dependency>
            <groupId>net.javacrumbs.shedlock</groupId>
            <artifactId>shedlock-provider-jdbc-template</artifactId>
            <version>4.23.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>

3.Mysql连接配置

# 应用服务 WEB 访问端口
server.port=8080
spring.datasource.url=jdbc:mysql://192.168.10.26:19131/ch?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=sa123456
spring.datasource.driver-class=com.mysql.cj.jdbc.Driver

4.ScheduledLock配置

package com.example.schedulerlock.config;

import net.javacrumbs.shedlock.core.LockProvider;
import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;
import java.util.TimeZone;

/**
 * @Title: ScheduledLockConfig
 * @Author ch
 * @Date 2024/7/9 11:13
 * @description:
 */
@Configuration
public class ScheduledLockConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public LockProvider lockProvider() {
        return new JdbcTemplateLockProvider(JdbcTemplateLockProvider.Configuration.builder()
                .withJdbcTemplate(new JdbcTemplate(dataSource))
                .withTimeZone(TimeZone.getTimeZone("UTC"))
                .build());
    }
}

5.启动类配置

package com.example.schedulerlock;

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 = "3m")
public class MySchedulerLockApplication {

    public static void main(String[] args) {
        SpringApplication.run(MySchedulerLockApplication.class, args);
    }

}

6.创建定时任务

package com.example.schedulerlock.job;

import lombok.extern.slf4j.Slf4j;
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.joda.time.DateTime;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

/**
 * @Title: SpringJob
 * @Author ch
 * @Date 2024/7/9 11:19
 * @description:
 */
@Slf4j
@Component
public class SpringJob {
    /**
     * 每5分钟跑一次
     */
    @Scheduled(cron = "0 */5 * * * ?")
    @SchedulerLock(name = "SpringJob.job1", lockAtMostFor = "2m", lockAtLeastFor = "1m")
    public void job1() {
        log.info("time=" + DateTime.now().toString("YYYY-MM-dd HH:mm:ss") + " do job1...");
    }

    /**
     * 每5秒跑一次
     */
    @Scheduled(fixedRate = 5000)
    @SchedulerLock(name = "SpringJob.job2", lockAtMostFor = "4s", lockAtLeastFor = "4s")
    public void job2() {
        log.info("time=" + DateTime.now().toString("YYYY-MM-dd HH:mm:ss") + " do job2...");
    }

    /**
     * 上次跑完之后隔5秒再跑
     * @throws InterruptedException
     */
    @Scheduled(fixedDelay = 5000)
    @SchedulerLock(name = "SpringJob.job3", lockAtMostFor = "4s", lockAtLeastFor = "4s")
    public void job3() throws InterruptedException {
        log.info("time=" + DateTime.now().toString("YYYY-MM-dd HH:mm:ss") + " do job3...");
        Thread.sleep(10000);
    }
}

7.启动多个项目服务进行测试

在这里插入图片描述

8.SchedulerLock注解说明

name:用来标注一个定时服务的名字,被用于写入数据库作为区分不同服务的标识,如果有多个同名定时任务则同一时间点只有一个执行成功。

lockAtMostFor: 这个属性指定了锁最多可以持有的时间长度。如果任务执行时间超过了给定时间,锁也将强制释放,以便其他等待的任务有机会获取锁并执行。但是,这也意味着如果任务执行时间超过锁的持有时间,可能会出现并发执行的情况。

lockAtLeastFor: 这个属性指定了锁至少可以持有的时间长度。这可以防止在任务执行结束后立即有另一个任务尝试获取相同的锁,从而产生不必要的锁争用。


四、使用注意事项

1、@SchedulerLock结合@Scheduled(cron = …):
利用 Cron 表达式来精确控制任务的执行时间点,同时利用 SchedulerLock 来保证任务执行的互斥性。Cron 表达式提供了更精细的时间控制,可以避免任务堆叠,而 SchedulerLock 则确保了即使在多个节点上部署,同一时刻也只有一个任务实例在运行。

2、@SchedulerLock结合@Scheduled(fixedRate = …)和(fixedDelay = …):
fixedRate 和fixedDelay 它们分别按照固定速率和固定延迟执行任务。这意味着无论上一次任务执行是否完成,到达设定的时间间隔后都会触发下一次执行。这种模式下,如果任务执行时间超过设定的间隔,可能会出现任务堆叠的情况,即多个任务实例同时运行。

SchedulerLock 的作用机制在于它会在任务开始执行前尝试获取一个锁,只有获取到锁的任务实例才能继续执行。执行完成后,锁会被释放,下一个任务实例才有机会获取锁并执行。这一机制非常适合保证任务的互斥执行,但在 fixedRate 或 fixedDelay 的场景下,由于任务的触发不依赖于前一个任务的完成,而是严格基于时间间隔,所以 SchedulerLock 只能保证每次任务开始执行时的互斥性,而无法直接控制任务的触发频率。

在分布式环境中使用 fixedRate 或 fixedDelay 时,如果不加额外控制,可能会导致多个节点上的任务实例几乎同时触发,从而违反了分布式定时任务的基本要求——确保同一时刻只有一个任务实例运行。此时,即使使用了 SchedulerLock,也可能因为多个节点几乎同时尝试获取锁而增加锁的争用,进而影响性能。

总之,SchedulerLock 与 fixedRate 或 fixedDelay 的结合使用需要谨慎,因为两者的设计目的和工作原理不同,可能不会达到预期的效果。在分布式定时任务的场景下,推荐使用 SchedulerLock 结合 Cron 表达式的方式,以获得更好的互斥性和时间控制。

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-10 04:46:09       49 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-10 04:46:09       53 阅读
  3. 在Django里面运行非项目文件

    2024-07-10 04:46:09       42 阅读
  4. Python语言-面向对象

    2024-07-10 04:46:09       53 阅读

热门阅读

  1. 大模型推理:vllm多机多卡分布式本地部署

    2024-07-10 04:46:09       43 阅读
  2. 调度的艺术:Eureka在分布式资源调度中的妙用

    2024-07-10 04:46:09       26 阅读
  3. 前后端的身份认证(学习自用)

    2024-07-10 04:46:09       22 阅读
  4. 计算机网络和因特网

    2024-07-10 04:46:09       25 阅读
  5. MySQL DDL

    MySQL DDL

    2024-07-10 04:46:09      24 阅读
  6. vue父子组件通信实现模糊搜索功能

    2024-07-10 04:46:09       23 阅读
  7. C#与物联网:打造智能家居解决方案

    2024-07-10 04:46:09       31 阅读
  8. FlutterWeb渲染模式及提速

    2024-07-10 04:46:09       24 阅读