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的开启。
就到这里吧,希望能帮到大家。

你可能感兴趣的:(框架搭建)