ShedLock确保计划的任务最多同时执行一次。如果一个任务正在一个节点上执行,则它会获得一个锁,以防止从另一个节点(或线程)执行同一任务。请注意,如果一个任务已经在一个节点上执行,则在其他节点上的执行不会等待,只是将其跳过。
ShedLock可以使用Mongo,JDBC数据库,Redis,Hazelcast,ZooKeeper或其他外部存储进行协调。
需要注意的是,ShedLock不是分布式调度程序,它只是一把锁。锁是基于时间的,ShedLock假定节点上的时钟已同步。所以,我们在使用的时候,最好还是同步一下服务器时间。
我觉得这是比较简单的方式解决不同服务器上部署相同服务(比如双机热备,负载均衡),但是服务中又包含定时器,不管是出于避免并发访问还是其他业务原因,我们最好还是在同一时间点只允许同一任务执行一次。
其实,ShedLock的配置是比较简单的,但是真正理解lockAtMostFor和lockAtLeastFor是比较困难的,而且这俩个配置又是最重要的俩个配置
首先引入依赖,因为我用的是oracle数据库,所以引入shedlock-provider-jdbc-template
net.javacrumbs.shedlock shedlock-spring 4.12.0 net.javacrumbs.shedlock shedlock-provider-jdbc-template 4.12.0
启动类中使用注解@EnableSchedulerLock,但是这个defaultLockAtMostFor参数还必须得有,去掉会报错。因为在每个任务中也要配置lockAtMostFor,只要每个任务中都配置了这个值,启动类中的应该就不会起作用。启动类中的配置只是起到一个保障的作用,又是必须的,那么我们可以给它配置一个比较大的值。
数据库建表(oracle):
name作为主键,标志不同的任务;locked_at是获取锁的时间;lock_until获取到锁后持续的时间,也就是下次释放锁的时间
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));
需要加一个配置类(如果需要更加详细的配置,去github上看项目中的介绍):
@Configuration
public class ShedLockConfig {
@Bean
public LockProvider lockProvider(DataSource dataSource) {
return new JdbcTemplateLockProvider(dataSource);
}
}
具体到每个任务的配置:
@Component
public class ShedLockTestJob {
@Scheduled(fixedRate = 30000)
//@SchedulerLock(name = "shedLockTest", lockAtMostForString = "PT10M", lockAtLeastForString = "PT10S")
@SchedulerLock(name = "shedLockTest", lockAtMostFor = "2m", lockAtLeastFor = "10s")
public void shedLockTest(){
System.out.println("==================" + new Date());
try {
Thread.sleep(8000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("=======================1111111111==========================");
}
}
name:每个任务的name不可以相同,唯一标志;
lockAtMostFor:这个任务获得锁后持续的最长时间(2m表示两分钟);
lockAtLeastFor:这个任务获得锁后持续的最短时间(10s表示10秒);
lockAtMostFor的作用是为了防止某一个服务获得锁了,但是中途服务挂掉了,没法释放锁,导致死锁,另外服务器的该任务也无法定时执行。所以我们需要设置一个时间点,这个时间点最好或者说是必须比你执行该任务花费的时间长。否则,这个任务还没有执行完,锁就释放了,到了定时器的执行时间,又开始执行该任务,可能会导致意想不到的问题。
lockAtLeastFor的作用是为了防止服务器之间有时间差。比如说两台服务器的时间差为5秒,服务器A获得锁执行完任务花了3秒,然后释放了锁,那么到了5秒的时候,服务器B才到了定时执行的时间,它就会又执行一遍该任务。如果这时候我们设置lockAtLeastFor为6秒,那么即使服务器A3秒执行完了任务也不会释放锁,直到6秒之后。
这两个配置总的一个原则就是:
任务开始执行后,LOCKED_AT就是获取到锁的时间,LOCK_UNTIL就是LOCKED_AT+lockAtMostFor所得得时间。意思就是任务执行过程中我就认为你是在最大的时间才会释放锁,因为我不知道你具体任务多会儿执行完,这样是最安全的。如果任务在lockAtLeastFor设置的时间内执行完了,那么LOCK_UNTIL就改为LOCKED_AT+lockAtLeastFor,到了这个时间才会释放锁。如果任务执行的时间大于lockAtLeastFor而小于lockAtMostFor,那么LOCK_UNTIL就会被改为LOCKED_AT+实际完成任务需要的时间,也就是LOCK_UNTIL为完成任务的时间。
简单画个图,也许会好理解一些
其实对于lockAtMostFor这个设置的作用,重点还是要看服务挂掉的时机,如果数据库中的LOCK_UNTIL跟lockAtMostFor没啥关系,也许服务A挂掉了,服务B不用等到lockAtMostFor之后就可以正常执行定时任务了
如果内容对大家有所帮助,感谢鼓励!实现我小时候一人给我一块钱的美梦,哈哈!