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