ShedLock做定時任務鎖

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

目前公共存儲目前支持的有:

  • Monogo
  • DynamoDB
  • JdbcTemplate
  • ZooKeeper (using Curator)
  • Redis (using Spring RedisConnectionFactory)
  • Redis (using Jedis)
  • Hazelcast

這裏我用mysql做模擬:

pom文件:

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

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

        <dependency>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
            <version>8.0.15</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.28</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
        </dependency>
    </dependencies>

yml文件:


spring:
  datasource:
    name: ilabservice
    url: jdbc:mysql://localhost:3306/springcloud?useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT%2B8
    username: root
    password: 12345678
    driver-class-name: com.mysql.jdbc.Driver
    filters: stat
    maxActive: 20
    initialSize: 1
    maxWait: 60000
    minIdle: 1
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000

    validationQuery: select 'x'
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
    maxOpenPreparedStatements: 20
    initialize: false
  jpa:
    hibernate:
      show-sql: true

創建數據庫sql文件:

  • MySQL
CREATE TABLE shedlock(
    name VARCHAR(64), 
    lock_until TIMESTAMP(3) NULL, 
    locked_at TIMESTAMP(3) NULL, 
    locked_by  VARCHAR(255), 
    PRIMARY KEY (name)
) 
  • SQLServer
CREATE TABLE [dbo].[shedlock](
    [name] [varchar](64) NOT NULL,
    [lock_until] [datetime] NULL,
    [locked_at] [datetime] NULL,
    [locked_by] [varchar](255) NOT NULL,
 CONSTRAINT [PK_shedlock] PRIMARY KEY CLUSTERED 
(
    [name] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
  • Oracle
CREATE TABLE SHEDLOCK (
  name VARCHAR2(64 CHAR),
  lock_until TIMESTAMP,
  locked_at TIMESTAMP,
  locked_by  VARCHAR2(255 CHAR),
  PRIMARY KEY (name)
);

配置類


import com.alibaba.druid.pool.DruidDataSource;
import net.javacrumbs.shedlock.core.LockProvider;
import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;
import net.javacrumbs.shedlock.spring.ScheduledLockConfiguration;
import net.javacrumbs.shedlock.spring.ScheduledLockConfigurationBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.time.Duration;

@Component
public class ShedLockConfig {

    @Value("${spring.datasource.url}")
    private String dbUrl;

    @Value("${spring.datasource.username}")
    private String username;

    @Value("${spring.datasource.password}")
    private String password;

    @Value("${spring.datasource.initialSize}")
    private int initialSize;

    @Value("${spring.datasource.minIdle}")
    private int minIdle;

    @Value("${spring.datasource.maxActive}")
    private int maxActive;

    @Value("${spring.datasource.maxWait}")
    private int maxWait;

    @Value("${spring.datasource.timeBetweenEvictionRunsMillis}")
    private int timeBetweenEvictionRunsMillis;

    @Value("${spring.datasource.minEvictableIdleTimeMillis}")
    private int minEvictableIdleTimeMillis;

    @Value("${spring.datasource.validationQuery}")
    private String validationQuery;

    @Value("${spring.datasource.testWhileIdle}")
    private boolean testWhileIdle;

    @Value("${spring.datasource.testOnBorrow}")
    private boolean testOnBorrow;

    @Value("${spring.datasource.testOnReturn}")
    private boolean testOnReturn;

    @Value("${spring.datasource.poolPreparedStatements}")
    private boolean poolPreparedStatements;

    @Value("${spring.datasource.filters}")
    private String filters;

    @Value("{spring.datasource.connectionProperties}")
    private String connectionProperties;

    @Bean
    public LockProvider lockProvider(DataSource dataSource) {
        DruidDataSource datasource = new DruidDataSource();

        datasource.setUrl(this.dbUrl);
        datasource.setUsername(username);
        datasource.setPassword(password);
        datasource.setDriverClassName("com.mysql.jdbc.Driver");
        datasource.setInitialSize(initialSize);
        datasource.setMinIdle(minIdle);
        datasource.setMaxActive(maxActive);
        datasource.setMaxWait(maxWait);
        datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        datasource.setValidationQuery(validationQuery);
        datasource.setTestWhileIdle(testWhileIdle);
        datasource.setTestOnBorrow(testOnBorrow);
        datasource.setTestOnReturn(testOnReturn);
        datasource.setPoolPreparedStatements(poolPreparedStatements);
        try {
            datasource.setFilters(filters);
        } catch (SQLException e) {
        }
        datasource.setConnectionProperties(connectionProperties);
        return new JdbcTemplateLockProvider(datasource);
    }

    @Bean
    public ScheduledLockConfiguration scheduledLockConfiguration(LockProvider lockProvider) {
        return ScheduledLockConfigurationBuilder.withLockProvider(lockProvider)
                .withPoolSize(10)
                .withDefaultLockAtMostFor(Duration.ofMinutes(10))
                .build();
    }

}

主程序啓動類:

@EnableSchedulerLock,開啓定時任務鎖,指定一個默認的鎖的時間

@SpringBootApplication
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "PT30S")
public class ShedlockApplication {

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

}

定時任務測試類:

import lombok.extern.slf4j.Slf4j;
import net.javacrumbs.shedlock.core.SchedulerLock;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class TestTask {

    Integer count = 0;
    Integer count2 = 0;

    @Scheduled(cron = "* * * * * ?")
    @SchedulerLock(name = "testJob1", lockAtLeastFor = 6000)
    public void doTask() throws InterruptedException {
        log.info(Thread.currentThread().getName() + "===task run" + "----" + count);
        count++;
        // Thread.sleep(2000);
        Thread.sleep(10000);
        log.info(Thread.currentThread().getName() + "===task end");
    }

    @SchedulerLock(name = "testJob2", lockAtLeastFor = 4000)
    @Scheduled(initialDelay = 3000, fixedDelay = 1000)
    public void doTask2() throws InterruptedException {
        log.info(Thread.currentThread().getName() + "===task2 run" + "----" + count2);
        Thread.sleep(2000);
        count2++;
        log.info(Thread.currentThread().getName() + "===task2 end");
    }

}

@SchedulerLock的作用是保證當前定時任務的方法執行時獲得鎖,忽略其他相同任務的執行。但是,name必須要指定,ShedLock就是根據這個name進行相同任務判定的。註解中有五個個參數:

  • lockAtLeastForString:鎖持有的最小時間(ms)。表示當前方法執行時獲取到的鎖的最小時間是6000ms,6000ms之內,就算當前定時任務已經執行完成,其他任務也無法獲得鎖,必須等下一秒之後才能獲取。
  • lockAtMostForString:鎖持有的最大時間(ms)。假如當前任務獲取的鎖的最大持有時間爲30s,30s之後就算沒有執行完,其他定時任務也可以獲取鎖去執行任務。例如:PT30S 代表30秒
  • name:定時任務的名字,就是數據庫中的內個主鍵
  • lockAtMostFor:鎖的最大時間單位爲毫秒
  • lockAtLeastFor:鎖的最小時間單位爲毫秒

分別啓動兩個實例,一個2000毫秒一個10000毫秒:

從輸出日誌中我們可以看到,兩個節點在交替執行任務,沒有重複執行。

來看一下表裏的數據:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章