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來保證原子性,像這裏是不可能存在兩個線程同時設值成功的, 只有設值成功的才能拿到鎖,沒設值成功的就拿不到鎖.

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