Kafka producer 端的工作逻辑及样例

在这里插入图片描述

producer 的工作逻辑:

启动 producer 的线程将待发送消息封装成 ProducerRecord. 然后将其序列化发送给 partitioner, 再由后者确定了目标分区后一同发送给位于 producer 程序中的一块内存缓冲区. 而 producer 的另一个线程负责实时从缓冲区中提取出来准备就绪的消息封装成一个批次,发送给对应的 broker.

public class SimpleKafkaProducer {

public static void main(String[] args) {

    Properties props = new Properties();

    //broker地址
    props.put("bootstrap.servers", "localhost:9092");

    //请求时候需要验证
    props.put("acks", "all");

    //请求失败时候需要重试
    props.put("retries", 0);

    //内存缓存区大小
    props.put("buffer.memory", 33554432);

    //指定消息key序列化方式
    props.put("key.serializer",
            "org.apache.kafka.common.serialization.StringSerializer");

    //指定消息本身的序列化方式
    props.put("value.serializer",
            "org.apache.kafka.common.serialization.StringSerializer");

    Producer<String, String> producer = new KafkaProducer<>(props);

    for (int i = 0; i < 10; i++)
        producer.send(new ProducerRecord<>("test", Integer.toString(i), Integer.toString(i)));
    System.out.println("Message sent successfully");
    producer.close();
}

}

至少要指定 bootstrap.servers、key.serializer 及 value.serializer 这三个值.

bootstrap.servers
用于创建向 Kafka broker 服务器的链接,比如 k1:9092,k2:9092,k3:9092. 该参数指定多台机器只是为了故障转移使用. 这样即使某一台 broker 挂掉了,producer 重启后依然能通过该参数指定的其他 broker 连入 Kafka 集群(如果说我们这里只指定一台机器,那么如果这台 broker 挂了,你就算重启producer 也连不到kafka集群).

key.serializer
被发送到 broker 端的任何消息格式都必须是字节数组,因此消息的各个组件必须首先序列化.

value.serializer
对消息体进行序列化

2.构造 KafkaProducer 对象
Producer<String, String> producer = new KafkaProducer<>(props);

3.构造 ProducerRecord 对象.
new ProducerRecord<>(“my-topic”, Integer.valueOf(1));

发送消息

异步发送是通过 Callback(){} 回调来实现的.

同步发送是通过 Future.get() 一直等待结果来实现的.

异常分类

1.可重试异常:LeaderNotAvailableException、NotControllerException、NetworkException

2.不可重试异常:所有非继承 RetriableException 类的其他异常都属于不可重试异常.
RecordTooLargeException、SerializationException、KafkaException

关闭 producer

producer 程序结束时一定要关闭 producer!如果调用的是普通的无参 close 方法,producer 会被允许先处理完之前的发送请求后再关闭,即所谓的“优雅”退出。还有一种是待超时的关闭,时间一到,producer 会被强制结束,并立即丢弃所有未发送以及未应答的发送请求

producer 主要参数

acks 用于控制 producer 生产消息的持久性.
当 producer 发送一条消息给 Kafka 集群时,这条消息会被发送到指定 topic 分区 leader 所在的 broker 上,producer 等待从该 leader broker 返回写入的结果以确定消息是否被成功写入. 这一切完成后 producer 可以继续发送新的消息.
显然 leader 何时发送写入结果返给 producer 直接会影响消息的持久性甚至是 producer 端的吞吐量.acks 指定了再给 producer 发送响应前,leader broker 必须要确保已成功写入该消息的副本数. 当前 acks 有三个值:0、1、all.

acks=0——producer 不理睬 leader broker 端的处理结果,
acks=all或者-1,会等待 ISR 中所有副本都成功写入他们各自的本地日志后,才发送响应结果给 producer.

acks=1 leader broker 仅将该消写入本地日志后便发送响应结果 producer,而不需等待 ISR 中其他副本写入该消息.

buffer.memory 指定 producer 端用于缓存消息的缓冲区大小,单位是字节. 默认值是 32M.

compression.type 设置 producer 端是否压缩消息. 默认值是 none. 目前支持 GZIP、Snappy 和 LZ4.

retries 在发送失败时重试的次数,默认值是 0.

batch.size 默认值是 16KB 批次的大小.

linger.ms 控制消息发送时延行为的,默认值为 0. 表示该消息立即被发送,无须关心 batch 是否被填满.

