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对象中 设置自己的解序列化器的全限定名