Kafka消费者不是线程安全的。
Kafka消费者通过订阅主题(Topic)拉取消息。每个消费者对应一个消费组,消息发布到主题后,只会投递给订阅它的每个消费组中的一个消费者。
一个主题有多个分区(parition),每一个分区只能被一个消费组中的一个消费者所有消费。Kafka默认的分区分配策略会根据消费组中消费者的数量动态分配分区,提供横向伸缩性。消费组内的消费者数量应该小于或等于订阅主题的分区数,否则多于的消费者是无法消费到消息造成资源浪费。
消费者可以订阅一个或多个主题,或通过正则表达式订阅,当新的主题符合正则表达式,消费者就能消费到消息。
void subscribe(Collection<String> topics);
void subscribe(Collection<String> topics, ConsumerRebalanceListener callback);
void subscribe(Pattern pattern, ConsumerRebalanceListener callback);
void subscribe(Pattern pattern);
subscribe()方法支持再均衡,自动分配分区。
assign()方法之前订阅某主题的特定分区中的消息,但是它不具备自动再均衡的特性。
void assign(Collection<TopicPartition> partitions);
poll()方法阻塞式,timeout参数控制阻塞时间。poll()方法每次拉取的是一组消息ConsumerRecords,里面包含若干的ConsumerRecord。
自动消费位移提交,当系统异常时会比较容易造成消息重复消费和丢失消息情况。
手动同步位移提交,以分区粒度提交,可保证一定的性能和容错性。
消费者位移持久化在内部的__consumer_offsets主题中。
seek()方法指定消费分区位移。
void seek(TopicPartition partition, long offset);
void seek(TopicPartition partition, OffsetAndMetadata offsetAndMetadata);
void seekToBeginning(Collection<TopicPartition> partitions);
void seekToEnd(Collection<TopicPartition> partitions);
再均衡是指分区的所属权从一个消费者转移到另一个消费者,有利用高可用和伸缩性。但是再均衡时,消费组是不可用的,消费者的状态会丢失,这样可能会造成数据丢失。可以利用再均衡监听器ConsumerRebalanceListener对再均衡前后做处理,保证消息可靠性。
public interface ConsumerRebalanceListener {
void onPartitionsRevoked(Collection<TopicPartition> partitions);
void onPartitionsAssigned(Collection<TopicPartition> partitions);
}
ConsumerRebalanceListener源码中给出的使用建议
public class SaveOffsetsOnRebalance implements ConsumerRebalanceListener {
private Consumer<?,?> consumer;
public SaveOffsetsOnRebalance(Consumer<?,?> consumer) {
this.consumer = consumer;
}
public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
// save the offsets in an external store using some custom code not described here
for(TopicPartition partition: partitions)
saveOffsetInExternalStore(consumer.position(partition));
}
public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
// read the offsets from an external store using some custom code not described here
for(TopicPartition partition: partitions)
consumer.seek(partition, readOffsetFromExternalStore(partition));
}
}
}