【Kafka】03 消费者
0. 目录
1. 消费者和消费者组
- 每一个分区只能被一个消费组中的一个消费者所消费。
- 在消费组中增加消费者可以增加整体的消费能力,消费者不能多于分区个数。
- 消息中间件2中模式:点对点的队列模式、订阅/发布模式
点对点:所有消费者属于1个消费组
订阅/发布:各个消费者属于不同的消费组 - 通过group.id参数配置
2. 客户端开发
2.1 消费步骤:
(1)配置客户端参数并创建消费组实例
(2)订阅主题
(3)拉取消息并消费
(4)提交消费位移
(5)关闭消费组实例
2.2 客户端参数:
group.id默认值为""
client.id默认值为"",之后KafkaConsumer会自动生成“consumer-”+数字
2.3 订阅主题和分区
一个消费者可以订阅一到多个主题:
public void subscribe(Collection<String> topics, ConsumerRebalanceListener listener); //ConsumerRebalanceListener用于设置再均衡监听器
public void subscribe(Collection<String> topics);
public void subscribe(Pattern pattern, ConsumerRebalanceListener listener); //正则表达式,可以订阅多个主题,用于和其他系统之间进行数据复制。
public void subscribe(Pattern pattern);
订阅指定分区:
public void assign(Collection<TopicPartition> partitions);
public class TopicPartition{
int partition;
String topic;
}
如果不知道分区个数,可以通过KafkaConsumer.partitionsFor(String topic);获取。
public List<PartitionInfo> partitionsFor(Collection<TopicPartition> partitions);
//获取分区信息的列表
public class PartitionInfo {
String topic;
int partition;
Node leader;
...
}
三种不同的订阅状态:
AUTO_TOPICS---->subcribe(Collection)
AUTO_PATTERN---->subcribe(Pattern)
USER_ASSIGNED---->assign(Collection)
2.4 消费消息
拉取消息
public ConsumerRecords<K, V> poll (final Duration timeout);
timeout用来控制poll的阻塞时间,如果没有可用数据会发生阻塞。需要多长时间将控制权交给轮询的应用程序。
设置为0,不管是否拉取到消息,直接返回。
如果应用线程唯一的工作就是拉取并消费消息,则可设置为最大值(Long.MAX_VALUE)
消费消息
指定分区消费
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(1000));
for (TopicPartition tp : records.partitions()) {
for (ConsumerRecord<String, String> record : records.records(tp)) {
System.out.println(record.value);
}
}
2.5 消费内部逻辑
位移提交
- 消费位移存在Kafka内部主题:_consumer_offset。
- 提交位移提交的是下一条需要拉取的位置。
- 位移提交导致的2种异常:重复消费、消息丢失
重复消费:消费完所有消息之后,提交位移,消费的过程中发生异常,会重新从之前的偏移量开始拉取消息。
消息丢失:拉取多个消息之后就提交位移,在消费拉取的消息过程中发生了异常,导致消息丢失。 - 默认自动提交位移,即定期提交,默认5s。自动提交会带来上面的2个问题
- 手工提交:
同步提交:
commitSync();每个批次提交一次;每个分区每个批次提交一次。阻塞式。
异步提交:
commitAsync();非阻塞。每个批次提交一次;每个分区每个批次提交一次;增加回调方法,在回调方法中引入重试,重试的时候通过保存并判断前一次位移序号的大小决定是否需要提交位移。
只要是消费者异常退出,重复消费的问题必然存在。
如果是消费者正常退出,或者发生再均衡,需要使用同步提交做最后的把关。
2.6 优雅关闭消费
public static final AtomicBoolean isRunning = new AtomicBoolean(true);
consumer.subscribe(ArraysList.asList(topic));
try {
while(running.get()) {
// consumer.poll($%##);
// commit offset
}
} catch (WakeupException e) {
// ignore the error
} catch (Exception e) {
// some process handle error
} finally {
consumer.close();
}
退出循环可以使用running.set(false)或者