kafka的一个有趣问题(BUG)

这是我的第104篇原创文章

问题由来

在使用kafka时,创建topic,对某个topic进行扩分区的操作,想必大家肯定都使用过。尤其是集群进行扩容时,对流量较大的topic进行扩分区操作。一般而言,期望的效果是:新扩的分区分布到新的broker节点上,这样才能达到均衡流量分摊broker压力。

然而,我们在实际使用过程中发现,在一种场景下,原生的代码逻辑中,对topic分区进行扩容后,新增的分区并没有分配到新的节点上,即新增的分区和老的分区仍旧位于扩容前的broker节点上,导致这个节点压力反而变大,引发生产者发送超时等问题,本文就这个问题进行分析。

问题产生的步骤

我们先来看下问题出现的操作步骤与现象,在具有3个broker的集群环境中,分别通过自带脚本(kafka-topics.sh)和adminClient的接口创建一个分区数为2、副本因子为1的topic,然后再扩容到3分区,此后查看topic分区的分布情况。

通过接口操作的情况如下所示:

d8780c79b0bfb24f3e7291d59a422c2d.jpeg

通过脚本操作的情况如下所示:

eed3a58c66ef978db672d56d153d9548.jpeg

从图中可以直观的看到通过接口创建的topic,在扩容分区后,新增的2号分区的leader与老的1号分区的leader位于同一个broker节点上,而不是像脚本操作的一样,3个分区的leader分别位于3个broker节点上。

两种操作方式的区别

由于扩分区的操作都是一样的,那为什么不同的创建的方式,在进行扩容后就有了不同的结果呢?我们还是先来看下两种创建topic方式的逻辑。

对于通过脚本创建topic的处理逻辑为:

  • 通过zk客户端获取当前集群的所有broker

  • 根据topic创建的参数,进行topic分区副本的分配

  • 通过zk客户端,将topic分区副本信息写入zookeeper

  • kafka broker的controller监听zk并感知其变化,然后根据分区副本的分布情况向对应broker发送请求完成topic分区的创建

关键代码如下所示:

8dc71473249a094612bb320c8f4b03b0.jpeg

对于通过接口创建topic的处理逻辑为:

  • 客户端向broker发送CREATE_TOPIC的请求

  • broker接收到请求后,从元数据缓存中获取当前集群的所有broker

  • 根据topic创建的参数,进行topic分区副本的分配

  • 通过zk客户端,将topic分区副本信息写入zookeeper

  • kafka broker的controller监听zk并感知其变化,然后根据分区副本的分布情况向对应broker发送请求完成topic分区的创建

两种方式本质上的处理都是一样的,即进行topic分区副本的分配,并将这个信息写入zookeeper,然后由controller完成真正的创建逻辑。只不过一个是在客户端侧完成,一个是向broker发送请求,在broker侧完成。

分区副本分配逻辑

既然两种创建topic的方式,其处理逻辑是一样的,那怎么进行扩分区操作后,就不一样了呢?我们再来看下扩分区的处理逻辑。其流程其实和创建topic是一样的,先获取集群所有broker的信息,然后对新增的分区进行broker的分配,最后将完整信息写入zookeeper,broker的controller监听感知信息的变化后,向对应的broker节点发送请求完成分区的新增动作。

值得注意的是:这里对新增分区进行broker分配时,与创建topic时分区的broker分配,调用的是同一个方法:AdminUtils的assignReplicasToBrokers方法,我们来仔细分析下这个函数。

