前言
我們在開發過程中經常會用到計劃任務,而計劃任務中我們比較常用的是 @Scheduled() ,但是,當我們同時使用了計劃任務和kafka監聽消費後,我們的計劃任務就無法成功生效了,因爲我們不管是使用多少線程,kafka監聽消費會一直佔用,這樣就會導致我們使用註解方式的計劃任務無法生效,如果這個時候我們仍然需要使用計劃任務,可以使用下面的方式去替換計劃任務。
計劃任務
週期執行
//設置一個當前計劃任務使用一個線程
ScheduledExecutorService rollService = Executors.newScheduledThreadPool(
1,
new ThreadFactoryBuilder().setNameFormat(
"自定義線程名" +
Thread.currentThread().getId() + "-%d").build());
rollService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
LOGGER.info("Marking time to execute task");
}
}, 60, 600, TimeUnit.SECONDS);
上面代碼表示延遲60秒執行run(){}
裏面的方法,第一次執行後每隔600秒重複執行一次。
但是我們知道,我們使用計劃任務通常是整點或者是在某個指定時間開始第一次執行任務,上面代碼可以看出是不具備這個功能的,但是我們只需要修改很少的一部分代碼就可以實現我們真正想要的週期執行的計劃任務。
那就是設置延遲時間,也就是代碼中的那個60
。
延遲時間
/**
* 當前時間與週期時間的時間差
* 時間單位 秒
* @param executorTime 執行週期
* @return 週期執行的等待時間
*/
public static int executorsDelayTime(int executorTime){
//獲取當前時間與週期時間的差
long nowTime = System.currentTimeMillis();
int timeDiscrepancy = Integer.parseInt(String.valueOf(nowTime%(executorTime*1000)/1000));
int delayTime = executorTime-timeDiscrepancy;
return delayTime;
}
上面的代碼可以計算出服務啓動時間與設置的週期時間的一個時間差,例如當前時間是 09:28:41
,執行週期是 300
,也就是5分鐘整點執行一次,那麼我們期望的下次執行時間應該是 09:30:00
,通過上面的方法就會計算出首次執行任務的等待時間是 79
。
總結
所以,綜上所述,如果只想要週期執行,用第一個週期方法即可,如果想要模擬註解方式的計劃任務,只需要搭配上下面的獲取延遲時間的方法即可。
需要注意的是兩個方法的時間單位都是秒,週期方法裏面的時間單位如果修改了,獲取延遲時間的方法也需要進行修改。建議根據實際需求去進行改造。(PS:畢竟如果是按照小時計算等待時間和週期時間的,用秒單位就太大了)
@KafkaListener() 兩種方式加載多個topic
# application.properties
spring.kafkaListenerList = topic1,topic2
第一種
@KafkaListener(topics = "#{'${spring.kafkaListenerList}'.split(',')}")
第二種
KafkaListenerReceiver
是自定義的消費對象
- KafkaListenerConfig.java
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
@Configuration
@Slf4j
public class KafkaListenerConfig implements BeanDefinitionRegistryPostProcessor, PriorityOrdered {
private List<String> listenerList;
KafkaListenerConfig() throws IOException {
Properties pro = new Properties();
File f;
f =new File( java.net.URLDecoder.decode(this.getClass().getResource("/application.properties").getPath(),"utf-8"));
FileInputStream in = new FileInputStream(f);
pro.load(in);
in.close();
String listString = pro.getProperty("spring.verify.kafkaListenerList");
if (StringUtils.isEmpty(listString)){
log.error("spring.verify.kafkaListenerList is empty or null");
System.exit(0);
}
log.info("spring.kafkaListenerList: "+listString);
listenerList = Arrays.asList(listString.split(","));
}
public List<String> getListenerList() {
return listenerList;
}
public void setListenerList(List<String> listenerList) {
this.listenerList = listenerList;
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
for (String listener : listenerList) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(KafkaListenerReceiver.class);
builder.addConstructorArgValue(listener);
beanDefinitionRegistry.registerBeanDefinition(listener, builder.getBeanDefinition());
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
- KafkaListenerReceiver.java
@KafkaListener(topics = "#{__listener.topic}")