1 数据结构

消费者的消费状态是保存在SubscriptionState类中的,而SubscriptionState有个重要的属性那就是assignment保存了消费者消费的partition及其partition的状态

public class SubscriptionState {

    /* the pattern user has requested */
private Pattern subscribedPattern; /* the list of topics the user has requested */
private final Set<String> subscription; /* the list of topics the group has subscribed to (set only for the leader on join group completion) */
private final Set<String> groupSubscription; /* the list of partitions the user has requested */
private final Set<TopicPartition> userAssignment; /* the list of partitions currently assigned */
private final Map<TopicPartition, TopicPartitionState> assignment; // 关键, 保存了消费者消费的partition及其partition的状态 // ...

看下TopicPartitionState。TopicPartitionState用于表示消费者消费到该partition哪个位置了,需要注意的是position表示下一条需要消费的位置而不是已经消费的位置,拉取消息的时候就是根据position来确定需要拉取的第一条消息的offset

private static class TopicPartitionState {
private Long position; // 下一条消费哪个offset
private OffsetAndMetadata committed; // 已经提交的position
private boolean paused; // whether this partition has been paused by the user
private OffsetResetStrategy resetStrategy; // 重置position的时候的策略 // ...
} public class OffsetAndMetadata implements Serializable {
private final long offset;
private final String metadata;
}

2 commit offset

以KafkaConsumer#commitSync为例来看下客户端是如何提交offset的

KafkaConsumer#commitSync

public void commitSync() {
acquire();
try {
commitSync(subscriptions.allConsumed()); // 调用SubscriptionState#allConsumed来获取已经消费的消息的位置,然后将其提交
} finally {
release();
}
} public void commitSync(final Map<TopicPartition, OffsetAndMetadata> offsets) {
acquire();
try {
coordinator.commitOffsetsSync(offsets);
} finally {
release();
}
}

2.1 获取已经消费的位置

来看下SubscriptionState#allConsumed,从哪获取到消费到的位置。从下面的代码可以看出提交的offset就是TopicPartitionState#position

public Map<TopicPartition, OffsetAndMetadata> allConsumed() {
Map<TopicPartition, OffsetAndMetadata> allConsumed = new HashMap<>();
for (Map.Entry<TopicPartition, TopicPartitionState> entry : assignment.entrySet()) {
TopicPartitionState state = entry.getValue();
if (state.hasValidPosition())
allConsumed.put(entry.getKey(), new OffsetAndMetadata(state.position));// 关键,原来是将TopicPartitionState中的position封装成OffsetAndMetadata,即提交的是TopicPartitionState#position
}
return allConsumed;
}

2.2 发送到网络

获取到消费到的offset位置后,最终是通过ConsumerCoordinator#sendOffsetCommitRequest将offset发送到coordinator的


private RequestFuture<Void> sendOffsetCommitRequest(final Map<TopicPartition, OffsetAndMetadata> offsets) {
if (coordinatorUnknown()) // 必须获取coordinator
return RequestFuture.coordinatorNotAvailable(); if (offsets.isEmpty())
return RequestFuture.voidSuccess(); // create the offset commit request
Map<TopicPartition, OffsetCommitRequest.PartitionData> offsetData = new HashMap<>(offsets.size());
for (Map.Entry<TopicPartition, OffsetAndMetadata> entry : offsets.entrySet()) {
OffsetAndMetadata offsetAndMetadata = entry.getValue();
offsetData.put(entry.getKey(), new OffsetCommitRequest.PartitionData(
offsetAndMetadata.offset(), offsetAndMetadata.metadata())); // 以TopicPartition为key, offsetAndMetadat组成request中的数据
} OffsetCommitRequest req = new OffsetCommitRequest(this.groupId,
this.generation,
this.memberId,
OffsetCommitRequest.DEFAULT_RETENTION_TIME,
offsetData); log.trace("Sending offset-commit request with {} to coordinator {} for group {}", offsets, coordinator, groupId); return client.send(coordinator, ApiKeys.OFFSET_COMMIT, req)
.compose(new OffsetCommitResponseHandler(offsets));// 发送到coordinator
}

2.3 处理response

从上面代码最后一行可以看出处理response的逻辑在OffsetCommitResponseHandler中。如果提交成功,那么会将TopicPartitionState#position更新到TopicPartitionState#commit

private class OffsetCommitResponseHandler extends CoordinatorResponseHandler<OffsetCommitResponse, Void> {

        private final Map<TopicPartition, OffsetAndMetadata> offsets;

        public OffsetCommitResponseHandler(Map<TopicPartition, OffsetAndMetadata> offsets) {
this.offsets = offsets;
} @Override
public OffsetCommitResponse parse(ClientResponse response) {
return new OffsetCommitResponse(response.responseBody());
} @Override
public void handle(OffsetCommitResponse commitResponse, RequestFuture<Void> future) {
sensors.commitLatency.record(response.requestLatencyMs());
Set<String> unauthorizedTopics = new HashSet<>(); for (Map.Entry<TopicPartition, Short> entry : commitResponse.responseData().entrySet()) {
TopicPartition tp = entry.getKey();
OffsetAndMetadata offsetAndMetadata = this.offsets.get(tp); // this.offsets即sendOffsetCommitRequest中的入参,这点很关键
long offset = offsetAndMetadata.offset(); Errors error = Errors.forCode(entry.getValue());
if (error == Errors.NONE) {
if (subscriptions.isAssigned(tp))
subscriptions.committed(tp, offsetAndMetadata); // 更新TopicPartitionState#committed为发送的时候的TopicPartitionState#position
}
// ...
}
}
}

3 总结