private def assignReplicasToBrokersRackUnaware(// 需要分配的分区个数nPartitions: Int,// 副本因子replicationFactor: Int,// 集群broker列表brokerList: Seq[Int],// 分配的起始IDfixedStartIndex: Int,// 起始分区IDstartPartitionId: Int): Map[Int, Seq[Int]] = {val ret = mutable.Map[Int, Seq[Int]]()val brokerArray = brokerList.toArray// 如果起始ID非0, 则以传入的为准, 否则从broker集群中随机挑选一个作为起始分配val startIndex = if (fixedStartIndex >= 0) fixedStartIndex else rand.nextInt(brokerArray.length)// 起始分配的分区ID// 对于创建topic而言, startPartitionId的值为-1, 因此分区是从0开始分配// 而对于新增分区而言, startPartitionId为当前topic实际分区的个数, 因此已存在的分区是不会再次分配的var currentPartitionId = math.max(0, startPartitionId)var nextReplicaShift = if (fixedStartIndex >= 0) fixedStartIndex else rand.nextInt(brokerArray.length)// 分区副本分配for (_ <- 0 until nPartitions) {if (currentPartitionId > 0 && (currentPartitionId % brokerArray.length == 0))nextReplicaShift += 1// 分区首个副本的 brokerIDval firstReplicaIndex = (currentPartitionId + startIndex) % brokerArray.lengthval replicaBuffer = mutable.ArrayBuffer(brokerArray(firstReplicaIndex))for (j <- 0 until replicationFactor - 1)replicaBuffer += brokerArray(replicaIndex(firstReplicaIndex, nextReplicaShift, j, brokerArray.length))// 新分区的副本情况ret.put(currentPartitionId, replicaBuffer)currentPartitionId += 1}ret
}

这个函数为了能同时适用于创建topic和新增topic分区,巧妙的利用了fixedStartIndexstartPartitionId两个参数。

创建topic调用时,fixedStartIndex的值为 -1,即随机挑选一个broker作为起始分配,startPartitionId的值也为-1,因此分区副本是从0开始分配。在分配的时候,随分区的递增,brokerID也进行循环递增,这样可以保证分区副本是尽可能均匀分布在broker中的。

而扩分区调用时,fixedStartIndex为当前topic首个分区副本的brokerID,startPartitionId为当前topic实际分区的个数,这样一来,已经存在的分区不会再次进行分配,即仅对新增的分区进行分配;同时结合fixedStartIndex字段,保证分区分配逻辑是延续创建topic时的分配逻辑,达到分区副本的在broker集群中均衡分布的效果。

举个例子来说明下:在有5个节点的集群中,创建分区数为2、副本因子为1的topic,假设首个分区随机挑选的brokerID为3,那么分区副本的分配结果为:"{0,[3]},{1,[4]}";此后将分区数扩到5个,根据上面的代码分析,fixedStartIndex参数的值为3、startPartitionId的值为2,这样,这个函数得到的结果就是"{2,[0]},{3,[1]},{4,[2]}"。

罪魁祸首

从上面的分析来看,逻辑上都没有问题,但为什么就出现了不符合预期的现象呢?通过断点调试分析,我们发现了一个细节。

在用脚本创建topic和扩分区时,assignReplicasToBrokers函数的入参brokerList都是有序排列的

4d0620c3e6d77fada42b026dc02b57bf.jpeg

从代码中也可以证实这一点。

98c4086223fd692a02a6f0979081d963.jpeg

然而,通过接口创建topic时,入参的brokerList是无序的

6dc2a401f4f5e771ef24ae9b4a040f8c.jpeg

这样一来,前后两个操作的顺序不一致也就导致出现了这个问题。

回头再看文章开头的那个图,其实可以看出端倪,通过接口创建的topic的两个分区分布为:

分区号副本所在的brokerID
01
10

从代码可以反推出,这里传入的broker列表是无序的:[1,0,2],扩分区时,fixedStartIndex的值为1,startPartitionId为2,同时这里传入的broker列表又是有序的,即[0,1,2],根据分配逻辑取broker数组的第 (1+2)%3=0位,即0,这样分区2就又分配到0号broker上了。

从上面断点的图中可以看到,broker侧在处理创建topic的请求时,brokerList是直接从元数据缓存中获取的,而这个元数据缓存是根据controller来更新的。

从代码逻辑可以分析出:controller被选举出来后首次初始化时触发的元数据更新,无法保证broker的有序,同样,随着broker可能的上下线引起的元数据更新也无法保证broker的有序(这里就不再贴相关的代码了,感兴趣的可以自行看下源码)。

小结

