文章目录
- 前言
- ConsumerCoordinator
- poll
- sendJoinGroupRequest
前言
本文主要解析ConsumerCoordinator这个模型对于消费者加入组、位移提交的底层原理。
Note:关于心跳线程这一部分,在下一篇文章中会解析。
ConsumerCoordinator
首先看下类的关系图。
来到它的父类 - AbstractCoordinator,看下它的有关注释说明。
AbstractCoordinator对一个消费者组中的消费者与Kafka服务端的协调者之间的交互进行管理。同时又指定了Kafka的组管理协议的四个操作:
组成员注册时,需要协调者提供它们的元数据。
协调者从组成员中选择一个作为leader。
leader收集组成员的元数据,分配状态。
每个成员收到leader分配的状态,开始处理。
而ConsumerCoordinator,继承了AbstractCoordinator相关逻辑,是消费者的协调者。
poll
主要处理消费者加入消费者组的相关逻辑、位移的自动提交。
public boolean poll(Timer timer) {
// 可能会更新元数据快照。
maybeUpdateSubscriptionMetadata();
// 触发OffsetCommitCallback#onComplete逻辑。
invokeCompletedOffsetCommitCallbacks();
// 如果订阅类型是根据主题自动分配分区,根据主题pattern自动分配分区两者之一。
if (subscriptions.partitionsAutoAssigned()) {
// 唤醒心跳线程。
pollHeartbeat(timer.currentTimeMs());
// 如果协调者未知 && 客户端无法与协调者节点连接就绪。
if (coordinatorUnknown() && !ensureCoordinatorReady(timer)) {
return false;
}
// 如果需要加入组 或者 加入组的请求还未完成。
if (rejoinNeededOrPending()) {
// 如果订阅类型是根据主题pattern自动分配分区
if (subscriptions.hasPatternSubscription()) {
if (this.metadata.timeToAllowUpdate(timer.currentTimeMs()) == 0) {
// 请求元数据更新
this.metadata.requestUpdate();
}
// 如果元数据更新失败
if (!client.ensureFreshMetadata(timer)) {
return false;
}
// 可能会更新元数据快照
maybeUpdateSubscriptionMetadata();
}
// 尝试加入组
if (!ensureActiveGroup(timer)) {
return false;
}
}
} else {
// 元数据需要更新 && 客户端与节点的连接状态没有处于就绪状态
if (metadata.updateRequested() && !client.hasReadyNodes(timer.currentTimeMs())) {
// 客户端进行元数据更新
client.awaitMetadataUpdate(timer);
}
}
// 提交位移
maybeAutoCommitOffsetsAsync(timer.currentTimeMs());
return true;
}
如果消费者元数据的更新版本号 > 元数据快照保存的版本号,那么就更新元数据快照。
触发队列中所有的OffsetCommitCompletion#invoke方法。
触发OffsetCommitCallback#onComplete逻辑。
唤醒心跳线程。
更新heartbeatTimer、sessionTimer、pollTimer保存的时间为当前时间。
heartbeatTimer的截止时间与当前时间的比较。判断是否可以发送心跳。
更新heartbeatTimer、sessionTimer、pollTimer保存的时间为当前时间。
重置pollTimer的截止时间。
检查协调者是否为null。
如果客户端连接协调者节点失败,标记协调者未知(coordinator为null)。
确保客户端与协调者节点连接就绪。
对于协调者节点,倾向于寻找服务端inflightRequests数量为0或者最少的节点。
需要加入组 或者 之前加入组的请求还未完成。
尝试让消费者加入组。
启动一个心跳线程。
启动心跳线程。
boolean joinGroupIfNeeded(final Timer timer) {
// 需要加入组 || 加入组的请求处理还未完成
while (rejoinNeededOrPending()) {
// 客户端不能成功连接协调者节点
if (!ensureCoordinatorReady(timer)) {
return false;
}
// 如果需要为加入组做些准备
if (needsJoinPrepare) {
// 进行加入组的准备工作
onJoinPrepare(generation.generationId, generation.memberId);
needsJoinPrepare = false;
}
// 发送一个加入组的请求
final RequestFuture<ByteBuffer> future = initiateJoinGroup();
// 委派NetworkClient进行底层的交互
client.poll(future, timer);
if (!future.isDone()) {
return false;
}
if (future.succeeded()) {
ByteBuffer memberAssignment = future.value().duplicate();
// 对加入组完成后的后续处理。
onJoinComplete(generation.generationId, generation.memberId, generation.protocol, memberAssignment);
// 重置joinFuture为null
resetJoinGroupFuture();
needsJoinPrepare = true;
} else {
resetJoinGroupFuture();
final RuntimeException exception = future.exception();
if (exception instanceof UnknownMemberIdException ||
exception instanceof RebalanceInProgressException ||
exception instanceof IllegalGenerationException ||
exception instanceof MemberIdRequiredException)
continue;
else if (!future.isRetriable())
throw exception;
timer.sleep(retryBackoffMs);
}
}
return true;
}
加入组的完整过程。
可能会自动提交位移。
触发ConsumerRebalanceListener#onPartitionsRevoked逻辑。
重置组订阅关系。
关闭心跳线程。
发送一个加入组的请求。(后续会详细分析)
添加监听器。
加入组成功或者失败的回调处理。
@Override
protected void onJoinComplete(int generation,
String memberId,
String assignmentStrategy,
ByteBuffer assignmentBuffer) {
// only the leader is responsible for monitoring for metadata changes (i.e. partition changes)
if (!isLeader)
assignmentSnapshot = null;
// 寻找指定名字的PartitionAssignor
PartitionAssignor assignor = lookupAssignor(assignmentStrategy);
if (assignor == null)
throw new IllegalStateException("Coordinator selected invalid assignment protocol: " + assignmentStrategy);
Assignment assignment = ConsumerProtocol.deserializeAssignment(assignmentBuffer);
// 如果实际分配的分区与保存的分配分区信息不匹配
if (!subscriptions.assignFromSubscribed(assignment.partitions())) {
// 处理实际分配的分区与保存的分配分区信息不匹配的情况
handleAssignmentMismatch(assignment);
return;
}
Set<TopicPartition> assignedPartitions = subscriptions.assignedPartitions();
// 更新订阅关系
maybeUpdateJoinedSubscription(assignedPartitions);
// 触发PartitionAssignor#onAssigment逻辑
assignor.onAssignment(assignment, generation);
// 如果开启了自动提交
if (autoCommitEnabled)
// 更新当前时间,重置截止时间
this.nextAutoCommitTimer.updateAndReset(autoCommitIntervalMs);
ConsumerRebalanceListener listener = subscriptions.rebalanceListener();
log.info("Setting newly assigned partitions: {}", Utils.join(assignedPartitions, ", "));
try {
// 触发监听器的onPartitionsAssigned逻辑
listener.onPartitionsAssigned(assignedPartitions);
} catch (WakeupException | InterruptException e) {
throw e;
} catch (Exception e) {
log.error("User provided listener {} failed on partition assignment", listener.getClass().getName(), e);
}
}
加入组完成之后的处理。
自动提交位移。
对所有消费过的位移进行提交,并且附加回调。
触发队列中存储的OffsetCommitCallback#onComplete逻辑。
提交位移。
发送位移提交请求。
如果成功提交,则触发拦截器的onCommit逻辑。
构造OffsetCommitRequest.Builder实例。
调用ConsumerNetworkClient进行发送。
使用OffsetCommitResponseHandler对响应进行处理。
如果服务端成功处理的话,客户端这里会触发RequestFutureListener#onSuccess逻辑。
sendJoinGroupRequest
前面的代码分析中,有提到发送一个加入组的请求。
接下来简单看下这段代码逻辑。
构造JoinGroupRequest。
委派NetworkClient发送这个请求。
对于后续的响应处理。这里只看下请求成功时的响应处理。
可见,根据角色的不同,分为了onJoinLeader和onJoinFollower两个处理逻辑。
首先看下onJoinLeader。
使用指定的PartitionAssignor分配新的分区分配策略。
构造SyncGroupRequest。
委派NetworkClient发送请求。
然后看下onJoinFollower。
构造SyncGroupRequest。
委派NetworkClient发送请求。