ShedLock是一个锁,官方解释是他永远只是一个锁,并非是一个分布式任务调度器。一般shedLock被使用的场景是,你有个任务,你只希望他在单个节点执行,而不希望他并行执行,而且这个任务是支持重复执行的。如对某些查询出来的数据打标签,没有任何的事务性处理操作。以上,官方的解释比较拗口,具体可以参见github上的wiki。
我的理解,ShedLock是一个悲观锁,而无论悲观锁还是乐观锁,他的实现,必须借助于公共存储。
所以,对于shedLock,需要实现分布式锁,他也需要借助于共享存储,目前支持如下几种方式:
所以,这个和zookeeper或者redis的理论基本类似。只是这个shedLock封装的更好,不需要我们去实现过多的客户端。而像zookeeper或者redis,需要我们自己实现。具体可以看我之前写的那篇《任务调度总结》文章。还有我github或者码云上的sample。
shedLock支持注解,和spring结合使用更简单。我们来看看他的使用方式。我们以共享存储使用mysql为例子:
- 首先,需要在你的项目中引入两个包,假设项目是maven构建:
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-jdbc-template</artifactId>
<version>2.2.0</version>
</dependency>
- 其次,在你的启动类中增加注解支持。
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "PT30S")
稍后会说明下,这个lockatMostFor的含义。
- 下一步,需要初始化Provider支持。操作数据库。
@Configuration
public class LockProviderConfiuration {
@Autowired DataSource dataSource;
@Bean
public LockProvider lockProvider () {
return new JdbcTemplateLockProvider(dataSource);
}
}
这里全局datasource需要事先创建好。在这里可以直接注入使用。然后实例化jdbcTemplateLockProvider。
- 建表
CREATE TABLE shedlock(
name VARCHAR(64),
lock_until TIMESTAMP(3) NULL,
locked_at TIMESTAMP(3) NULL,
locked_by VARCHAR(255),
PRIMARY KEY (name)
)
相信看到这里大家都明白了,name是全局唯一的。用这个来标识全局唯一的定时任务。用此来变相实现一个悲观锁。
- 以上做好了后,可以开始写你的定时任务了,我们这里写一个测试类
@Component
public class RiskAdminScheduler {
private static final String SIXTY_MIN = "PT60M";
private static final String THREE_MIN = "PT3M";
@Scheduled(cron = "0 */1 * * * ? ")
@SchedulerLock(name = "test",lockAtLeastForString = THREE_MIN,lockAtMostForString = SIXTY_MIN)
public void test() {
System.out.println(LocalDateTime.now());
System.out.println(Thread.currentThread().getName()+"--executed......");
}
}
@Scheduler(cron=xxxx) 这个是spring的定时任务触发器。每分钟跑一次。
@SchedulerLock这个是shedlock的注解方式。
name的含义:定时任务的名称。这个必须全局唯一。这个是定时任务的标识。
lockAtLeastForString & lockAtMostForString我想引用官方的解释更明白一点:By setting lockAtMostFor we make sure that the lock is released even if the node dies and by setting lockAtLeastFor we make sure it's not executed more than once in fifteen minutes.
意思就是,当节点挂掉后,这个锁还是要释放的。最长时间就是most设置的,和zookeeper中的临时节点相似,和redis设置超时时间类似。而为了防止集群启动的先后或者各节点 没有做时钟同步,用了least来防止这种情况下,重复起任务的状况。
这里需要注意的是,least时间设置了后,比如我这里是1分钟定时任务执行一次,而least设置是2分钟。那么也就是2分钟是持有锁的最小时间,2分钟后才释放。所以1分钟执行完的定时任务,必须等到2分钟结束,锁释放后,才能再次执行。当然,实际我们业务中不会出现这种每分钟执行的业务相关的定时任务,即使有,那么情况也比较,比如快速掉单查询,以前做支付的时候,那也是支持并发的,不需要分布式锁的。这里提醒下大家注意下使用场景。大部分场景我们还是可以用这个shedlock的。
我们看下表中的数据:
很明显,持有时间是21:06,释放时间是21:09,就是least设置的时间。大家使用的时候注意下。
好了,下次有时间再讲解下他的内部实现。
工作和生活的乐趣在于分享和总结。