文章摘要:本文从作者接触MQ技术的经历谈起,对比了ActiveMQ、RocketMQ和Kafka的异同,重点介绍了Spring Kafka的基础入门配置。文章详细讲解了Maven依赖配置、Spring自动装配数据、KafkaTemplate使用、@MessageListener注解以及自定义Container的实现方法,为Java开发者提供了实用的Spring Kafka配置指南。
闲谈
还记得多年前实现的时候第一次接触的MQ 类型是ActiveMQ,在那个时候的应用场景完成能够支撑得住。
后来换了一家公司,全部标配 Rocketmq,RocketMQ 经过多年阿里内部的实践已经是一款非常优秀的MQ产品了。
Rocketmq和kafka很多概念非常的相似,之前一直没有在产品中使用过kafka,最近接手两个产品都使用了Kafka,了解一下相关的原理。
为什么说Kafka和Rocketmq 很多概念是类似的?
Kafka中,1个Partition只能被1个消费线程消费。
Rocketmq中1个队列只能被一个消费者线程消费。
如果消费线程大于分区(队列)数,则多余的消费线程将空闲;如果消费线程小于分区(队列)数,则有部分消费线程将消费多个分区(队列)的数据;
如果消费线程等于分区(队列)数,则刚刚好一个消费线程对应一个分区,这也是最理想的情况.
恰好看到生产环境Kafka的分区数Only 配置了3个分区,消费者组2个线程,一共部署了十台实例,其实很多集群都是无用的。
基于对Rocketmq 的理解 ,其实针对 Kafka的理解就非常容易。 Kafka初识之架构之美 写得非常不错。
spring kafka 基础入门
配置maven 依赖
仔细阅读官方文档 spring-kafka 2.9.13
bash
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
配置spring 自动装配的数据
propertis
###########【Kafka集群】###########
spring.kafka.bootstrap-servers=
###########【初始化消费者配置】###########
# 默认的消费组ID
#spring.kafka.consumer.properties.group.id=longConnConsumerGroup
# 是否自动提交offset
spring.kafka.consumer.enable-auto-commit=false
spring.kafka.listener.ack-mode=manual_immediate
# 提交offset延时(接收到消息后多久提交offset)
#spring.kafka.consumer.auto.commit.interval.ms=1000
# 当kafka中没有初始offset或offset超出范围时将自动重置offset
# earliest:重置为分区中最小的offset;
# latest:重置为分区中最新的offset(消费分区中新产生的数据);
# none:只要有一个分区不存在已提交的offset,就抛出异常;
spring.kafka.consumer.auto-offset-reset=latest
# 消费会话超时时间(超过这个时间consumer没有发送心跳,就会触发rebalance操作)
spring.kafka.consumer.properties.session.timeout.ms=240000
# 消费请求超时时间
spring.kafka.consumer.properties.request.timeout.ms=240000
spring.kafka.consumer.properties.max.poll.interval.ms=240000
spring.kafka.consumer.properties.heartbeat.interval.ms=80000
# Kafka提供的序列化和反序列化类
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.value-deserializer=org.apache.kafka.common.serialization.StringDeserializer
# 消费端监听的topic不存在时,项目启动会报错(关掉)
spring.kafka.listener.missing-topics-fatal=false
# 设置批量消费
spring.kafka.listener.type=batch
#并发数
spring.kafka.listener.concurrency=2
# 批量消费每次最多消费多少条消息
spring.kafka.consumer.max-poll-records=3
spring.kafka.consumer.client-id=
###########【初始化生产者配置】###########
## 重试次数
spring.kafka.producer.retries=2
## 应答级别:多少个分区副本备份完成时向生产者发送ack确认(可选0、1、all/-1)
#acks = 0 如果设置为零,则生产者将不会等待来自服务器的任何确认,该记录将立即添加到套接字缓冲区并视为已发送。在这种情况下,无法保证服务器已收到记录,并且重试配置将不会生效(因为客户端通常不会知道任何故障),为每条记录返回的偏移量始终设置为-1。
#acks = 1 这意味着leader会将记录写入其本地日志,但无需等待所有副本服务器的完全确认即可做出回应,在这种情况下,如果leader在确认记录后立即失败,但在将数据复制到所有的副本服务器之前,则记录将会丢失。
#acks = all 这意味着leader将等待完整的同步副本集以确认记录,这保证了只要至少一个同步副本服务器仍然存活,记录就不会丢失,这是最强有力的保证,这相当于acks = -1的设置。
spring.kafka.producer.acks=-1
## 批量大小
#spring.kafka.producer.batch-size=16384
## 提交延时
#spring.kafka.producer.properties.linger.ms=0
## 当生产端积累的消息达到batch-size或接收到消息linger.ms后,生产者就会将消息提交给kafka
## linger.ms为0表示每接收到一条消息就提交给kafka,这时候batch-size其实就没用了
#?
## 生产端缓冲区大小
#spring.kafka.producer.buffer-memory = 33554432
## Kafka提供的序列化和反序列化类
#spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
#spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer
## 自定义分区器
## spring.kafka.producer.properties.partitioner.class=com.felix.kafka.producer.CustomizePartitioner
#开启生产幂等
spring.kafka.producer.properties.enable.idempotence=true
spring.kafka.producer.client-id=
kafka-template
java
kafkaTemplate.send(new ProducerRecord<>(topic, message));
@MessageListener
concurrency 消费者数量和分区密切相关避免浪费
java
@KafkaListener(clientIdPrefix = ""
, topics = ""
, concurrency = ""
, groupId = "TEST_GROUP")
public void listen(List<ConsumerRecord> consumerRecords, Acknowledgment acknowledgment) {
for (ConsumerRecord consumerRecord : consumerRecords) {
}
//消息确认
acknowledgment.acknowledge();
}
广播消费...
java
@KafkaListener(clientIdPrefix = ""
, topics = ""
, concurrency = ""
, groupId = "#{T(java.util.UUID).randomUUID().toString()}")
public void listen(List<ConsumerRecord> consumerRecords, Acknowledgment acknowledgment) {
for (ConsumerRecord consumerRecord : consumerRecords) {
}
//消息确认
acknowledgment.acknowledge();
}
需要自定义 Container ?
Dynamically Creating Containers
比如下面这个配置消费者需要广播消费... 其实默认情况下spring 提供了默认的,不需配置全局统一就行了,广播消费这个直接可以在@KafkaListener处理。
java
@Configuration
public class KafkaProducerConfig {
private final ConsumerFactory consumerFactory;
private final KafkaProperties kafkaProperties;
@Bean("containerFactory")
public ConcurrentKafkaListenerContainerFactory<String, String> containerFactory() {
ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory();
factory.setConsumerFactory(consumerFactory);
//手动确认
factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE);
//批量消费
factory.setBatchListener(true);
factory.setConcurrency(kafkaProperties.getListener().getConcurrency());
factory.getContainerProperties().setGroupId(getGroupId());
return factory;
}
private String getGroupId() {
String groupId;
String inetAddress = HostNameUtil.getInetAddress();
if (inetAddress == null) {
groupId = UUID.randomUUID().toString().replace("-", "");
} else {
groupId = inetAddress.replace(".", "");
}
return groupId;
}
}