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. vue中按需引入Element-ui

    安装 1.安装element-ui:npm i element-ui -S. 2.安装babel-plugin-component:npm install babel-plugin-component ...

  2. VS编译时,出现无法将文件“obj\Debug\*.exe”复制到“bin\Debug\*.exe”。文件“bin\Debug\*.exe”正由另一进程使用,因此该进程无法访问此文件。

    重命名将MyThread.exe 重命名 一下其他名字后就可以了.

  3. 在不受支持的 Mac 上安装 macOS Monterey 12(OpenCore Patcher)

    一.介绍 本文通用于 macOS Big Sur 和 macOS Monterey,也可以视作笔者 早期文章 的升级版. 这一章节将介绍 macOS Monterey 的系统要求和不受支持的 Mac ...

  4. Python:使用piecewise与curve_fit进行三段拟合

    x = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ,11, 12, 13, 14, 15,16,17,18,19,20,21], dtype=float) y = ...

  5. java-排查

    https://www.chinacion.cn/article/4271.html https://blog.csdn.net/zhengwei223/article/details/7715122 ...

  6. 超好用的Markdown编辑器Typora中的常见语法

    目录 下载网址 安装 一.标题 一级标题 二级标题 三级标题 四级标题 五级标题 六级标题 二.语法环境 三.单选 四.字体 五.分割符 六.列表 七.图片引入 八.表格 九.超链接 下载网址 正版中 ...

  7. LeetCode-033-搜索旋转排序数组

    搜索旋转排序数组 题目描述:整数数组 nums 按升序排列,数组中的值 互不相同 . 在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行 ...

  8. DFT/FFT/NTT

    在Seal库和HElib库中都用到了NTT技术,用于加快多项式计算,而NTT又是FFT的优化,FFT又来自于DFT,现在具体学习一下这三个技术! 基础概念 名词区分 1.DFT:离散傅立叶变换 2.F ...

  9. Python 从底层结构聊 Beautiful Soup 4(内置豆瓣最新电影排行榜爬取案例)

    1. 前言 什么是 Beautiful Soup 4 ? Beautiful Soup 4(简称 BS4,后面的 4 表示最新版本)是一个 Python 第三方库,具有解析 HTML 页面的功能,爬虫 ...

  10. mysql使用group by分组时出现错误ERROR 1055 (42000): Expression #1 of SELECT list is not in GROUP BY clause and

    问题: 1055 - Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column ...