背景

在分布式多机集群环境下,一个服务可能有多个实例,其中的定时任务@Scheduled也会多次触发。在一些业务场景下,我们只希望他执行一次,此时可通过@SchedulerLock来对该定时任务进行加锁从而控制定时任务执行次数(也可通过Redis实现分布式锁,本文不做拓展)。

引入@SchedulerLock相关依赖

		<!--shedlock-->
        <dependency>
            <groupId>net.javacrumbs.shedlock</groupId>
            <artifactId>shedlock-spring</artifactId>
            <version>4.42.0</version>
        </dependency>
        
        <dependency>
            <groupId>net.javacrumbs.shedlock</groupId>
            <artifactId>shedlock-provider-jdbc-template</artifactId>
            <version>4.42.0</version>
        </dependency>

ShedLock使用外部存储,如Mongo,JDBC,Redis,Hazelcast,ZooKeeper等,本文采用JDBC。

创建shedlock表

CREATE TABLE `shedlock`  (
  `name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `lock_until` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `locked_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  `locked_by` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  PRIMARY KEY (`name`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

SET FOREIGN_KEY_CHECKS = 1;

shedlock表结构

添加LockProvider配置:ShedLockConfig

package com.ergo.demo.config;

import net.javacrumbs.shedlock.core.LockProvider;
import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;

@Component
public class ShedLockConfig {

    @Bean
    private LockProvider lockProvider(DataSource dataSource) {

        return new JdbcTemplateLockProvider(dataSource);
    }
}

配置数据源及数据库连接信息

引入JDBC、MySQL依赖

		<!--mysql jdbc-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

引入Druid数据源依赖

		<!--druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.23</version>
        </dependency>

application.properties

server.port=8081
spring.datasource.druid.url=jdbc:mysql://127.0.0.1:3306/data-center?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.druid.username=root
spring.datasource.druid.password=root
spring.datasource.druid.async-init=true
spring.datasource.druid.initial-size=5
spring.datasource.druid.min-idle=5
spring.datasource.druid.max-active=50
spring.datasource.druid.max-wait=6000
spring.datasource.druid.pool-prepared-statements=false
spring.datasource.druid.max-open-prepared-statements=20
spring.datasource.druid.validation-query=select 1
spring.datasource.druid.test-on-borrow=false
spring.datasource.druid.test-on-return=false
spring.datasource.druid.test-while-idle=true
spring.datasource.druid.time-between-eviction-runs-millis=60000
spring.datasource.druid.min-evictable-idle-time-millis=300000
spring.datasource.druid.max-evictable-idle-time-millis=172800000
spring.datasource.druid.keep-alive=true
spring.datasource.druid.time-between-log-stats-millis=86400000
spring.datasource.druid.filter.stat.enabled=true
spring.datasource.druid.filter.stat.log-slow-sql=true
spring.datasource.druid.filter.stat.slow-sql-millis=3000

创建定时任务

package com.ergo.demo.schedule;

import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @author ergo
 * @date 2023/3/31 14:43
 * @description 定时任务测试
 */
@Component
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "PT30S")
public class TestSchedule {

    @Scheduled(cron = "0/5 * * * * ? ")	// 5秒执行一次
    @SchedulerLock(name = "test",lockAtLeastFor = "20000",lockAtMostFor = "30000")
    public void test() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println(sdf.format(new Date()));
    }
}
注解 含义
@EnableScheduling 开启定时任务
@EnableSchedulerLock 开启定时任务锁,defaultLockAtMostFor属性表示默认最大锁持有时间,PT30S表示30秒。

@SchedulerLock

放在需要加锁的定时任务上

属性 含义
name 锁名称
lockAtLeastFor 任务节点锁的最短占有时间,此处为20s
lockAtMostFor 任务节点锁的最长占有时间,此处为30s

特别地,若属性lockAtLeastFor时间大于CRON表达式所表示的任务执行时间间隔,则任务实际执行时间间隔是lockAtLeastFor的值(对应此处的定时任务,会20s输出一次而不是5s)。

测试结果

idea启动多个节点

IDEA创建多实例

启动节点观察结果

在这里插入图片描述
在这里插入图片描述

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