定时任务
在开发中相信大家或多或少都会用过 @Scheduled处理定时任务,在以前都是单体服务,没有多实例的概念,但是现在随着发展越来越多的项目应用上了微服务分布式系统,分布式引发的一系列问题也随之而来,像前面说的CAP理论、分布式事务,以及这里说的分布式定时任务等等问题。
定时任务如果不处理的话,N个实例会导致定时任务N次触发,后果不亚于银行转账不用强一致性导致的灾难。
解决方案
只要涉及到分布式,基本上就是使用分布式锁解决,定时任务也不列外,一般有以下几种:
一:幂等
定时任务相对简单,比如定时清理不活跃的用户,只要保证此任务是幂等,那么执行多少次都没关系。缺点是多实例都会执行,浪费资源,虽然简洁但不推荐。
二:开源分布式定时任务
ShedLock只做一件事。它确保您的计划任务最多同时执行一次。如果正在一个节点上执行任务,它将获取一个锁,以防止从另一个节点(或线程)执行相同的任务。请注意,如果一个任务已在一个节点上执行,则其他节点上的执行不会等待,只会跳过它。
Use: 添加依赖
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
<!-- 确保spring-boot版本大于2.1.0 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-redis-jedis</artifactId>
<version>4.13.0</version>
</dependency>
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId>
<version>4.13.0</version>
</dependency>
</dependencies>
- 添加配置
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
"10m")
public class demoConfig {
public JedisPool jedisPool() {
JedisPoolConfig config = new JedisPoolConfig();
return new JedisPool(config, "IP", 6379,10000, "密码");
}
public LockProvider lockProvider(JedisPool jedisPool) {
return new JedisLockProvider(jedisPool);
}
}
(defaultLockAtMostFor = - 测试多实例相同任务是否加锁
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
public class ScheduledTest {
(cron = "0/5 * * * * ?")
(name = "scheduledTaskName", lockAtMostFor = "5s",lockAtLeastFor = "1s")
public void getInfoa() {
System.out.println(System.currentTimeMillis());
System.out.println(new Date());
System.out.println("Lock one");
}
(cron = "0/5 * * * * ?")
(name = "scheduledTaskName", lockAtMostFor = "5s",lockAtLeastFor = "1s")
public void getInfoaa() {
System.out.println(System.currentTimeMillis());
System.out.println(new Date());
System.out.println("Lock two");
}
}
三:在代码中使用redis分布式锁
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
"0 0 8 * * ? ")
public void sendMessage() {
RLock rLock = null;
try {
rLock = this.redisson.getLock("gene_sms_state");
if (!rLock.tryLock(0L, 300, TimeUnit.SECONDS)) {
return;
}
// do something
} catch (Exception e) {
this.log.warn("", e.getMessage());
} finally {
if (rLock != null) {
rLock.forceUnlock();
}
}
}
(cron = 但是每个定时任务都需要写这一套,非常繁琐。或者即使想使用上面提到的ShedLock但是引入新依赖需要考虑团队因素,最低版本限制等等, 那么这里就可以使用切面注解进行优化,注解相对比较简单,这里就不阐述了。可以参考
优化
- 1
像这种每秒都执行的或者几分钟执行不建议使用以上的分布式定时任务,这种情况应该根据业务场景去写脚本走批量或者单独使用任务调度服务去解决。