Kafka consumer

本文围绕Kafka Consumer展开,介绍了消费者组、位移等概念。消费者组可实现高伸缩性与容错性,位移提交决定消费语义。还阐述了构建Kafka Consumer的步骤、主要参数、订阅topic的方式、手动位移提交方法、重平衡触发条件以及解序列化器的自定义方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

consumer分类:

1。消费者组consumer group

消费者使用一个消费者组名group id 来标记自己,topic的每条消息都只会被发送到每个订阅它的消费者组的一个消费者实例上。

一个组可以只有一个消费者实例

 

所有consumer实例都属于想同的group---------实现基于队列的模型,每条消息只会被一个consumer处理

consumer实例都属于不同的group----实现基于发布 订阅模型极端情况就是每个consumer实例都设置完全不同的group 这样kafka就会广播到所有consumer实例上

 

consumer group适用于实现高伸缩性,高容错性的consumer机制,组内多个consumer实例可以同时读取kafka消息 一个consumer挂了 consumer group会立即将已经崩溃的consumer负责的分区 转交给其他实例处理,整个group不会影响 不会丢失数据 这个过程被称为重平衡 rebalance

 

 

位移 offset

这里的位移指的是consumer的offset 与分区日志中的offset不同,通俗点来说就是每个consumer实例都会为它消费的分区维护一个属于自己的位置信息来记录当前消费了多少消息,这里的使用的一个map来保存其实例订阅的topic中某个分区的offset

客户端需要定时向kafka提交自己的offset 这个过程叫做位移提交 offset commit 它不仅表征了consumer的消费进度 还直接决定了consumer端的消费语义保证

 

是个kv格式的 k是个三元数组 group id +topic+分区号 v就是offset最新的值

 

kafka内部采用一个内部topic (_consumer_offsets)上 为内部topic创建了50个分区,对每个group id做哈希取模运算,从而实现了负载 所以每个consumer group保存对offset都有极大可能出现在该内部topic的不同分区上

很多消息引擎都把消费端的offset保存在服务端 这样做好处是实现简单 但有三个不足

服务端变成了有状态的 增加同步成本

需要引入应答机制 确认消费成功

保存许多consumer实例的offset ,故必然引入复杂的数据结果 造成不必要的资源浪费

kafka选择了不同的方式 让客户端保存,只需要保存一个简单的长整类型数据就可以了

 

构建kafka consumer

1。构造一个java utils properties对象 至少指定 bootstra.server,key.deserializer,value.deserializer,和group.id 与producer不同的是 需要显示指明group.id

2。使用上面构建的properties 构建 kafkaconsumer

3。调用kafkaconsumer的subscribe方法订阅 consumer group的topic列表

4。循环调用kafkaconsumer的poll方法获取封装在consumerRecord的topic消息

5。处理获取到的consumerRecord对象

6。关闭kafkaConsumer

 

consumer主要参数:

session.timout.ms : 明确为coordinator 检测失败的时间,因此在实际使用中 可以为该参数设置一个较小的值 让coordinator更快的检测到sonsumer崩溃情况 从而更快的开启rebalance 避免造成消费滞后 默认值10秒

 

max.poll.interval.ms:consumer处理逻辑的最大时间

 

auto.offset.reset :制定了无位移信息或者位移越界 即consumer要消费的消息不再当前消息日志和区间范围时 kafka的对应策略

有三个值

earliest:指定从最早的位移开始消费,最早不一定为0

latest:指定从最新位移处开始消费

none:如果位移越界或者无位移直接抛出异常,

 

enable.audo.commit:该参数指定consumer是否自动提交位移,为true则consumer在后台自动提交位移,对于有较强 “精确处理一次的“ 语义用户要求来说 建议设置为手动提交

 

fetch.max.bytes:制定了consumer端单次获取数据的最大字节数

 

max.poll.records:该参数控制单次poll调用返回的最大消息数

 

 

订阅topic:

第一种基于队列的 consumer.subscribe(Arrays.asList("topic1","topic2","topic3"));

第二种 使用独立consumer(standalone sonsumer)

TopicPartition tp1 = new ......

TopicPartition tp2 = new ......

consumer.assign(Arrays.asList(tp1,tp2));

基于正则表达式订阅topic

consumer.subscribe(Pattern.compile("kafka-.*"),new ConsumerRebalanceListener().......);

使用正则表达式 必须指定ConsumerRebalanceListener() 该类是一个回调接口,用户通过这个类来实现consumer分区分配方案改变的处理逻辑

注:如果用户设置的是自动提交 即设置 enable.auto.commit = true 则通常不用理会这个类,使用如下方式就好

consumer.subscribe(Pattern.compile("kafka-.*"),new NoOpConsumerRebalanceListener().......);

如果为手动 至少要在ConsumerRebalanceListener 实现类的onPartitionsRevoked方法中处理分区分配方案变更的位移提交

 

 

常见的手动位移提交方式代码如下 有参版本 和无参版本:

 

consumer.subscribe(Arrays.asList("xinyu-topic"));

final int minBatchSize = 500;

List<ConsumerRecord<String,String>> buffer = new ArrayList<>();

