public ConsumerRecords<K, V> poll(final Duration timeout) { // @1
return poll(time.timer(timeout), true); // @2
}
代码@1:参数为超时时间,使用 java 的 Duration 来定义。
代码@2:调用内部的 poll 方法。
KafkaConsumer#poll
private ConsumerRecords<K, V> poll(final Timer timer, final boolean includeMetadataInTimeout) { // @1
acquireAndEnsureOpen(); // @2
try {
if (this.subscriptions.hasNoSubscriptionOrUserAssignment()) { // @3
throw new IllegalStateException(“Consumer is not subscribed to any topics or assigned any partitions”);
}
// poll for new data until the timeout expires
do { // @4
client.maybeTriggerWakeup(); //@5
if (includeMetadataInTimeout) { // @6
if (!updateAssignmentMetadataIfNeeded(timer)) {
return ConsumerRecords.empty();
}
} else {
while (!updateAssignmentMetadataIfNeeded(time.timer(Long.MAX_VALUE))) {
log.warn(“Still waiting for metadata”);
}
}
final Map<TopicPartition, List<ConsumerRecord<K, V>>> records = pollForFetches(timer); // @7
if (!records.isEmpty()) {
if (fetcher.sendFetches() > 0 || client.hasPendingRequests()) { // @8
client.pollNoWakeup();
}
return this.interceptors.onConsume(new ConsumerRecords<>(records)); // @9
}
} while (timer.notExpired());
return ConsumerRecords.empty();
} finally {
release();
}
}
代码@1:首先先对其参数含义进行讲解。
- boolean includeMetadataInTimeout
拉取消息的超时时间是否包含更新元数据的时间,默认为true,即包含。
代码@2:检查是否可以拉取消息,其主要判断依据如下:
-
KafkaConsumer 是否有其他线程再执行,如果有,则抛出异常,因为 - KafkaConsumer 是线程不安全的,同一时间只能一个线程执行。
-
KafkaConsumer 没有被关闭。
代码@3:如果当前消费者未订阅任何主题或者没有指定队列,则抛出错误,结束本次消息拉取。
代码@4:使用 do while 结构循环拉取消息,直到超时或拉取到消息。
代码@5:避免在禁止禁用wakeup时,有请求想唤醒时则抛出异常,例如在下面的@8时,会禁用wakeup。
代码@6:更新相关元数据,为真正向 broker 发送消息拉取请求做好准备,该方法将在下面详细介绍,现在先简单介绍其核心实现点:
-
如有必要,先向 broker 端拉取最新的订阅信息(包含消费组内的在线的消费客户端)。
-
执行已完成(异步提交)的 offset 提交请求的回调函数。
-
维护与 broker 端的心跳请求,确保不会被“踢出”消费组。
-
更新元信息。
-
如果是自动提交消费偏移量,则自动提交偏移量。
-
更新各个分区下次待拉取的偏移量。
这里会有一个更新元数据是否占用消息拉取的超时时间,默认为 true。
代码@7:调用 pollForFetches 向broker拉取消息,该方法将在下文详细介绍。
代码@8:如果拉取到的消息集合不为空,再返回该批消息之前,如果还有挤压的拉取请求,可以继续发送拉取请求,但此时会禁用warkup,主要的目的是用户在处理消息时,KafkaConsumer 还可以继续向broker 拉取消息。
代码@9:执行消费拦截器。
接下来对上文提到的代码@6、@7进行详细介绍。
1.1 KafkaConsumer updateAssignmentMetadataIfNeeded 详解
KafkaConsumer#updateAssignmentMetadataIfNeeded
boolean updateAssignmentMetadataIfNeeded(final Timer timer) {
if (coordinator != null && !coordinator.poll(timer)) { // @1
return false;
}
return updateFetchPositions(timer); // @2
}
要理解这个方法实现的用途,我们就必须依次对 coordinator.poll 方法与 updateFetchPositions 方法。
1.1.1 ConsumerCoordinator#poll
public boolean poll(Timer timer) {
invokeCompletedOffsetCommitCallbacks(); // @1
if (subscriptions.partitionsAutoAssigned()) { // @2
pollHeartbeat(timer.currentTimeMs()); // @21
if (coordinatorUnknown() && !ensureCoordinatorReady(timer)) { //@22
return false;
}
if (rejoinNeededOrPending()) { // @23
if (subscriptions.hasPatternSubscription()) { // @231
if (this.metadata.timeToAllowUpdate(time.milliseconds()) == 0) {
this.metadata.requestUpdate();
}
if (!client.ensureFreshMetadata(timer)) {
return false;
}
}
if (!ensureActiveGroup(timer)) { // @232
return false;
}
}
} else { // @3
if (metadata.updateRequested() && !client.hasReadyNodes(timer.currentTimeMs())) {
client.awaitMetadataUpdate(timer);
}
}
maybeAutoCommitOffsetsAsync(timer.currentTimeMs()); // @4
return true;
}
代码@1:执行已完成的 offset (消费进度)提交请求的回调函数。
代码@2:队列负载算法为自动分配(即 Kafka 根据消费者个数与分区书动态负载分区)的相关的处理逻辑。其实现关键点如下:
-
代码@21:更新发送心跳相关的时间,例如heartbeatTimer、sessionTimer、pollTimer 分别代表发送最新发送心跳的时间、