背景

由于项目上Flink在设置parallel多于1的情况下,job没法正确地获取watermark,所以周末来研究一下一部分,大概已经锁定了原因:

虽然我们的topic只设置了1的partition,但是Kafka的Comsumer还是起了好几个subtask去读索引是2、3的partition,然后这几个subtask的watermark一直不更新,导致我们job整体的watermark一直是Long.MIN_VALUE。现在需要去了解一下subtask获取partition的流程,等上班的时候debug一遍应该就可以知道原因。

翻源码的过程

通过log找到分配partition的大概位置

从图中可以看到,在org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumerBase这个类中可以找到一些关键信息。

跟踪源码

往上翻翻,看有没有有用信息

关键源码,附上注释

	public void open(Configuration configuration) throws Exception {
// determine the offset commit mode
this.offsetCommitMode = OffsetCommitModes.fromConfiguration(
getIsAutoCommitEnabled(),
enableCommitOnCheckpoints,
((StreamingRuntimeContext) getRuntimeContext()).isCheckpointingEnabled()); // create the partition discoverer
this.partitionDiscoverer = createPartitionDiscoverer(
topicsDescriptor,
getRuntimeContext().getIndexOfThisSubtask(),
getRuntimeContext().getNumberOfParallelSubtasks());
this.partitionDiscoverer.open(); subscribedPartitionsToStartOffsets = new HashMap<>();
// 重点函数,这个函数或获取到subtask的所有partition。
final List<KafkaTopicPartition> allPartitions = partitionDiscoverer.discoverPartitions();
if (restoredState != null) {
...
} else {
// use the partition discoverer to fetch the initial seed partitions,
// and set their initial offsets depending on the startup mode.
// for SPECIFIC_OFFSETS and TIMESTAMP modes, we set the specific offsets now;
// for other modes (EARLIEST, LATEST, and GROUP_OFFSETS), the offset is lazily determined
// when the partition is actually read.
switch (startupMode) {
...
default:
for (KafkaTopicPartition seedPartition : allPartitions) {
subscribedPartitionsToStartOffsets.put(seedPartition, startupMode.getStateSentinel());
}
} if (!subscribedPartitionsToStartOffsets.isEmpty()) {
switch (startupMode) {
...
case GROUP_OFFSETS:
LOG.info("Consumer subtask {} will start reading the following {} partitions from the committed group offsets in Kafka: {}",
getRuntimeContext().getIndexOfThisSubtask(),
subscribedPartitionsToStartOffsets.size(),
subscribedPartitionsToStartOffsets.keySet());
}
} else {
LOG.info("Consumer subtask {} initially has no partitions to read from.",
getRuntimeContext().getIndexOfThisSubtask());
}
} public List<KafkaTopicPartition> discoverPartitions() throws WakeupException, ClosedException {
if (!closed && !wakeup) {
try {
List<KafkaTopicPartition> newDiscoveredPartitions; // (1) get all possible partitions, based on whether we are subscribed to fixed topics or a topic pattern
if (topicsDescriptor.isFixedTopics()) {
// 对于没有使用通配符的topic,直接获取topic的所有partition
newDiscoveredPartitions = getAllPartitionsForTopics(topicsDescriptor.getFixedTopics());
} else {
// 对于使用了通配符的topic, 先找到所有topic,再一一match
List<String> matchedTopics = getAllTopics(); // retain topics that match the pattern
Iterator<String> iter = matchedTopics.iterator();
while (iter.hasNext()) {
if (!topicsDescriptor.isMatchingTopic(iter.next())) {
iter.remove();
}
} if (matchedTopics.size() != 0) {
// get partitions only for matched topics
newDiscoveredPartitions = getAllPartitionsForTopics(matchedTopics);
} else {
newDiscoveredPartitions = null;
}
} // (2) eliminate partition that are old partitions or should not be subscribed by this subtask
if (newDiscoveredPartitions == null || newDiscoveredPartitions.isEmpty()) {
throw new RuntimeException("Unable to retrieve any partitions with KafkaTopicsDescriptor: " + topicsDescriptor);
} else {
Iterator<KafkaTopicPartition> iter = newDiscoveredPartitions.iterator();
KafkaTopicPartition nextPartition;
while (iter.hasNext()) {
nextPartition = iter.next();
// 只保留符合要求的partition,这就是我们要找的函数
if (!setAndCheckDiscoveredPartition(nextPartition)) {
iter.remove();
}
}
} return newDiscoveredPartitions;
}...
}...
} public boolean setAndCheckDiscoveredPartition(KafkaTopicPartition partition) {
if (isUndiscoveredPartition(partition)) {
discoveredPartitions.add(partition); // 在这
return KafkaTopicPartitionAssigner.assign(partition, numParallelSubtasks) == indexOfThisSubtask;
} return false;
} public static int assign(KafkaTopicPartition partition, int numParallelSubtasks) {
// 先算出此topic的hash(partition.getTopic().hashCode() * 31),这里不知道为什么不直接用hash,还要再*31,然后取正数(& 0x7FFFFFFF),最后获取到此topic的起始位置。
int startIndex = ((partition.getTopic().hashCode() * 31) & 0x7FFFFFFF) % numParallelSubtasks; // here, the assumption is that the id of Kafka partitions are always ascending
// starting from 0, and therefore can be used directly as the offset clockwise from the start index
// 计算当前的partition应该属于哪个subtask。例如:一共有20个subtask,算出来的起始位置是5,partition是5,那么最后就是
// (5 + 5) % 20 = 10, 这个partition应该分给10号subtask。
return (startIndex + partition.getPartition()) % numParallelSubtasks;
}

思考

某topic的每个partition会分给哪个subtask其实是确定的

topic名字是确定的 -> topic的hashCode是确定的 && subtask的数量是确定的 -> startIndex是确定的 -> 某partition会分给哪个subtask其实是确定的

为什么要算startIndex

大概是为了平均分配不同的topic,如果topic很多,每个topic都只从0开始,那么subtask 0,1,2之类的靠前subtask就需要读大量的partition。

Kafka源码研究--Comsumer获取partition下标的更多相关文章

  1. Apache Kafka源码分析 – Replica and Partition

    Replica 对于local replica, 需要记录highWatermarkValue,表示当前已经committed的数据对于remote replica,需要记录logEndOffsetV ...

  2. org.reflections 接口通过反射获取实现类源码研究

    org.reflections 接口通过反射获取实现类源码研究 版本 org.reflections reflections 0.9.12 Reflections通过扫描classpath,索引元数据 ...

  3. 【Kafka源码】Kafka代码模块

    Kafka源码依赖于Scala环境,首先需要安装scala,这块请自行百度进行安装. 传送门 当然,我们要分析源码,需要下载源码,请自行从github上面下载. 说明:本文使用的kafka版本为0.1 ...

  4. Kafka源码分析(三) - Server端 - 消息存储

    系列文章目录 https://zhuanlan.zhihu.com/p/367683572 目录 系列文章目录 一. 业务模型 1.1 概念梳理 1.2 文件分析 1.2.1 数据目录 1.2.2 . ...

  5. kafka源码分析之一server启动分析

    0. 关键概念 关键概念 Concepts Function Topic 用于划分Message的逻辑概念,一个Topic可以分布在多个Broker上. Partition 是Kafka中横向扩展和一 ...

  6. Kakfa揭秘 Day3 Kafka源码概述

    Kakfa揭秘 Day3 Kafka源码概述 今天开始进入Kafka的源码,本次学习基于最新的0.10.0版本进行.由于之前在学习Spark过程中积累了很多的经验和思想,这些在kafka上是通用的. ...

  7. Kafka 源码剖析

    1.概述 在对Kafka使用层面掌握后,进一步提升分析其源码是极有必要的.纵观Kafka源码工程结构,不算太复杂,代码量也不算大.分析研究其实现细节难度不算太大.今天笔者给大家分析的是其核心处理模块, ...

  8. Apache Kafka源码分析 – Broker Server

    1. Kafka.scala 在Kafka的main入口中startup KafkaServerStartable, 而KafkaServerStartable这是对KafkaServer的封装 1: ...

  9. Kafka源码系列之源码分析zookeeper在kafka的作用

    浪尖的kafka源码系列以kafka0.8.2.2源码为例给大家进行讲解的.纯属个人爱好,希望大家对不足之处批评指正. 一,zookeeper在分布式集群的作用 1,数据发布与订阅(配置中心) 发布与 ...

随机推荐

  1. zookeeper伪集群

    Zookeeper运行需要java环境,需要安装jdk,建议本地下载好需要的安装包然后上传到服务器上面,服务器上面下载速度太慢. 上传 [root@192 ~]# java -version java ...

  2. Django-官网查询部分翻译(1.11版本文档)-QuerySet-字段查找-06

    目录 Making queries 进行查询 创建一个对象(一条数据记录) 保存修改的表对象 保存外键字段或多对多字段(ForeignKey or ManyToManyField fields) Re ...

  3. 代码审计之SQL注入及修复

    在新手入门web安全的时候,sql注入往往是最先上手的一个漏洞,它也是危害相当大的一个漏洞,存在此漏洞的话,将有被脱裤的风险. 以下所有代码都是我自己写的,可能有不美观,代码错误等等问题,希望大家可以 ...

  4. Flume和Kafka完成实时数据的采集

    Flume和Kafka完成实时数据的采集 写在前面 Flume和Kafka在生产环境中,一般都是结合起来使用的.可以使用它们两者结合起来收集实时产生日志信息,这一点是很重要的.如果,你不了解flume ...

  5. CDH6.3.0 - Cloudera Enterprise 6 Release Guide 安装准备篇

    一.安装之前 Cloudera管理器的存储空间规划 ClouderaManager跟踪许多后台流程中的服务.作业和应用程序的指标.所有这些指标都需要存储.根据组织的大小,此存储可以是本地的或远程的,基 ...

  6. 05、Linux通配符、转义字符、环境变量

    问题:作为Linux运维人员,我们有时候也会遇到明明一个文件的名称就在嘴边但就是想不起来的情况.如果就记得一个文件的开头几个字母,想遍历查找出所有以这个关键词开头的文件,该怎么操作呢? 范例:单个查看 ...

  7. C# 反射Reflection——反射反射程序员的快乐

    一.什么是反射 反射Reflection:System.Reflection,是.Net Framework提供的一个帮助类库,可以读取并使用metadata. 反射是无处不在的,MVC-Asp.Ne ...

  8. springboot值mybatis 别名等配置

    在application配置文件中添加如下: mybatis: #该配置替换在sql-config-map中的typeAliases配置 type-aliases-package: com.ww.ww ...

  9. 【ADO.NET基础知识】SqlConnection、command、DataSet 、DataTable、dataAdapter

    1.使用Connection连接数据库的步骤: (1).添加命名空间 System.Data.SqlClient(注意:初学者经常会忘记) (2)定义连接字符串.连接SQL Server 数据库时: ...

  10. python爬虫——京东评论、jieba分词、wordcloud词云统计

    接上一章,动态页面抓取——抓取京东评论区内容. url=‘https://club.jd.com/comment/productPageComments.action?callback=fetchJS ...