以下是 Kafka 的生产者工具类、消费者监听及测试代码示例,封装了多种发送方式,并包含详细注释:
1. 依赖配置 (pom.xml)
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Kafka -->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<!-- Lombok(可选,简化代码) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2. 配置文件 (application.yml)
spring:
kafka:
bootstrap-servers: localhost:909
producer:
retry:
enabled: false # 关闭自动重试
#当有多个消息需要被发送到同一个分区时,生产者会把它们放在同一个批次里。该参数指定了一个批次可以使用的内存大小,按照字节数计算。
batch-size: 16384
# 设置生产者内存缓冲区的大小。
buffer-memory: 33554432
# 键的序列化方式
key-serializer: org.apache.kafka.common.serialization.StringSerializer
# 值的序列化方式
value-serializer: org.apache.kafka.common.serialization.StringSerializer
# acks=0 : 生产者在成功写入消息之前不会等待任何来自服务器的响应。
# acks=1 : 只要集群的首领节点收到消息,生产者就会收到一个来自服务器成功响应。
# acks=all :只有当所有参与复制的节点全部收到消息时,生产者才会收到一个来自服务器的成功响应。
acks: 1
consumer:
# 自动提交的时间间隔 在spring boot 2.X 版本中这里采用的是值的类型为Duration 需要符合特定的格式,如1S,1M,2H,5D
auto-commit-interval: 1S
# 该属性指定了消费者在读取一个没有偏移量的分区或者偏移量无效的情况下该作何处理:
# latest(默认值)在偏移量无效的情况下,消费者将从最新的记录开始读取数据(在消费者启动之后生成的记录)
# earliest :在偏移量无效的情况下,消费者将从起始位置读取分区的记录
auto-offset-reset: earliest
# 是否自动提交偏移量,默认值是true,为了避免出现重复数据和数据丢失,可以把它设置为false,然后手动提交偏移量
enable-auto-commit: false
# 键的反序列化方式
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
# 值的反序列化方式
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
listener:
# 在侦听器容器中运行的线程数。
concurrency: 5
#listner负责ack,每调用一次,就立即commit
ack-mode: manual_immediate
missing-topics-fatal: false
retry:
enabled: false # 关闭重试机制
3. 生产者工具类 (KafkaProducerUtil)
import lombok.extern.slf4j.Slf4j;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.support.SendResult;
import org.springframework.stereotype.Component;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;
@Slf4j
@Component
public class KafkaProducerUtil {
private final KafkaTemplate<String, String> kafkaTemplate;
public KafkaProducerUtil(KafkaTemplate<String, String> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}
/**
* 同步发送消息(阻塞等待结果)
*/
public SendResult<String, String> sendSync(String topic, String message) {
try {
return kafkaTemplate.send(topic, message).get();
} catch (Exception e) {
log.error("同步发送消息失败 | Topic: {} | Message: {}", topic, message, e);
throw new RuntimeException(e);
}
}
/**
* 异步发送消息(默认无回调)
*/
public void sendAsync(String topic, String message) {
kafkaTemplate.send(topic, message);
log.info("异步消息已提交 | Topic: {} | Message: {}", topic, message);
}
/**
* 异步发送消息(带回调)
*/
public void sendAsyncWithCallback(String topic, String message) {
ListenableFuture<SendResult<String, String>> future = kafkaTemplate.send(topic, message);
future.addCallback(new ListenableFutureCallback<>() {
@Override
public void onSuccess(SendResult<String, String> result) {
log.info("异步发送成功 | Topic: {} | Partition: {} | Offset: {}",
topic, result.getRecordMetadata().partition(), result.getRecordMetadata().offset());
}
@Override
public void onFailure(Throwable ex) {
log.error("异步发送失败 | Topic: {} | Message: {}", topic, message, ex);
}
});
}
/**
* 发送到指定分区(需提前知道分区数量)
*/
public void sendToPartition(String topic, Integer partition, String key, String message) {
kafkaTemplate.send(topic, partition, key, message);
log.info("发送到指定分区 | Topic: {} | Partition: {} | Key: {} | Message: {}", topic, partition, key, message);
}
/**
* 发送带 Key 的消息(相同 Key 进入同一分区,保证顺序性)
*/
public void sendWithKey(String topic, String key, String message) {
kafkaTemplate.send(topic, key, message);
log.info("发送带 Key 的消息 | Topic: {} | Key: {} | Message: {}", topic, key, message);
}
}
4. 消费者监听类
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.support.Acknowledgment;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class KafkaConsumer {
// 监听普通消息(自动提交)
@KafkaListener(topics = "SYNC_TOPIC", groupId = "sync-group")
public void listenSyncMessage(ConsumerRecord<String, String> record) {
log.info("[普通消息] 接收 | Topic: {} | Partition: {} | Offset: {} | Key: {} | Value: {}",
record.topic(), record.partition(), record.offset(), record.key(), record.value());
}
// 监听带 Key 的消息(手动提交)
@KafkaListener(topics = "KEY_TOPIC", groupId = "key-group")
public void listenKeyMessage(ConsumerRecord<String, String> record, Acknowledgment ack) {
try {
log.info("[带 Key 消息] 接收 | Key: {} | Value: {}", record.key(), record.value());
// 业务处理...
ack.acknowledge(); // 手动提交偏移量
} catch (Exception e) {
log.error("消费失败 | Topic: {} | Message: {}", record.topic(), record.value(), e);
}
}
// 监听指定分区的消息(分区 0)
@KafkaListener(topicPartitions = @org.springframework.kafka.annotation.TopicPartition(
topic = "PARTITION_TOPIC",
partitions = "0"
), groupId = "partition-group")
public void listenPartitionMessage(ConsumerRecord<String, String> record) {
log.info("[指定分区消息] 接收 | Partition: {} | Value: {}", record.partition(), record.value());
}
}
5. 测试类
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class KafkaProducerTest {
@Autowired
private KafkaProducerUtil kafkaProducer;
// 同步发送测试
@Test
public void testSyncSend() {
kafkaProducer.sendSync("SYNC_TOPIC", "同步测试消息");
}
// 异步发送测试
@Test
public void testAsyncSend() {
kafkaProducer.sendAsync("ASYNC_TOPIC", "异步测试消息");
}
// 带回调的异步发送测试
@Test
public void testAsyncWithCallback() {
kafkaProducer.sendAsyncWithCallback("CALLBACK_TOPIC", "带回调的异步消息");
try { Thread.sleep(3000); } catch (InterruptedException e) {} // 等待回调
}