背景
spring shedlock用于在分布式服务的情况下执行定时任务,例如定时删除数据库中的一些数据,做数据迁移等等操作。这项技术在项目的分布式服务中大量使用。 使用的主要原因有以下几点:
- 定时任务的业务需要,要在服务service正常运行的过程当中同时在背后执行一些操作,满足我们的业务需要,定时任务也就是schedule task必不可少。
- 分布式服务的要求。试想下面一个场景: 随着业务增长,有一天单个服务service的压力过大,一个服务支撑不住了,我们要考虑部署多个服务来分散压力。这时问题就来了,之前的定时任务,在各个服务上全都会跑,做着同样的事情,并且还会造成一些并发问题。这必然不是我们想要的结果,这时我们就会发现,虽然有多个服务,但是我们只能让这样的定时任务执行一次. 这时就可以考虑通过数据库来控制任务。因为多个服务的数据库依然是同一个。
用法配置
以postgres为例,我们可以按如下方式使用shedlock
首先我们需要引入对应的依赖:
<!-- shed lock -->
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId>
<version>${schedlock.version}</version>
</dependency>
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-jdbc-template</artifactId>
<version>${schedlock.version}</version>
</dependency>
然后我们需要创建一张shedlock需要用来存储scheduler lock信息的table
CREATE TABLE shedlock (
name VARCHAR(64),
lock_until TIMESTAMP(3) NULL,
locked_at TIMESTAMP(3) NULL,
locked_by VARCHAR(255),
PRIMARY KEY (name)
)
通常,我们以锁的名字作为表的主键即可. 然后我们需要配置LockProvider, 使得访问数据库的时候使用shedlock
@Configuration
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "30s")
public class SchedulerConfiguration {
@Bean
public LockProvider lockProvider(final DataSource dataSource) {
return new JdbcTemplateLockProvider(dataSource);
}
}
有了以上配置之后我们就可以创建task了.
创建tasks
要创建一个由shedlock控制的schedule task,我们可以将@Scheduled和@SchedulerLock?注解放在对应的方法上即可.
@Component
public class SchedulerTaskTrigger {
@Scheduled(fixedDelayString = "PT30S")
@SchedulerLock(name = "TaskScheduler_scheduledTask",
lockAtLeastForString = "30s",
lockAtMostForString = "3h")
public void scheduledTask() {
// ...
}
}
然后,task里面的逻辑就会按照我们的配置所执行,上面的配置当中,最短lock时间设置为了30s,最长设置为了3小时,也就是说每一次task执行,这个task最少也会lock 30秒,而最长会lock3个小时,取决于实际执行任务的情况。
并且,在分布式多服务同时运行的情况下,同一时间也只会有一个服务执行task任务。
若每次执行任务都花了30s,那么我们就会在数据库里的表里看到下面的现象:
下一次执行:
可以看到每次从lockedat到lock until之间的时间都是30s,而上一次的lock until则会成为下一次的lock at时间。 如果查看task所执行的sql的话,我们会发现每次job执行,shedlock都会执行下面的两条sql:
//task started
UPDATE shedlock SET lock_until = ?, locked_at = ?, locked_by = ? WHERE name = ? AND lock_until <= ?;
//task execution
...
//task end
UPDATE shedlock SET lock_until = ? WHERE name = ?;
从本质上来说,shedlock解决分布式服务定时任务的做法,是借助了多个服务是基于同一数据库这一点。也就是说虽然是分布式的多个服务,但是他们依然依赖了同样的资源,即数据库。其他的一些解决方案,例如使用zookeeper或者使用redis的解决原理其实本质是一模一样的。 这个问题本质上也就是一个分布式锁的问题。解决办法自然就是寻找分布式服务共享的资源
?
|