Spring Cloud(Spring Boot)分布式定时器的简单解决方案(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() {
        return Executors.newScheduledThreadPool(20);
    }
}

3. 用法:实现一个基本的调度方法。基本结构如下:

package com.netease.yx.service;
 
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
 
@Service
public class ScheduledService {
    @Scheduled(cron = "0 0 5 * * *")
    public void build() {
       System.out.println("Scheduled Task");
    }
}

@Scheduled注解支持秒级的cron表达式,上述声明表示每天5点执行build任务,当然本篇重点不是介绍cron,想知道更多的cron表达式,可以看我的另一篇博文 @Scheduled注解 详解,里面有详细语法介绍。

好,回到正题,前文已经提过,这种方式在单台应用服务器上运行没有问题,但是在集群环境下,会造成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分钟)
            redisTemplate.expire(key, 9, TimeUnit.MINUTES);
            //业务
            dojob();
        }
    }
}

PS: 很多人说9到11行之间会存在并发问题,其实并不会,因为setIfAbsent先天自带锁,是基于redis的setnx来保证原子性,像这里是不可能存在两个线程同时设值成功的, 只有设值成功的才能拿到锁,没设值成功的就拿不到锁.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章