本文介绍shelLock(https://github.com/lukas-krecan/ShedLock),实现分布式定时任务锁。

在实际开发中,通常会有多个server,代码中如果使用spring-boot的schedule定时任务,那么就会导致定时任务多次触发。使用分布式锁可以解决这个问题,这里介绍下shelLock。

要使用ShedLock,请执行以下操作

  1. 启用和配置定时锁定
  2. 为计划任务添加注释
  3. 配置LockProvider
  • 启用和配置定时锁定
    首先,我们必须导入这个项目
<dependency>
    <groupId>net.javacrumbs.shedlock</groupId>
    <artifactId>shedlock-spring</artifactId>
    <version>4.23.0</version>
</dependency>

现在我们需要将库与Spring集成。要启用计划锁定,请使用@EnableSchedulerLock注释

@Configuration
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "10m")
class MySpringConfiguration {
    ...
}

  • 为计划任务添加注释
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;

...

@Scheduled(...)
@SchedulerLock(name = "scheduledTaskName")
public void scheduledTask() {
   // To assert that the lock is held (prevents misconfiguration errors)
   LockAssert.assertLocked();
   // do something
}

@SchedulerLock注释有几个用途。首先,只有带注释的方法被锁定,库忽略所有其他计划任务。您还必须指定锁的名称。同一时间只能执行一个同名任务。

您还可以设置lockAtMostFor属性,该属性指定在执行节点死亡的情况下锁应该保持多长时间。这只是一种回退,在正常情况下,一旦任务完成,锁就会被释放。必须将lockamostfor设置为比正常执行时间长得多的值。如果任务花费的时间超过lockamost,则结果行为可能不可预测(多个进程将有效地持有锁)。

如果未在@SchedulerLock中指定lockamostfor,将使用@EnableSchedulerLock的默认值。

最后,您可以设置lockAtLeastFor属性,该属性指定应该保留锁的最短时间。它的主要目的是防止在任务很短且节点间存在时钟差的情况下从多个节点执行。

所有注释都支持Spring表达式语言(SpEL)。

eg:
假设您有一个任务,每15分钟执行一次,通常运行几分钟。此外,您最多希望每15分钟执行一次。在这种情况下,可以这样配置:

import net.javacrumbs.shedlock.core.SchedulerLock;


@Scheduled(cron = "0 */15 * * * *")
@SchedulerLock(name = "scheduledTaskName", lockAtMostFor = "14m", lockAtLeastFor = "14m")
public void scheduledTask() {
   // do something
}

通过设置lockAtMostFor,我们可以确保即使节点死亡,锁也会被释放;通过设置lockAtMostFor,我们可以确保在15分钟内不会执行多次。请注意,lockamostfor只是一个安全网,以防执行任务的节点死亡,因此将其设置为明显大于最大估计执行时间的时间。如果任务花费的时间超过lockamostfor,则可能会再次执行该任务,并且结果将不可预测(更多进程将持有锁)。

  • 配置LockProvider
    LockProvider有几种实现。
    JdbcTemplate
    首先,创建锁表(请注意名称必须是主键)
# MySQL, MariaDB
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));

# Postgres
CREATE TABLE shedlock(name VARCHAR(64) NOT NULL, lock_until TIMESTAMP NOT NULL,
    locked_at TIMESTAMP NOT NULL, locked_by VARCHAR(255) NOT NULL, PRIMARY KEY (name));

# Oracle
CREATE TABLE shedlock(name VARCHAR(64) NOT NULL, lock_until TIMESTAMP(3) NOT NULL,
    locked_at TIMESTAMP(3) NOT NULL, locked_by VARCHAR(255) NOT NULL, PRIMARY KEY (name));

# MS SQL
CREATE TABLE shedlock(name VARCHAR(64) NOT NULL, lock_until datetime2 NOT NULL,
    locked_at datetime2 NOT NULL, locked_by VARCHAR(255) NOT NULL, PRIMARY KEY (name));

# DB2
CREATE TABLE shedlock(name VARCHAR(64) NOT NULL PRIMARY KEY, lock_until TIMESTAMP NOT NULL,
    locked_at TIMESTAMP NOT NULL, locked_by VARCHAR(255) NOT NULL);

添加maven依赖

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

配置

import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;

...
@Bean
public LockProvider lockProvider(DataSource dataSource) {
            return new JdbcTemplateLockProvider(
                JdbcTemplateLockProvider.Configuration.builder()
                .withJdbcTemplate(new JdbcTemplate(dataSource))
                .usingDbTime() // Works on Postgres, MySQL, MariaDb, MS SQL, Oracle, DB2, HSQL and H2
                .build()
            );
}

通过指定usingDbTime(),lockProvider将基于DB服务器时钟使用UTC时间。如果不指定此选项,将使用应用程序服务器的时钟(应用程序服务器上的时钟可能不同步,从而导致各种锁定问题)。

如果需要指定模式,可以使用常用的点符号new JdbcTemplateLockProvider(datasource,“my\u schema.shedlock”)在表名中设置它

注意:
不要手动从DB表中删除锁的行数据。ShedLock具有现有锁行的内存缓存,因此在应用程序重新启动之前不会自动重新创建该行。如果需要,您可以编辑行,但是这只会导致持有多个锁。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