  1. 下一条要消费的消息的offset就是TopicPartitionState#position
  2. 提交offset的时候即将TopicPartitionState#position发送到coordinator
  3. 提交成功后则将TopicPartitionState#committed更新为TopicPartitionState#position

consumer提交offset原理的更多相关文章

  1. Kafka提交offset机制

    在kafka的消费者中,有一个非常关键的机制,那就是offset机制.它使得Kafka在消费的过程中即使挂了或者引发再均衡问题重新分配Partation,当下次重新恢复消费时仍然可以知道从哪里开始消费 ...

  2. 关于SpringKafka消费者的几个监听器:[一次处理单条消息和一次处理一批消息]以及[自动提交offset和手动提交offset]

    自己在使用Spring Kafka 的消费者消费消息的时候的实践总结: 接口 KafkaDataListener 是spring-kafka提供的一个供消费者接受消息的顶层接口,也是一个空接口; pu ...

  3. spring-kafka手动提交offset

    <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.sp ...

  4. kafka消费端提交offset的方式

    Kafka 提供了 3 种提交 offset 的方式 自动提交 复制 1234 consumer.commitSync(); 手动异步提交 offset 复制 1 consumer.commitAsy ...

  5. Spring-Kafka —— 消费后不提交offset情况的分析总结

    最近在使用kafka,过程中遇到了一些疑问,在查阅了一些资料和相关blog之后,关于手动提交offset的问题,做一下总结和记录. 消费端手动提交offset代码如下: /** * 这是手动提交的消费 ...

  6. kafka consumer 自动提交 offset

    org.apache.kafka.clients.consumer.KafkaConsumer#pollOnce private Map<TopicPartition, List<Cons ...

  7. Kafka配置项unclean.leader.election.enable造成consumer出现offset重置现象

    消费端出现offset重置为latest, earliest现象,类似log: (org.apache.kafka.clients.consumer.internals.Fetcher.handleF ...

  8. kafka consumer 指定 offset,进行消息回溯

    kafka consumer 如何根据 offset,进行消息回溯?下面的文档给出了 demo: https://cwiki.apache.org/confluence/display/KAFKA/0 ...

  9. spark提交运算原理

    前面几天元旦过high了,博客也停了一两天,哈哈,今天我们重新开始,今天我们介绍的是spark的原理 首先先说一个小贴士: spark中,对于var count = 0,如果想使count自增,我们不 ...

随机推荐

  1. 学习Spring5必知必会(4)~使用注解配置、使用java代码配置

    ● 注意:使用注解并不能完全取代xml配置,比如配置连接池DruidDataSource,我们就不能到这个类中去贴注解. 想要实现零配置(完全不使用xml进行配置):javaConfig + 注解 ● ...

  2. BI报表系统该如何集成到其他系统呢?

    近期小麦我经常收到很多用户的反馈,想知道Smartbi的报表能不能从微信/钉钉之类的直接跳转到已做好的报表页面?他们都希望通过这种方式尽可能地避免由于各个管理软件账号密码不同而造成的不便,能够在日常工 ...

  3. 【C# 基础概念】抽象类、密封类及类成员

    使用 abstract 关键字可以创建不完整且必须在派生类中实现的类和 class 成员. 使用 sealed 关键字可以防止继承以前标记为 virtual 的类或某些类成员. abstract修饰符 ...

  4. 抛弃模板,一种Prompt Learning用于命名实体识别任务的新范式

    原创作者 | 王翔 论文名称: Template-free Prompt Tuning for Few-shot NER 文献链接: https://arxiv.org/abs/2109.13532 ...

  5. Python:matplotlib.pyplot

    翻译总结自:matplotlib.pyplot - Matplotlib 3.4.3 documentation 函数 说明 acorr x的自相关性图 angle_spectrum 角度谱 anno ...

  6. python 镜像仓库获取最新版本号

    #/bin/python# -*- coding: utf-8 -*-import requestsfrom urllib import parsefrom requests.auth import ...

  7. Laravel7-验证器使用

  8. mysql 获取当前时间和时间戳

    mysql 获取当前时间为select now()运行结果: 2012-09-05 17:24:15 mysql 获取当前时间戳为select unix_timestamp(now()) 运行结果:u ...

  9. 谈谈对mvc 的认识?

    由模型(model),视图(view),控制器(controller)完成的应用程序由模型发出要实现的功能到控制器,控制器接收组织功能传递给视图;MVC 是一个设计模式,它强制性的使应用程序的输入.处 ...

  10. php 23种设计模式 - 解释器模式

    给定一个语言, 定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子.角色:环境角色(PlayContent):定义解释规则的全局信息.抽象解释器(Empress):定义了部 ...