max.request.size 用于控制 producer 发送请求的大小(控制 producer 端能发送的最大消息的大小).

request.timeout.ms 当 producer 发送请求给 broker 后,broker 需要在规定的时间范围内将处理结果返还给 producer.

消息分区机制

Java 版的 producer 会根据 murmur2 算法计算消息 key 的哈希值,然后对总分区数求模得到消息要发送到的目标分区号.
问题:Kafka 允许用户动态扩展分区不?
对于这个问题,首先需要明确一点的是 kafka 支持扩展分区,但是不支持减少分区. 新增分区后,各个分区上的消息不会重新分配. 比如说以前分区A上有30条消息,分区比上有30条消息,现在新增分区C,不会说消息均衡下,ABC三个分区各20条数据,而是说在后面的时候,消息通过模运算,可能落到A上,可能落到B上,也可能落到C上. 新增分区后,会多消息的顺序造成影响,也会影响消息落到那个分区. 现在说下为啥不能减少分区了,第一个问题是,减少分区上的消息怎么办了?如果保留的话直接插入到某个分区的尾部?还是说消息重平衡?这些都是需要考虑的问题.

参考:https://blog.youkuaiyun.com/u013256816/article/details/82804564

自定义分区:
1.创建一个类继承 Partitioner 接口,主要分区逻辑在 Partitioner.partition 中实现.
2.在用于构造 KafkaProducer 的 Properties 对象中设置 partitioner.class 参数.

消息序列化

目前内置的序列化类有:StringSerializer、DoubleSerializer 等

自定义序列化

1.定义数据对象格式
2.创建自定义序列化类,实现 Serializer 接口
3.在用于构造 KafkaProducer 的 Properties 对象中设置 key.serializer 或 value.serializer.

producer 拦截器

interceptor 使得用户在消息发送前以及 producer 回调逻辑前有机会对消息做一些定制化需求,比如修改消息等. producer 允许用户指定多个 producer 按序作用于同一条消息从而形成拦截链. interceptor 的实现接口:ProducerInterceptor.

onSend:该方法封装进 KafkaProducer.send 方法中,即它允许在用户主线程中. producer 确保消息被序列化以计算分区前调用该方法. 用户可以在该方法中对消息做任何操作,但最好不要修改消息所属的 topic 和分区,否则会影响目标分区的计算.

onAcknowledgement:该方法会在消息被应答之前或消息发送失败时调用,并且通常是在 producer 回调逻辑触发之前. onAcknowledgement 运行在 producer 的 I/O 线程中,因此不要在该方法中放入很“重”的逻辑,否则会拖慢 producer 的消息发送效率.

close:关闭 interceptor,主要用于执行一些资源清理工作.

无消息丢失配置

block.on.buffer.full=true
acks=all
retries=Integer.MAX_VALUE
max.in.flight.requests.per.connection=1
使用 KafkaProducer.send(record,callback)
unclean.leader.election.enable=false
replication.factory=3
min.insync.replicas=2
replication.factory>min.insync.replicas
enable.auto.commit=false

producer 端配置

block.on.buffer.full=true 内存缓冲区填满时 producer 处于阻塞状态并停止接收新的消息而不是抛出异常.
acks=all 必须等到所有 follower 都响应了发送消息才能认为提交成功.
retries=Integer.MAX_VALUE producer 不会重试那些肯定无法恢复的错误.
max.in.flight.requests.per.connection=1 防止 topic 同分区下的消息乱序问题.

broker 端配置

unclean.leader.election.enable=false 不允许非 ISR 中的副本被选举为 leader,从而避免 broker 端因为日志水位被截断导致消息丢失

replication.factory>=3 强调使用多分区保存消息.

min.insync.replicas > 1 用于控制某条消息被写入到 ISR 中的多少个副本才算成功,设置成大于 1 是为了提升 producer 端发送语义的持久性. 只有在 producer 端 acks 被设置成 all 时,这个参数才有意义.

确保 replication.factory>min.insync.replicas. 推荐配置成 replication.factory = min.insync.replicas + 1

消息压缩

producer 端压缩——broker 端保持——consumer 端解压缩.

压缩算法

GZIP LZ4 SNAPPY

对于 Kafka 而言 LZ4 > Snappy > GZip

多线程处理

多线程单 KafkaProducer 实例
多线程多 KafkaProducer 实例

