文章目录
- @Scheduled注解
- 参数介绍
- 建表
- 配置类
- 示例
- 参考
如果服务中使用了@Scheduled注解,且服务部署了多个节点。那么在同一时刻,所有节点都会执行定时任务。但有有些任务我们只需执行一次,这就需要使用分布式锁的方式来控制,如可以使用如基于Redis的Lock4J框架。
@Scheduled注解
本文介绍一个SchedulerLock,SchedulerLock分布式锁可以基于Mysq,Redis、Mongo等中间件,本文介绍基于Mysql的方式,为什么使用Mysql呢,因为Mysql是我们业务中基本上一定会使用的中间件,使用MySQL不使用Redis,可以减少一个中间件,哈哈。
在bulid文件引入依赖,建议引入较新版本
api 'net.javacrumbs.shedlock:shedlock-spring:4.42.0'
api 'net.javacrumbs.shedlock:shedlock-provider-jdbc-template:4.42.0'
较新版本中的@SchedulerLock源码只有3个参数,比老版本的较少。
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SchedulerLock {
/**
* Lock name.
*/
String name() default "";
/**
* How long the lock should be kept in case the machine which obtained the lock died before releasing it.
* This is just a fallback, under normal circumstances the lock is released as soon the tasks finishes.
*
* Can be either time with suffix like 10s or ISO8601 duration as described in {@link java.time.Duration#parse(CharSequence)}, for example PT30S.
*/
String lockAtMostFor() default "";
/**
* The lock will be held at least for given duration. Can be used if you really need to execute the task
* at most once in given period of time. If the duration of the task is shorter than clock difference between nodes, the task can
* be theoretically executed more than once (one node after another). By setting this parameter, you can make sure that the
* lock will be kept at least for given period of time.
*
* Can be either time with suffix like 10s or ISO8601 duration as described in {@link java.time.Duration#parse(CharSequence)}, for example PT30S.
*/
String lockAtLeastFor() default "";
}
参数介绍
name:任务唯一标识。最重要的参数。同一个name的任务,同一个时刻,多个线程只会有一个线程获取到锁。其他没有获取到锁的线程会跳过,不会阻塞等待。
lockAtLeastFor:持有锁的最短时间。这个主要是防止不同节点时间存在误差,比如有个任务是0点执行,节点1的时间是准的,在0点执行花了30秒执行完成。节点2的时间比节点1慢了1分钟,那么节点2到0点的时候,又执行了一次任务。这个参数可以设置10秒、30秒等。保证不同节点的时间戳不会出现。
lockAtMostFor:持有锁的最长时间。主要是为了防止死锁。当一个任务执行完成时会释放锁,当一个任务执行超过lockAtMostFor时间时,也会释放锁。这个时间要大于业务执行的时间,不然一个任务可能会被执行多次。
建表
我这里是使用的MySQL方式,所以需要建表。
CREATE TABLE `shedlock`
(
`name` varchar(64) COLLATE utf8_bin NOT NULL,
`lock_until` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP (3),
`locked_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`locked_by` varchar(255) COLLATE utf8_bin NOT NULL,
PRIMARY KEY (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
主键一定要是name!
配置类
注入MySQL数据源
@Configuration
@EnableSchedulerLock(defaultLockAtMostFor = "PT180S")
public class ShedlockConfig {
@Autowired
private DataSource dataSource;
@Bean
public LockProvider lockProvider() {
return new JdbcTemplateLockProvider(
JdbcTemplateLockProvider.Configuration.builder()
.withJdbcTemplate(new JdbcTemplate(dataSource))
.build()
);
}
}
@EnableSchedulerLock注解不要忘记开启~
示例
@Scheduled(cron = "0 0/1 * * * ?")
@SchedulerLock(name = "myTask1", lockAtLeastFor = "PT30S", lockAtMostFor = "PT20M")
public void myTask1() {
log.info("mytask start. thread:{} time:{}", Thread.currentThread().getName(), new SimpleDateFormat("yyyy-MM-dd HH::mm:ss").format(new Date()));
long start = System.currentTimeMillis();
try {
myService.process();
} catch (Exception e) {
log.error("mytask error.", e);
}
log.info("pmytask. use time:{} s", (System.currentTimeMillis() - start) / 1000);
}
执行完后MySQL表会自动生成几天记录,name就是任务名称~
参考
官方资料:https://github.com/lukas-krecan/ShedLock