SpringBoot整合Kafka生產(chǎn)環(huán)境標(biāo)準(zhǔn)配置
一、環(huán)境準(zhǔn)備
| 組件 | 版本 | 說(shuō)明 |
|---|---|---|
| Java | 17+ | Spring Boot 3.x 需 Java 17+ |
| Apache Kafka | 3.6.x | 下載地址:Kafka 官網(wǎng) |
| Spring Boot | 3.1.0+ | 通過(guò) Spring Initializr 生成項(xiàng)目 |
| Maven/Gradle | 最新 | 項(xiàng)目構(gòu)建工具 |
? 啟動(dòng) Kafka 服務(wù)(本地開(kāi)發(fā)示例):
# 啟動(dòng) Zookeeper(Kafka 3.0+ 已內(nèi)置) bin/zookeeper-server-start.sh config/zookeeper.properties # 啟動(dòng) Kafka 服務(wù) bin/kafka-server-start.sh config/server.properties
二、項(xiàng)目搭建(Maven 依賴)
1. 添加 Spring Kafka 依賴
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<version>3.1.0</version>
</dependency>?? 關(guān)鍵點(diǎn):Spring Boot 3.x 已移除 spring-boot-starter-kafka,直接使用 spring-kafka。
三、YAML 配置(application.yml)
? 重點(diǎn):YAML 配置替代 properties,支持對(duì)象序列化器配置
spring:
kafka:
bootstrap-servers: localhost:9092
producer:
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: com.example.config.AttendanceSerializer # 自定義序列化器
batch-size: 16384 # 批量發(fā)送優(yōu)化
linger-ms: 100 # 批量發(fā)送延遲
retries: 3
acks: all
consumer:
group-id: attendance-group
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: com.example.config.AttendanceDeserializer # 自定義反序列化器
auto-offset-reset: earliest
enable-auto-commit: true
max-poll-records: 500
isolation.level: read_committed
# 事務(wù)配置(可選)
transaction-id-prefix: attendance-trans?? YAML 配置優(yōu)勢(shì):
- 層級(jí)清晰,避免
.重復(fù) - 支持嵌套配置(如
producer.batch-size) - 與 Spring Boot 3.x 完美兼容
四、自定義序列化器(核心代碼)
1. 定義消息實(shí)體(AttendanceStatisticsSingleDto)
package jnpf.model.attendance.event;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Date;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AttendanceStatisticsSingleDto implements Serializable {
private static final long serialVersionUID = 1L;
@NotBlank(message = "租戶Id不能為空")
private String tenantId;
@NotBlank(message = "考勤組Id不能為空")
private String groupId;
@NotBlank(message = "用戶Id不能為空")
private String userId;
@NotNull(message = "日期不能為空")
private Date day;
}2. 創(chuàng)建序列化器(AttendanceSerializer.java)
package com.example.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import jnpf.model.attendance.event.AttendanceStatisticsSingleDto;
import org.apache.kafka.common.serialization.Serializer;
import java.util.Map;
public class AttendanceSerializer implements Serializer<AttendanceStatisticsSingleDto> {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public void configure(Map<String, ?> configs, boolean isKey) {
// 配置方法,無(wú)需額外操作
}
@Override
public byte[] serialize(String topic, AttendanceStatisticsSingleDto data) {
try {
return objectMapper.writeValueAsBytes(data);
} catch (Exception e) {
throw new RuntimeException("序列化失敗: " + data, e);
}
}
@Override
public void close() {
// 無(wú)需關(guān)閉資源
}
}3. 創(chuàng)建反序列化器(AttendanceDeserializer.java)
package com.example.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import jnpf.model.attendance.event.AttendanceStatisticsSingleDto;
import org.apache.kafka.common.serialization.Deserializer;
import java.util.Map;
public class AttendanceDeserializer implements Deserializer<AttendanceStatisticsSingleDto> {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public void configure(Map<String, ?> configs, boolean isKey) {
// 配置方法,無(wú)需額外操作
}
@Override
public AttendanceStatisticsSingleDto deserialize(String topic, byte[] data) {
try {
return objectMapper.readValue(data, AttendanceStatisticsSingleDto.class);
} catch (Exception e) {
throw new RuntimeException("反序列化失敗: " + topic, e);
}
}
@Override
public void close() {
// 無(wú)需關(guān)閉資源
}
}?? 為什么需要自定義序列化器?
Spring Kafka 默認(rèn)只支持 String/byte[],復(fù)雜對(duì)象必須通過(guò)序列化器轉(zhuǎn)換為字節(jié)數(shù)組。
五、完整集成示例
1. 生產(chǎn)者服務(wù)(發(fā)送 AttendanceStatisticsSingleDto)
package com.example.service;
import com.example.config.AttendanceSerializer;
import jnpf.model.attendance.event.AttendanceStatisticsSingleDto;
import org.apache.kafka.common.serialization.StringSerializer;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
@Service
public class AttendanceProducer {
private final KafkaTemplate<String, AttendanceStatisticsSingleDto> kafkaTemplate;
public AttendanceProducer(KafkaTemplate<String, AttendanceStatisticsSingleDto> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}
public void sendAttendanceData(AttendanceStatisticsSingleDto attendance) {
// 發(fā)送消息到主題 attendance-topic
kafkaTemplate.send("attendance-topic", attendance.getUserId(), attendance);
System.out.println("? 消息發(fā)送成功: " + attendance.getUserId() + " | " + attendance.getDay());
}
}2. 消費(fèi)者服務(wù)(接收 AttendanceStatisticsSingleDto)
package com.example.consumer;
import com.example.config.AttendanceDeserializer;
import jnpf.model.attendance.event.AttendanceStatisticsSingleDto;
import org.apache.kafka.common.header.Headers;
import org.apache.kafka.common.header.internals.RecordHeaders;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.support.Acknowledgment;
import org.springframework.stereotype.Service;
@Service
public class AttendanceConsumer {
@KafkaListener(
topics = "attendance-topic",
groupId = "attendance-group",
containerFactory = "kafkaListenerContainerFactory"
)
public void listen(AttendanceStatisticsSingleDto attendance,
Acknowledgment ack,
Headers headers) {
// 獲取 Kafka 元數(shù)據(jù)
int partition = Integer.parseInt(headers.lastHeader("partition").value().toString());
long offset = headers.lastHeader("offset").value().toString().getBytes()[0];
System.out.println("? 消息消費(fèi)成功 | 分區(qū): " + partition +
" | 偏移量: " + offset +
" | 用戶: " + attendance.getUserId());
// 手動(dòng)提交偏移量(可選)
ack.acknowledge();
}
}3. 配置 Kafka 監(jiān)聽(tīng)器容器工廠(關(guān)鍵?。?/h3>
package com.example.config;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.config.ContainerCustomizer;
import org.springframework.kafka.core.ConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
import org.springframework.kafka.support.serializer.DeserializationException;
@Configuration
public class KafkaConfig {
@Bean
public ConsumerFactory<String, AttendanceStatisticsSingleDto> consumerFactory() {
// 配置消費(fèi)者工廠
return new DefaultKafkaConsumerFactory<>(
Map.of(
"bootstrap.servers", "localhost:9092",
"group.id", "attendance-group",
"key.deserializer", StringDeserializer.class.getName(),
"value.deserializer", AttendanceDeserializer.class.getName()
)
);
}
@Bean
public ConcurrentKafkaListenerContainerFactory<String, AttendanceStatisticsSingleDto> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, AttendanceStatisticsSingleDto> factory =
new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory());
factory.setCommonErrorHandler(new DefaultErrorHandler(
new FixedBackOff(1000L, 3), // 重試3次,間隔1秒
new TopicPartitionOffset("attendance-topic.DLQ", 0) // 死信隊(duì)列
));
// 啟用自動(dòng)提交
factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL);
return factory;
}
}
package com.example.config;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.config.ContainerCustomizer;
import org.springframework.kafka.core.ConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
import org.springframework.kafka.support.serializer.DeserializationException;
@Configuration
public class KafkaConfig {
@Bean
public ConsumerFactory<String, AttendanceStatisticsSingleDto> consumerFactory() {
// 配置消費(fèi)者工廠
return new DefaultKafkaConsumerFactory<>(
Map.of(
"bootstrap.servers", "localhost:9092",
"group.id", "attendance-group",
"key.deserializer", StringDeserializer.class.getName(),
"value.deserializer", AttendanceDeserializer.class.getName()
)
);
}
@Bean
public ConcurrentKafkaListenerContainerFactory<String, AttendanceStatisticsSingleDto> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, AttendanceStatisticsSingleDto> factory =
new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory());
factory.setCommonErrorHandler(new DefaultErrorHandler(
new FixedBackOff(1000L, 3), // 重試3次,間隔1秒
new TopicPartitionOffset("attendance-topic.DLQ", 0) // 死信隊(duì)列
));
// 啟用自動(dòng)提交
factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL);
return factory;
}
}?? 關(guān)鍵配置說(shuō)明:
- setCommonErrorHandler:實(shí)現(xiàn)自動(dòng)重試 + 死信隊(duì)列
- setAckMode(ContainerProperties.AckMode.MANUAL):手動(dòng)提交偏移量(推薦)
- AttendanceDeserializer:與生產(chǎn)者序列化器嚴(yán)格匹配
六、測(cè)試用例(JUnit 5)
package com.example;
import com.example.service.AttendanceProducer;
import jnpf.model.attendance.event.AttendanceStatisticsSingleDto;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Date;
@SpringBootTest
public class KafkaIntegrationTest {
@Autowired
private AttendanceProducer producer;
@Test
public void testSendAttendanceData() {
AttendanceStatisticsSingleDto attendance = AttendanceStatisticsSingleDto.builder()
.tenantId("tenant_001")
.groupId("group_001")
.userId("user_001")
.day(new Date())
.build();
producer.sendAttendanceData(attendance);
// 實(shí)際測(cè)試需等待消費(fèi)者處理(此處簡(jiǎn)化)
System.out.println("? 測(cè)試消息已發(fā)送");
}
}? 測(cè)試執(zhí)行流程:
- 啟動(dòng) Kafka 服務(wù)
- 運(yùn)行 Spring Boot 應(yīng)用
- 執(zhí)行測(cè)試用例
- 消費(fèi)者控制臺(tái)輸出日志
七、最佳實(shí)踐與配置優(yōu)化
| 場(chǎng)景 | 配置 | 說(shuō)明 |
|---|---|---|
| 批量發(fā)送優(yōu)化 | spring.kafka.producer.batch-size=16384 spring.kafka.producer.linger-ms=100 | 提升吞吐量 30%+ |
| 消費(fèi)者線程控制 | spring.kafka.consumer.max-poll-records=500 | 防止單次拉取過(guò)多消息 |
| 事務(wù)保障 | spring.kafka.consumer.transaction-id-prefix=attendance-trans | 確保消息與數(shù)據(jù)庫(kù)操作原子性 |
| 死信隊(duì)列 | error-handler.dlq-topic=attendance-topic.DLQ | 消費(fèi)失敗消息自動(dòng)移至 DLQ |
| 監(jiān)控指標(biāo) | micrometer.kafka.enabled=true | 集成 Prometheus 監(jiān)控 |
八、常見(jiàn)問(wèn)題排查(YAML 配置特有)
| 問(wèn)題 | 現(xiàn)象 | 解決方案 |
|---|---|---|
| 序列化器未生效 | ClassCastException: String cannot be cast to AttendanceStatisticsSingleDto | 檢查 value-serializer 配置路徑是否正確 |
| YAML 縮進(jìn)錯(cuò)誤 | Invalid configuration | 嚴(yán)格使用 2 空格縮進(jìn)(避免 Tab) |
| 死信隊(duì)列未創(chuàng)建 | 消息未進(jìn)入 DLQ | 手動(dòng)創(chuàng)建主題:kafka-topics.sh --create --topic attendance-topic.DLQ --partitions 1 --replication-factor 1 |
| 日期格式異常 | Invalid format for Date | 在 AttendanceStatisticsSingleDto 中添加 @JsonFormat 注解 |
| 消費(fèi)者無(wú)法啟動(dòng) | No available broker | 檢查 bootstrap-servers 是否可訪問(wèn) |
九、總結(jié):Spring Boot + Kafka 核心流程
? 關(guān)鍵結(jié)論:
- YAML 配置更清晰:避免 properties 中的 spring.kafka.consumer.group-id 拼寫(xiě)錯(cuò)誤
- 自定義序列化器是核心:必須與消息實(shí)體嚴(yán)格匹配
- 死信隊(duì)列是生產(chǎn)必備:任何消費(fèi)者都應(yīng)配置 DLQ
- 手動(dòng)提交偏移量:避免消息丟失(AckMode.MANUAL)
?? 立即實(shí)踐:
- 創(chuàng)建 AttendanceStatisticsSingleDto 實(shí)體
- 實(shí)現(xiàn) AttendanceSerializer/AttendanceDeserializer
- 配置 application.yml 中的序列化器路徑
- 啟動(dòng) Kafka + Spring Boot 服務(wù)
- 發(fā)送測(cè)試消息 → 查看消費(fèi)者日志
到此這篇關(guān)于SpringBoot整合Kafka生產(chǎn)環(huán)境標(biāo)準(zhǔn)配置的文章就介紹到這了,更多相關(guān)SpringBoot整合Kafka生產(chǎn)環(huán)境配置內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Boot 配置文件從基礎(chǔ)語(yǔ)法到驗(yàn)證碼實(shí)戰(zhàn)
本文詳細(xì)解析了SpringBoot配置文件的作用及其主流格式,包括Properties和YAML(YML)的語(yǔ)法差異、優(yōu)先級(jí)及適用場(chǎng)景,并通過(guò)@Value與@ConfigurationProperties注解的代碼實(shí)戰(zhàn),演示了如何將配置項(xiàng)優(yōu)雅地集成到實(shí)際業(yè)務(wù)中,感興趣的朋友跟隨小編一起看看吧2025-12-12
SpringCloud+Nacos實(shí)現(xiàn)環(huán)境切換與配置管理最佳實(shí)踐
本文介紹了在SpringBoot項(xiàng)目中,如何通過(guò)SpringProfiles、Nacos配置中心及Maven構(gòu)建工具實(shí)現(xiàn)環(huán)境切換和配置管理,需要的朋友可以參考下2026-04-04
java中ReentrantLock實(shí)現(xiàn)公平鎖和非公平鎖
在Java里,公平鎖和非公平鎖是多線程編程中用于同步的兩種鎖機(jī)制,它們的主要差異在于獲取鎖的順序規(guī)則,下面就來(lái)詳細(xì)的介紹一下,感興趣的可以了解一下2025-12-12
使用kotlin編寫(xiě)spring cloud微服務(wù)的過(guò)程
這篇文章主要介紹了使用kotlin編寫(xiě)spring cloud微服務(wù)的相關(guān)知識(shí),本文給大家提到配置文件的操作代碼,給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-09-09
基于Java實(shí)現(xiàn)Redis多級(jí)緩存方案
這篇文章主要介紹了Redis多級(jí)緩存方案分享,傳統(tǒng)緩存方案、多級(jí)緩存方案、JVM本地緩存,舉例說(shuō)明這些方案,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-03-03
Java實(shí)現(xiàn)的文本字符串操作工具類(lèi)實(shí)例【數(shù)據(jù)替換,加密解密操作】
這篇文章主要介紹了Java實(shí)現(xiàn)的文本字符串操作工具類(lèi),可實(shí)現(xiàn)數(shù)據(jù)替換、加密解密等操作,涉及java字符串遍歷、編碼轉(zhuǎn)換、替換等相關(guān)操作技巧,需要的朋友可以參考下2017-10-10
Java實(shí)現(xiàn)使用Websocket發(fā)送消息詳細(xì)代碼舉例
這篇文章主要給大家介紹了關(guān)于Java實(shí)現(xiàn)使用Websocket發(fā)送消息的相關(guān)資料,WebSocket是一種協(xié)議,用于在Web應(yīng)用程序和服務(wù)器之間建立實(shí)時(shí)、雙向的通信連接,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-05-05

