在Spring Schedule進行定時關單任務時候呢,由於項目涉及到分布式,導致遇到多線程上的問題,
最后在Spring Schedule定時關單快速入門(三)的最后也得到了解決。但是解決的方法相對原生。
至此,我們引入Redisson框架進行優化出v4版本的定時器。
1.首先我們要在項目中的pom.xml文件中引入Redisson框架。
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>2.9.0</version>
</dependency>
2.在項目中的pom.xml文件中引入Redisson框架的依賴文件。
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-avro</artifactId>
<version>2.9.0</version>
</dependency>
3.將Redisson進行初始化
A:首先創建Redisson類,在類上添加上Spring的注解(@Component)使該類被Spring管理。
B:創建兩個主要類成員(config)(redisson)和對應Redis的類成員(RedisIP)(RedisPort)
config為配置文件,負責加載上Redis的類成員(RedisIP)(RedisPort)
redisson為主要成員,負責加載上配置文件(config)
C:添加初始化方法,為方法添加上@PostConstruct注解使該方法在spring加載此類組件(Component)的時候調用此方法。
D:為主要成員(redisson)提供外界訪問方式(getter方法)
Redisson初始化代碼如下:
package com.mmall.common;
import com.mmall.util.PropertiesUtil;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.config.Config;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component //組件
@Slf4j
public class RedissonManager {
private Config config = new Config();
private Redisson redisson = null;
public Redisson getRedisson() { //提供外界訪問使用
return redisson;
}
private static String redis1Ip = PropertiesUtil.getProperty("redis1.ip");//RedisIP
private static Integer redis1Port = Integer.parseInt(PropertiesUtil.getProperty("redis1.port"));//RedisPort
private static String redis2Ip = PropertiesUtil.getProperty("redis2.ip");
private static Integer redis2Port = Integer.parseInt(PropertiesUtil.getProperty("redis2.port"));
@PostConstruct //在spring加載此類組件(Component)的時候調用此方法。
private void init(){//初始化方法
//setAddress 需要跟進源碼,源碼里頭有該方法的使用介紹,里頭詳細的介紹了參數該如何傳遞
try {
config.useSingleServer().setAddress(new StringBuilder().append(redis1Ip).append(":").append(redis1Port).toString());
redisson = (Redisson) Redisson.create(config);
log.info("初始化Redisson結束");
} catch (Exception e) {
log.error("redisson init error",e);
e.printStackTrace();
}
}
//如果不添加下面這個方法,那項目在關閉Tomcat的時候會有線程未關閉。導致Tomcat無法順利關閉。(重點)
@PreDestroy //被@PreConstruct修飾的方法會在服務器卸載Servlet的時候運行,並且只會被服務器調用一次,類似於Servlet的destroy()方法。被@PreConstruct修飾的方法會在destroy()方法之后運行,在Servlet被徹底卸載之前。
private void close(){
redisson.shutdown();
log.info("Redisson關閉");
}
}
定時關單器V4版本(代碼)
@Scheduled(cron = "0 */1 * * * ?")//每1分鍾(每個1分鍾的整數倍)調用一次
public void closeOrderTaskV4(){
RLock lock = redissonManager.getRedisson().getLock(Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK);//獲取到鎖 參數傳遞鎖的名字
boolean getLock = false;
try { //在參數1中的等待時間中,我們應該預估定時器代碼的執行而定,
//如果項目中定的等待時間是2秒,但是定時器的代碼在1秒的時候執行完后釋放了鎖
//此時另一個Tomcat的定時器發現在等待2秒的期間發現鎖釋放了,隨即進入了鎖定時器。
//所以在我們不知道定時器代碼執行的大概時間時,我們盡可能將等待時間設置為0
if(getLock = lock.tryLock(0,5, TimeUnit.SECONDS)){//參數1:等待時間2 參數2:工作時間5 參數3:單位秒
log.info("Redisson獲取到分布式鎖:{},ThreadName:{}",Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK,Thread.currentThread().getName());
int hour = Integer.parseInt(PropertiesUtil.getProperty("close.order.task.time.hour","2"));
//iOrderService.closeOrder(hour);
}else{
log.info("Redisson沒有獲取到分布式鎖:{},ThreadName:{}",Const.REDIS_LOCK.CLOSE_ORDER_TASK_LOCK,Thread.currentThread().getName());
}
} catch (InterruptedException e) {
log.error("Redisson分布式鎖獲取異常",e);
}finally {
if(!getLock){
return;
}
lock.unlock();//釋放鎖
log.info("Redisson分布式鎖釋放鎖");
}
}