本文通过分析集群扩容,同时对topic进行扩分区操作,新扩的分区没有分布到新的broker节点上这一问题现象进行分析,最后发现是由于创建topic时broker列表没有按ID排序,而扩分区操作时broker列表又是按ID排序,两次操作时的broker列表顺序不一致导致出现该问题。

另外,在分析过程中,我们发现kafka-topics.sh脚本可以是指定--zookeeper参数,或者是--bootstrap-server参数。如果指定--bootstrap-server参数的话,等同于通过调用接口完成相关逻辑,也就是会遇到上面提到的问题。

好了,这就是本文的全部内容,如果觉得本文对您有帮助,请点赞+转发,如果觉得有不正确的地方,欢迎留言交流~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/407552.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Qt】常用控件QPushButton

常用控件QPushButton QWidget中涉及的各种属性/函数/使用方法&#xff0c;对Qt中的各种控件都是有效的。 QPushButton继承自QAbstractButton。这个类是抽象类&#xff0c;是其他按钮的父类。 QAbstractButton中和QPushButton相关性比较大的属性。 属性说明 text 按钮中的⽂本…

TCP/UDP的对比,粘包分包抓包,http协议

服务器端&#xff1a; 一、loop 127.0.0.1本地回环测试地址 二、tcp特点 面向连接、可靠传输、字节流 粘包问题&#xff1a;tcp流式套接字&#xff0c;数据与数据之间没有套接字&#xff0c;导致可能多次的数据粘到一起 解决方法&#xff1a;&#xff08;1&#xff09;规…

后端Java秋招面试中的自我介绍需要说什么?

本文主要面向校招/实习面试中求职后端开发岗位的同学&#xff0c;其他岗位/社招的同学也可以参考&#xff0c;道理都是相通的 1 背景 1.1为什么要认真准备自我介绍&#xff1f; 1. 必要性&#xff1a;在求职面试中&#xff0c;一般来说同学和面试官打过招呼之后第一项就是自…

html+css+js网页设计 电商 珠宝首饰电商3个页面

htmlcssjs网页设计 电商 珠宝首饰电商3个页面 网页作品代码简单&#xff0c;可使用任意HTML编辑软件&#xff08;如&#xff1a;Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及修改编辑等操作&#xff09;。 获取源码 1…

网络编程知识点总结

物理链路网络运输会话表示应用 物链网运会表应 实际的数据帧 TCP和UDP的异同&#xff08;笔试面试&#xff09; 主机&#xff1a;host 转换&#xff1a;to 网络&#xff1a;network uint32_t htonl(uint32_t hostlong); //将4字节无符号整数的主机字节序转换为网络字节序&a…

掌握语义内核(Semantic Kernel):如何使用Memories增强人工智能应用

随着人工智能领域的不断发展&#xff0c;语义内核&#xff08;Semantic Kernel&#xff09;的概念应运而生&#xff0c;为我们处理和理解庞大的数据集提供了新的视角。今天&#xff0c;我们将聚焦于语义内核中的一个核心概念——Memories&#xff0c;它是如何使我们的数据查询更…

QT中使用QAxObject类读取xlsx文件内容并显示在ui界面

一、源码 #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow>QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACEclass MainWindow : public QMainWindow {Q_OBJECTpublic:MainWindow(QWidget *parent nullptr);~MainWindow();pr…

鸿蒙内核源码分析(共享内存) | 进程间最快通讯方式

运行机制 共享好端端的一词&#xff0c;近些年被玩坏了&#xff0c;共享单车,共享充电宝,共享办公室&#xff0c;共享雨伞… 甚至还有共享女朋友&#xff0c;真是人有多大胆&#xff0c;共享有多大产。但凡事太尽就容易恶心到人&#xff0c;自己也一度被 共享内存 恶心到了&am…

看图学sql之sql中的子查询

&#xfeff;&#xfeff; &#xfeff;where子句子查询 语法&#xff1a; SELECT column_name [, column_name ] FROM table1 [, table2 ] WHERE column_name OPERATOR(SELECT column_name [, column_name ]FROM table1 [, table2 ][WHERE]) 子查询需要放在括号( )内。O…

解决git checkout -b 拉取远端某分支到本地时报错