如果是分区数不是很多的 Kafka 集群而言,第一种比较合适,若是有超多分区的集群,第二种方式更合适.

<think>好的,我现在需要帮助用户查找Spring Boot集成Kafka的配置和代码示。用户之前提到过关于Spring Kafka的错误,现在转向具体的集成配置和代码,可能需要详细的步骤。 首先,我需要回顾用户的历史问题。用户之前遇到了“Invalid state: the invoker was not active, but the consumer had allocated partitions”错误,这可能与消费者配置或分区分配有关。现在用户想要的是如何正确配置Spring Boot与Kafka的集成,避免类似错误。 接下来,用户提供的引用中有提到Spring Integration和Spring Boot的版本信息,特别是引用[1]和[4]提到了Spring Integration的功能和Spring Boot的文档。这可能意味着用户希望使用Spring Integration来简化Kafka的集成,或者需要参考官方文档。不过用户明确要求的是Spring Boot集成Kafka,所以可能需要结合Spring Boot的Kafka支持,而不是Spring Integration。 用户可能需要基本的配置步骤,比如依赖添加、配置文件的设置,以及生产者和消费者的代码示。此外,用户之前的问题涉及到消费者组和分区分配,因此在配置中可能需要特别注意相关参数,如group.id、partition.assignment.strategy等,以避免之前出现的错误。 考虑到用户可能遇到的状态不一致问题,配置中的关键参数如session.timeout.ms、max.poll.interval.ms和auto.offset.reset需要正确设置。如,增加max.poll.interval.ms可以避免因为处理时间过长导致的重平衡,而auto.offset.reset可以配置消费者在找不到偏移量时的行为。 另外,用户可能需要了解如何手动提交偏移量,以确保消息处理的可靠性。在代码示中,使用@KafkaListener并手动确认偏移量,可以避免自动提交可能带来的问题,比如消息处理失败后偏移量仍然提交的情况。 用户可能还希望看到如何配置生产者的参数,比如重试机制和确认机制(acks),以确保消息的可靠传输。如,设置retries和acks=all可以提高生产者的可靠性,确保消息不会丢失。 在高级配置部分,用户可能对如何自定义序列化器、拦截器和错误处理器感兴趣。如,使用自定义的序列化器来处理特定的消息格式,或者添加拦截器来记录生产者和消费者的日志,有助于调试和监控。 错误处理方面,用户可能需要配置监听容器错误处理器和死信队列(DLQ),以便在消息处理失败时进行重试或转移到其他主题,避免消息丢失和无限重试导致的阻塞。 最后,用户可能需要一些调试和监控的建议,比如使用Kafka原生工具检查消费者组状态,或者通过Spring Boot Actuator监控Kafka相关指标,这有助于及时发现和解决问题。 总结下来,用户需要的是一个全面的Spring Boot集成Kafka的指南,包括基本配置、生产者和消费者的代码示、关键参数说明、高级配置选项以及错误处理和监控方法。同时,需要结合用户之前遇到的错误,强调相关配置的重要性,避免再次出现状态不一致的问题。</think>### Spring Boot集成Kafka的完整配置与代码示 --- #### 一、基础环境搭建 1. **依赖引入**(Gradle/Maven) ```groovy // Gradle配置 implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.kafka:spring-kafka' // 核心依赖[^1] ``` ```xml <!-- Maven配置 --> <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> <version>2.8.3</version> <!-- 版本与Spring Boot 2.5.x兼容 --> </dependency> ``` 2. **application.yml核心配置** ```yaml spring: kafka: bootstrap-servers: localhost:9092 # Kafka集群地址 producer: key-serializer: org.apache.kafka.common.serialization.StringSerializer value-serializer: org.apache.kafka.common.serialization.StringSerializer retries: 3 # 生产者重试次数 acks: all # 消息确认级别[^1] consumer: group-id: my-consumer-group # 消费者组唯一标识 key-deserializer: org.apache.kafka.common.serialization.StringDeserializer value-deserializer: org.apache.kafka.common.serialization.StringDeserializer auto-offset-reset: earliest # 偏移量策略 enable-auto-commit: false # 禁用自动提交 max-poll-records: 500 # 单次拉取最大记录数 ``` --- #### 二、生产者实现 1. **同步发送模式** ```java @Autowired private KafkaTemplate<String, String> kafkaTemplate; public void sendMessage(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("发送成功: partition={}, offset={}", result.getRecordMetadata().partition(), result.getRecordMetadata().offset()); } @Override public void onFailure(Throwable ex) { log.error("发送失败: {}", ex.getMessage()); } }); } ``` 2. **事务支持配置** ```java @Bean public KafkaTransactionManager<String, String> transactionManager( ProducerFactory<String, String> producerFactory) { return new KafkaTransactionManager<>(producerFactory); } @Transactional(transactionManager = "kafkaTransactionManager") public void sendWithTransaction(String topic, String message) { kafkaTemplate.send(topic, message); // 其他数据库操作 } ``` --- #### 三、消费者实现 1. **基础监听器** ```java @KafkaListener(topics = "my-topic", groupId = "my-consumer-group") public void listen(String message, @Header(KafkaHeaders.RECEIVED_PARTITION_ID) int partition, Acknowledgment acknowledgment) { try { log.info("收到消息: partition={}, content={}", partition, message); // 业务处理逻辑 acknowledgment.acknowledge(); // 手动提交偏移量 } catch (Exception e) { acknowledgment.nack(Duration.ofSeconds(10)); // 延迟重试 } } ``` 2. **批量消费模式** ```java @KafkaListener(topics = "batch-topic", containerFactory = "batchFactory") public void batchListen(List<ConsumerRecord<String, String>> records) { for (ConsumerRecord<String, String> record : records) { log.info("批量处理: key={}, value={}", record.key(), record.value()); } } // 配置容器工厂 @Bean public ConcurrentKafkaListenerContainerFactory<String, String> batchFactory() { ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>(); factory.setConsumerFactory(consumerFactory()); factory.setBatchListener(true); // 启用批量模式 factory.getContainerProperties().setIdleEventInterval(60000L); return factory; } ``` --- #### 四、高级配置 1. **自定义序列化** ```java @Bean public ProducerFactory<String, CustomObject> customProducerFactory() { Map<String, Object> configProps = new HashMap<>(); configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, CustomSerializer.class.getName()); return new DefaultKafkaProducerFactory<>(configProps); } @Bean public KafkaTemplate<String, CustomObject> customKafkaTemplate() { return new KafkaTemplate<>(customProducerFactory()); } ``` 2. **消费者拦截器** ```java public class ConsumerInterceptor implements ConsumerInterceptor<String, String> { @Override public ConsumerRecords<String, String> onConsume(ConsumerRecords<String, String> records) { records.forEach(record -> log.debug("拦截消息: topic={}, partition={}", record.topic(), record.partition())); return records; } } // 配置注入 spring.kafka.consumer.properties.interceptor.classes=com.example.ConsumerInterceptor ``` --- #### 五、错误处理机制 1. **监听容器错误处理器** ```java @Bean public ConsumerAwareListenerErrorHandler customErrorHandler() { return (message, exception, consumer) -> { log.error("消费异常: headers={}, payload={}", message.getHeaders(), message.getPayload()); return null; }; } // 使用示 @KafkaListener(topics = "error-topic", errorHandler = "customErrorHandler") public void handleErrors(String message) {...} ``` 2. **死信队列(DLQ)配置** ```java @Bean public DeadLetterPublishingRecoverer dlqRecoverer() { return new DeadLetterPublishingRecoverer(kafkaTemplate, (record, ex) -> new TopicPartition("dlq-topic", record.partition())); } @Bean public SeekToCurrentErrorHandler errorHandler() { return new SeekToCurrentErrorHandler(dlqRecoverer(), new FixedBackOff(1000L, 3)); } ``` --- ### 调试与监控 1. **消费者状态检查** ```bash kafka-consumer-groups.sh --bootstrap-server localhost:9092 \ --describe --group my-consumer-group ``` 2. **Spring Boot Actuator集成** ```yaml management: endpoints: web: exposure: include: health,kafka metrics: export: prometheus: enabled: true ``` --- ### 相关问题 1. 如何实现Kafka消息的延迟重试机制? 2. Spring Boot中如何配置多个Kafka集群? 3. Kafka消费者如何实现动态主题订阅? 4. 如何通过JMX监控Kafka生产者的吞吐量? 5. Spring Kafka如何与Spring Cloud Stream集成使用? [^1]: 核心配置参数参考自Spring Kafka官方文档的生产者/消费者配置章节 [^2]: 事务支持实现基于Spring Framework的声明式事务管理机制 [^3]: 错误处理策略结合了Kafka原生重试机制与Spring的重试模板
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值