ShedLock日常使用


首發於個人博客:ShedLock日常使用

場景模擬

定時器Scheduler在平時使用比較頻繁,比如定時數據整理,定時向客戶發送問候信息等...,定時任務的配置比較簡單,比如在springboot中,配置好@Scheduled@EnableScheduling之后,定時器就能正常執行,實現定時任務的功能。

但是在這樣的情況下:如果開發的服務需要水平部署實現負載均衡,那么定時任務就會同時在多個服務實例上運行,那么一方面,可能由於定時任務的邏輯處理需要訪問公共資源從而造成並發問題;另一方面,就算沒有並發問題,那么一個同樣的任務多個服務實例同時執行,也會造成資源的浪費。因此需要一種機制來保證多個服務實例之間的定時任務正常、合理地執行。

ShedLock介紹

ShedLock的出現就是為了解決上述問題,它可以保證多個一個定時任務在多個服務實例之間最多只執行一次,是一個在分布式環境中保證定時任務合理執行的框架。

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

ShedLock的實現原理是采用公共存儲實現的鎖機制,使得同一時間點只有第一個執行定時任務的服務實例能執行成功,並在公共存儲中存儲"我正在執行任務,從什么時候(預計)執行到什么時候",其他服務實例執行時如果發現任務正在執行,則直接跳過本次執行,從而保證同一時間一個任務只被執行一次。

上面提到的公共存儲目前支持的有:

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

值得注意的是,ShedLock不是一個分布式的定時任務框架,只是一個鎖,用於保證分布式環境中的定時任務合理執行。貼一段原話:Please note that ShedLock is not and will never be full-fledged scheduler, it's just a lock. If you need a distributed scheduler, please use another project. ShedLock is designed to be used in situations where you have scheduled tasks that are not ready to be executed in parallel, but can be safely executed repeatedly. For example if the task is fetching records from a database, processing them and marking them as processed at the end without using any transaction. In such case ShedLock may be right for you.

依賴說明:

  • Java 8
  • slf4j-api
  • Spring Framework (optional)

ShedLock使用

以SpringBoot為例。

引入依賴:

<dependency>
    <groupId>net.javacrumbs.shedlock</groupId>
    <artifactId>shedlock-spring</artifactId>
    <version>2.3.0</version>
</dependency>

定義一個定時任務:

@Component
public class DataHouseKeepingScheduler {
    @Scheduled(cron = "*/5 * * * * ?")
    private void dataHouseKeeping(){
        System.out.println(String.format("[%s] DataHouseKeeping job run...", new Date()));
    }
}

添加ShedLock
在主類或者@EnableScheduling的地方添加@EnableSchedulerLock(defaultLockAtMostFor = "PT30S")

@SpringBootApplication
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "PT30S")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

然后在@Scheduled(...)的地方添加@SchedulerLock(name = "scheduledTaskName")

@Component
public class DataHouseKeepingScheduler {
    @Scheduled(cron = "*/5 * * * * ?")
    @SchedulerLock(name = "dataHouseKeepingLock", lockAtLeastForString = "20000", lockAtMostForString = "30000")
    private void dataHouseKeeping(){
        System.out.println(String.format("[%s] DataHouseKeeping job run...", new Date()));
    }
}

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

  • lockAtLeastForString: 鎖持有的最小時間(ms)。比如示例代碼中設定的5000ms表示當前方法執行時獲取到的鎖的最小時間是20s,20s之內,就算當前定時任務已經執行完成,其他任務也無法獲得鎖,必須等5s之后才能獲取。
  • lockAtMostForString: 鎖持有的最大時間(ms)。當前任務獲取的鎖的最大持有時間為30s,30s之后就算沒有執行完,其他定時任務也可以獲取鎖去執行任務。

配置LockProvider,此處以jdbc為例。先引入依賴:

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

配置@Bean:

@Component
public class ShedLockConfig {
    @Resource
    private DataSource dataSource;

    @Bean
    private LockProvider lockProvider(){
        return new JdbcTemplateLockProvider(dataSource);
    }
}

此處的DataSource即為當前應用的數據源。

注意,如果使用SqlServer需要設置schema,可以在JdbcTemplateLockProvider的構造方法中設置:

new JdbcTemplateLockProvider(dataSource, "schema.shedlock")

創建存儲鎖信息的表。

MySQL

```sql
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)
);

啟動應用,定時任務開始執行:

[Mon Feb 25 22:38:15 CST 2019] DataHouseKeeping job run...
[Mon Feb 25 22:38:35 CST 2019] DataHouseKeeping job run...
[Mon Feb 25 22:38:55 CST 2019] DataHouseKeeping job run...

可以看到,雖然定時任務設定的是每5秒執行一次,但是控制台輸出的卻是每20秒一次,這時就是lockAtLeastForString="20000"生效了。

這時候來看看表里的數據:

20190225224750.png

總結

再次強調,ShedLock不是一個定時任務框架,而是一個保證定時任務在分布式環境中的合理執行的輔助框架,保證定時任務在分布式環境中同一時間最多只執行一次。同時一個任務在執行時,另一個任務無法獲取鎖時會跳過當前任務的執行。

最后關於ShedLock的版本說明(直接貼原話了):
Version 1.x.x is compiled and tested with Spring 5 and Spring Data 2. It should be safe to use ShedLock 1.x.x with Spring 4 if you are not using Spring Redis lock provider which introduced incompatibility. In other words

  • If you have dependency on spring-data-redis 2 - use ShedLock 1.x.x
  • If you have dependency on spring-data-redis 1 - use ShedLock 0.x.x
  • In all other cases, you can use both versions, prefereably 1.x.x

Demo源碼地址

Demo源碼地址:https://gitee.com/nickhan/ShedLockDemo


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM