序
上次分享了springboot的優雅集成,最近在跟同事討論的時候,發現有更簡單的集成方式,真的是極簡。直接上代碼吧,今天週日,昨天加班到9點,閨女要我帶她玩。
一、maven引包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
二、Kafka配置類
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import javax.annotation.PostConstruct;
/**
* Kafka配置
* springboot的自動配置以及對kafka的基本屬性都支持了,這裏只擴展配置
* @author zhengwen
**/
@Configuration
public class KafkaConfig {
@Value("${spring.kafka.consumer.properties.batch-listener:false}")
private boolean batchListener;
@Autowired
private ConcurrentKafkaListenerContainerFactory concurrentKafkaListenerContainerFactory;
@PostConstruct
public void postConstruct() {
//開啓批量監聽
concurrentKafkaListenerContainerFactory.setBatchListener(batchListener);
}
}
如果是簡單使用,其實這個配置類都可以省略。這裏增加這個配置類,是爲了開啓批量監聽,現在springBoot還沒有對這個配置進行自動配置。後面應該有可能支持的。
配置這個還有個好處就是可以批量取kafka裏的數據,然後配合多線程,提升性能。
三、多線程配置類
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.lang.reflect.Method;
import java.util.concurrent.Executor;
/**
* @author zhengwen
*/
@Configuration
@EnableAsync
@Slf4j
public class ThreadConfig implements AsyncConfigurer {
/**
* 核心線程樹
*/
@Value("${thread.config.corePoolSize:5}")
private Integer corePoolSize;
/**
* 最大線程池數量
*/
@Value("${thread.config.maxPoolSize:10}")
private Integer maxPoolSize;
/**
* 隊列長度
*/
@Value("${thread.config.queueCapacity:10}")
private Integer queueCapacity;
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
log.info("---------[多線程配置初始化],最大核心線程數:{},最大線程池數量:{},線程處理隊列長度:{}", corePoolSize, maxPoolSize, queueCapacity);
//核心線程數
executor.setCorePoolSize(corePoolSize);
//最大線程池數量
executor.setMaxPoolSize(maxPoolSize);
//線程處理隊列長度
executor.setQueueCapacity(queueCapacity);
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
log.info("-----------多線程異常handler------------");
return new SpringAsyncExceptionHandler();
}
class SpringAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
log.error("Asyn返回異常:" + ex.getCause().getMessage() + method.getName());
}
}
}
這個配置還可以優化對異常的處理,特別注意多線程方法需要增加註解 @Async,且不能與業務方法在一個service裏,否則無效。
四、kafka監聽
@Slf4j
@Component
public class AnalyzeListenConsumer {
@Autowired
private AnalyzeService analyzeService;
/**
* 人臉listenner
*
* @param records 消費信息
* @param ack Ack機制
*/
@KafkaListener(topics = "${fillersmart.analyze.face.dahua.topic.consumer}", containerFactory = "kafkaListenerContainerFactory")
public void dahuaFaceListen(List<ConsumerRecord> records, Acknowledgment ack) {
log.info("=====大華人臉faceListen消費者接收信息====");
try {
for (ConsumerRecord record : records) {
log.info("---開啓線程解析大華人臉數據:{}",record.toString());
analyzeService.dahuaFaceAnalyze(record);
}
} catch (Exception e) {
e.printStackTrace();
log.error("----大華人臉消費者解析數據異常:{}",e.getMessage(),e);
} finally {
//手動提交偏移量
ack.acknowledge();
}
}
}
AnalyzeService 就是一個普通的service。這裏要特別說明的是,如果不開啓批量監聽,這裏的List records入參將會是一個字符串集合(與kafak的序列化和反序列化無關,最開始我也是懷疑,各種配置+自定義序列與反序列的方法,都沒有用),執行到for就會報String不能轉換爲List 的異常。
解決方式:
1、使用ConsumerRecord作爲入參
這樣就不能配合多線程了,因爲信息就是一條條的來的,analyzeService開啓多線程意義不大
2、開啓批量監聽,這就是爲啥要前面的kafka配置類的原因
List records入參,然後循環處理,開啓線程,纔有意義。
五、生產者使用
String msg = JSON.toJSONString(collectDataDto);
ListenableFuture listenableFuture = kafkaTemplate.send(dahuaFaceProducerTopic,msg);
//發送成功後回調
SuccessCallback<String> successCallback = new SuccessCallback() {
@Override
public void onSuccess(Object result) {
log.info("----大華人臉抓拍kafka記錄解析完成放入topic:{},發送成功:{}",dahuaFaceProducerTopic, msg);
}
};
//發送失敗回調
FailureCallback failureCallback = new FailureCallback() {
@Override
public void onFailure(Throwable ex) {
log.error("----大華人臉抓拍kafka記錄解析完成放入topic:{},發送失敗{}",dahuaFaceProducerTopic,msg,ex);
}
};
listenableFuture.addCallback(successCallback,failureCallback);
將要發送的對象轉爲json字符串,然後調用send方法,如果還有配置partition、key的,這就是send參數不一樣。下面設置成功、失敗回調是一樣的。
六、分享配置文件
spring:
kafka:
bootstrap-servers: 127.0.0.1:9092
consumer:
group-id: test-consumer-group
max-poll-records: 10
concurrency: 10
#Kafka中沒有初始偏移或如果當前偏移在服務器上不再存在時,默認區最新 ,有三個選項 【latest, earliest, none】
auto-offset-reset: earliest
#是否開啓自動提交
enable-auto-commit: false
ack-mode: MANUAL_IMMEDIATE
#自動提交的時間間隔
auto-commit-interval: 1000
#key的解碼方式
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
#value的解碼方式
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
properties:
batch-listener: true
producer:
batch-size: 4096
buffer-memory: 40960
retries: 1
value-serializer: org.apache.kafka.common.serialization.StringSerializer
key-serializer: org.apache.kafka.common.serialization.StringSerializer
listener:
#創建多少個consumer,值必須小於等於Kafk Topic的分區數。
ack-mode: MANUAL_IMMEDIATE
concurrency: 1 #推薦設置爲topic的分區數
properties:
batch-listener: true就是補充擴展的參數,用於開啓批量監聽。
這應該是最簡單的接入了吧,但是怎麼說呢。使用springboot的自動配置,對配置的理解要非常高,搞不好2個配置會導致直接報錯。比如enable-auto-commit的開啓。
就到這裏吧,希望能幫到大家。