问题描述 日常开发场景中&#xff0c;经常会出现切分支的情况&#xff0c;所以git checkout 命令是非常高频的 git checkout -b feature/xxx默认情况下&#xff0c;这条命令是基于当前所在分支来开辟新分支feature/xxx 但是&#xff0c;还有一些情况&#xff0c;我们需要基于…

appium下载及安装

下载地址&#xff1a;https://github.com/appium/appium-desktop/releases 双击安装就可以

基于UE5和ROS2的激光雷达+深度RGBD相机小车的仿真指南(三)---创建自定义激光雷达Componet组件

前言 本系列教程旨在使用UE5配置一个具备激光雷达深度摄像机的仿真小车&#xff0c;并使用通过跨平台的方式进行ROS2和UE5仿真的通讯&#xff0c;达到小车自主导航的目的。本教程默认有ROS2导航及其gazebo仿真相关方面基础&#xff0c;Nav2相关的学习教程可以参考本人的其他博…

数学建模学习(115):主成分分析(PCA)与Python实践

文章目录 一.主成分分析简介1.1 数学背景与维度诅咒1.2 PCA的定义与应用二.协方差矩阵——特征值和特征向量三.如何为数据集选择主成分数量四.特征提取方法五.LDA——与PCA的区别六.PCA的应用七.PCA在异常检测中的应用八.总结一.主成分分析简介 1.1 数学背景与维度诅咒 主成成…

视频智能分析平台烟火检测视频安防监控烟火算法识别应用方案

烟火检测算法的应用方案主要围绕其核心技术——深度学习&#xff08;特别是卷积神经网络CNN&#xff09;和计算机视觉技术展开&#xff0c;旨在实现对监控视频中的烟雾和火焰进行实时、准确的检测与识别。以下是一个详细的烟火检测算法应用方案&#xff1a; 一、技术原理 烟火…

高并发集群饿了么后端的登录模块

高并发集群饿了么后端的登录模块 1.数据库 非交互式python&#xff1a; 非交互式: 2.数据库的负载均衡&#xff1a;阿里巴巴的mycat 修改配置文件 /usr/local/mycat/conf/server.xml :对外的账号 密码 数据库 /usr/local/mycat/conf/schema.xml 如果出现启动异常&…

【微信小程序】自定义组件 - 数据监听器

1. 什么是数据监听器 2. 数据监听器的基本用法 组件的 UI 结构如下&#xff1a; 组件的 .js 文件代码如下&#xff1a; 3. 监听对象属性的变化 数据监听器 - 案例 案例效果 2. 渲染 UI 结构 3. 定义 button 的事件处理函数 4. 监听对象中指定属性的变化 5. 监听对象中所…

readpaper在读论文时候的默认规定

红色代表主旨思想 蓝色代表专业名词解析

【MySQL】 黑马 MySQL进阶 笔记

文章目录 存储引擎MySQL的体系结构存储引擎概念存储引擎特点InnoDBMyISAMMemory 存储引擎选择 索引概述结构B Tree(多路平衡查找树)B TreeHash为什么InnoDB存储引擎选择使用Btree索引结构? 分类思考题 语法SQL性能分析&#xff08;索引相关&#xff09;SQL执行频率慢查询日志p…

XSS game复现(DOM型)

目录 1.Ma Spaghet! 2.Jefff 3.Ugandan Knuckles 4.Ricardo Milos 5.Ah Thats Hawt 6.Ligma 7.Mafia 8.Ok, Boomer 1.Ma Spaghet! 通过简单的尝试发现传递参数可以直接进入h2标签 接下来我们尝试传入一个alert(1) 可以看到并没有触发。原因是在innerHTML中官方禁用了sc…

MySQL InnoDB引擎四大特性ACID实现方案分析

文章目录 概要InnoDb引擎ACID模型的实现方案小结 概要 对于Mysql&#xff0c;事物的支撑并不依赖于Server层&#xff0c;不同的存储引擎对于事物的支持也不一样&#xff0c;对于我们常用的InnoDB引擎&#xff0c;其提供了一套基于【ACID模型】的事物完整的解决方案。为什么MyIS…