Spring Cloud(Spring Boot)分布式定时器的简单解决方案

定时任务的实现方式有多种,例如JDK自带的Timer+TimerTask方式,Spring 3.0以后的调度任务(Scheduled Task),Quartz等。

因为项目中用到了Scheduled,所以这里只说Scheduled。

1. Spring配置文件application-context.xml中添加task命名空间和描述。


<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:task="http://www.springframework.org/schema/task"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans.xsd
	http://www.springframework.org/schema/task
	http://www.springframework.org/schema/task/spring-task.xsd">

2. 添加调度器和线程池声明。

<task:executor id="taskExecutor" pool-size="10" />
<task:annotation-driven executor="taskExecutor" />

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分钟过期,这个过期时间是由任务调度的间隔时间决定的,只要小于两次任务执行时间差,大于集群间应用服务器的时间差即可。

完整定时任务类如下:

@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();
        }
    }
}

 

展开阅读全文

没有更多推荐了,返回首页