try {

while (true){

ConsumerRecords<String,String> records = consumer.poll(1000);

//第一种手动提交

/*for (ConsumerRecord<String, String> record : records) {

System.out.print(record);

buffer.add(record);

}

//提交一定数量后 手动提交位移值

if(buffer.size()>= minBatchSize){

//业务操作。。。。。。

consumer.commitAsync();

//清空缓冲区

buffer.clear();

}*/

 

//第二种 更加细粒度的位移提交策略 手动提交部分分区的位移代码如下

try {

for (TopicPartition partition :records.partitions()){

//获取到该主题分区

List<ConsumerRecord<String,String>> partitionRecods = records.records(partition);

//遍历该分区消息 打印

for (ConsumerRecord<String,String> record : partitionRecods){

System.out.println(record.offset()+":"+record.value());

}

//获取到该分区下的最后一个值的offset

long lastoffset = partitionRecods.get(partitionRecods.size()-1).offset();

 

//提交该分区位移 注意提交的位移为当前消费位移数+1

consumer.commitSync(Collections.singletonMap(partition,new OffsetAndMetadata(lastoffset+1)));

 

}

} catch (Exception e) {

e.printStackTrace();

}finally {

consumer.close();

}

 

}

} catch (Exception e) {

e.printStackTrace();

} finally {

//关闭consumer 默认最多等待30s

consumer.close();

}

}

 

重平衡 rebalance

组rebalance触发的条件有三个:

组成员发生变更,比如新的consumer加入组 或已有的主动离开组,再或者是有consumer崩溃 或者是consumer无法在指定的时间内完成消息处理 coordinator就认识该consumer崩溃

组订阅的topic发生变更,比如使用正则表达式的订阅,当有新的匹配正则表达式的topic被创建时 则触发rebalance

组订阅topic的分区数发生变更

 

解序列化器:

与product一样 可以自定义自己的解序列化器

定义或者复用serializer的对象格式

创建自定义的解序列化类,令其实现org.apache.kafka.common.serialization.Deserializer接口 在deserializer方法中实现解序列化逻辑

在构造kafkaconsumer的propertites对象中 设置自己的解序列化器的全限定名

 

 

 

### Kafka Consumer 使用指南 #### 1. 基础配置 Kafka Consumer 是 Apache Kafka 提供的一个用于消费消息的组件。其核心功能是从指定的主题(Topic)中拉取消息并处理它们。为了正确使用 Kafka Consumer,需要设置一些必要的参数。 以下是常见的基础配置项及其作用: - `bootstrap.servers`:定义连接到 Kafka 集群所需的地址列表。 - `group.id`:消费者组 ID,多个消费者可以共享同一个组以实现负载均衡。 - `key.deserializer` 和 `value.deserializer`:分别指定了键和值的反序列化器,默认情况下通常使用 `StringDeserializer` 或其他自定义实现。 - `auto.offset.reset`:当消费者的偏移量不存在或超出范围时的行为策略,可选值有 `earliest`, `latest`, `none` 等[^1]。 #### 2. 示例代码 下面是一个完整的 Java 实现示例,展示如何创建、订阅主题以及读取消息: ```java import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.clients.consumer.KafkaConsumer; import java.time.Duration; import java.util.Collections; import java.util.Properties; public class SimpleKafkaConsumer { public static void main(String[] args) { Properties props = new Properties(); props.put("bootstrap.servers", "localhost:9092"); props.put("group.id", "test-group"); props.put("enable.auto.commit", "true"); // 自动提交偏移量 props.put("auto.commit.interval.ms", "1000"); props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); try (KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props)) { consumer.subscribe(Collections.singletonList("my-topic")); while (true) { ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100)); for (ConsumerRecord<String, String> record : records) { System.out.printf("Offset = %d, Key = %s, Value = %s%n", record.offset(), record.key(), record.value()); } } } catch (Exception e) { e.printStackTrace(); } } } ``` 此代码片段展示了如何通过 Kafka Consumer 订阅特定主题,并持续轮询新到达的消息[^4]。 #### 3. 常见报错及解决方案 在实际开发过程中可能会遇到各种异常情况,这里列举几个典型问题及其解决办法: ##### a. **No current assignment** 如果尝试调用某些操作而未先分配分区,则会抛出此类错误。可以通过显式调用 `assign()` 方法来解决问题: ```java List<TopicPartition> partitions = Arrays.asList( new TopicPartition("topic-name", 0), new TopicPartition("topic-name", 1) ); consumer.assign(partitions); ``` ##### b. **Deserialization exception** 这通常是由于发送方与接收方之间使用的序列化协议不匹配引起的。确认生产者端的数据格式是否符合当前消费者所期望的形式。 ##### c. **TimeoutException during poll() 可能是因为网络延迟过高或者服务器资源不足造成的超时现象。适当增加请求等待时间限制可以帮助缓解这一状况: ```properties max.poll.interval.ms=300000 request.timeout.ms=60000 ``` #### 4. 性能优化建议 对于大规模应用场景下的性能考量,可以从以下几个方面入手改进效率: - 调整批次大小 (`fetch.min.bytes`) 来减少频繁的小规模传输开销; - 启用压缩机制降低带宽占用率; - 根据工作负载调整线程池数量平衡计算能力需求[^2]; --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值