SpringCloud(SpringBoot)分布式定时器的简单解决⽅案
(redis锁)
定时任务的实现⽅式有多种,例如JDK⾃带的Timer+TimerTask⽅式,Spring 3.0以后的调度任务(Scheduled Task),Quartz等。
因为项⽬中⽤到了Scheduled,所以这⾥只说Scheduled。
1. SpringBoot启动类上加注解
@EnableScheduling
2. ⾃定义线程池。
spring底层默认是new⼀个核⼼数量为1的单线程池,如果需要对定时器线程池核⼼线程数量调优或⾃定义什么的,可以新增⼀个配置类,实现SchedulingConfigurer接⼝,重写configureTasks⽅法,通过taskRegistrar设置⾃定义线程池。
@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
}
@Bean(destroyMethod="shutdown")
public Executor taskExecutor() {
wScheduledThreadPool(20);
}
}
3. ⽤法:实现⼀个基本的调度⽅法。基本结构如下:
service;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@Service
public class ScheduledService {
springboot结构@Scheduled(cron = "0 0 5 * * *")
public void build() {
System.out.println("Scheduled Task");
}
}
@Scheduled注解⽀持秒级的cron表达式,上述声明表⽰每天5点执⾏build任务,当然本篇重点不是介绍cron,想知道更多的cron表达式,可以看我的另⼀篇博⽂ ,⾥⾯有详细语法介绍。
好,回到正题,前⽂已经提过,这种⽅式在单台应⽤服务器上运⾏没有问题,但是在集环境下,会造成build任务在5点的时候运⾏多次,遗憾的是,Scheduled Task在框架层⾯没有相应的解决⽅案,只能靠程序员在应⽤级别进⾏控制。
如何控制?
1. ⽆⾮是⼀个任务互斥访问的问题,声明⼀把全局的“锁”作为互斥量,哪个应⽤服务器拿到这把“锁”,就有执⾏任务的权利,未拿
到“锁”的应⽤服务器不进⾏任何任务相关的操作。
2.这把“锁”最好还能在下次任务执⾏时间点前失效。
在项⽬中我将这个互斥量放在了redis缓存⾥,9分钟过期,这个过期时间是由任务调度的间隔时间决定的,只要⼩于两次任务执⾏时间差,⼤于集间应⽤服务器的时间差即可。
完整定时任务类如下,该段代码⽀持多台机⼦部署,不会出现多台服务都同时执⾏的情况,当然前提是他们⽤的redis都是同⼀个:
@Component
public class AutoInsertVuserToGroupBuying {
@Autowired
private RedisTemplate redisTemplate;
@Scheduled(cron = "0 */10 * * * ?") //定时器10分钟⼀次
public void shTask() {
//先判断redis中是否有锁记录,如果能设值成功,代表拿到锁,不能设值成功就是锁还没释放
if(redisTemplate.opsForValue().setIfAbsent(key, value)){
//设值成功后,设置锁超时时间 (我这⾥是9分钟)
//业务
dojob();
}
}
}
PS: 很多⼈说9到11⾏之间会存在并发问题,其实并不会,因为setIfAbsent先天⾃带锁,是基于redis的setnx来保证原⼦性,像这⾥是不可能存在两个线程同时设值成功的, 只有设值成功的才能拿到锁,没设值成功的就拿不到锁.
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论