springBoot極簡接入kafka(自動配置)


上次分享了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的開啓。
就到這裏吧,希望能幫到大家。

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