一.前言
kafka是一个分布式的,支持多分区、多副本,基于 Zookeeper 的分布式消息流平台,它同时也是一款开源的基于发布订阅模式的消息引擎系统。
博主所在的部门就是使用kafka做消息中间件,前不久碰到了一个奇奇怪怪的bug,找问题还花了不少功夫,特此在这记录一下。
二.bug起源
我所负责的模块里面有一个功能是修改宿主机的网络ip。
功能页面如下
输入IPV4地址后,就会将服务所在的宿主机地址给修改掉。
先说一下背景:
测试环境所有服务均布在一台机器上,修改ip之后需要同步修改nacos配置中心的配置,各个中间件读取系统环境变量的配置等。然后整机重启,重启完成后,自动启动各个业务应用与中间件的docker容器。
从需求上来看,逻辑实现比较简单,java程序调用shell脚本去做一些宿主机上的操作,然后重启机器就好了。
功能代码咔咔一顿写。
写完之后一测试,ifconfig看一下ip已经修改,docker ps看一下容器都正常启动了,前端页面简单测试一些功能,都正常。
美滋滋啊,我果然是一个代码天才~
提测后,测试小姐姐给我说系统的操作记录一直没有生成,操作记录都是统一拦截发送到kafka,由专门的日志模块去消费记录到es。
淦!这功能也是最近刚开发的,刚刚改完一轮bug,自测过没有问题了。看了看代码,最后一次这个模块的提交记录也是我。
奇怪了。
看一下后台的日志,疯狂打印WARN日志
代码语言:javascript复制1 partitions have leader brokers without a matching listener...
我就进到kafka的容器里面看了一下服务的消费情况
代码语言:javascript复制kafka-consumer-groups.sh --bootstrap-server 127.0.0.1:9092 --group baiyan-topic --describe
看到消费情况为比较滞后,再去看一下卡主的topic的数据是什么
代码语言:javascript复制kafka-console-consumer.sh --bootstrap-server 127.0.0.1:9092 --topic baiyan-topic --offset 373159 --partition 0
数据为
代码语言:javascript复制{省略业务字段,"createTime":"2021-10-20 16:27:39.447","updateTime":"2021-10-20 16:27:39.447"}
what?
这个时间怎么好像跟我更新测试环境IP的时间这么接近?
大量的消息积压在这个时间点无法被consumer处理到!
三.找问题
3.1.google报错
为了确定这个bug就是修改ip导致的,我让测试小姐姐在另外一台机器上全量模拟安装了一套环境,我又去修改了一下ip,果然又出现了这个问题。
ok,到这里,我们掌握的bug来源或者解决问题的入口就只有两个。
1.报错信息: 1 partitions have leader brokers without a matching listener 2.修改ip: 修改ip后,kafka消息堆积,无法消费,从提示信息来看是分区没有了lead节点导致。
好了,解决bug开始,第一步,google,哈哈哈哈~
在google一顿搜索:1 partitions have leader brokers without a matching listener
网上给出的答案大致集中在:
- kafka分区所在的服务器down了,rebalance失败。
- 防火墙没有关闭,导致网络不通。
- 代理ip配置不正确。
一个个看
第一点: 看了kafka的容器,启动正常,且如果新增一个topic,也能正常发送与消费,排除!
第二点: 防火墙未开启,哪怕开启了,端口也是通的,排除!
第三点: 从google来看,应该是这个答案了。
看一下配置文件的内容
代码语言:javascript复制cat /opt/kafka/config server.properties
与ip有关系的参数都很正常,已经修改成目标ip值,并且往上搜索到的配置都是注释的
我们使用的是比较早期的配置
代码语言:javascript复制advertised.host.name=目标ip
但是走投无路,我还是抱着试一试心态,按照网上的操作去修改了配置,重启。
结果跟预想的一样,完全没用。
3.2.切换ip入手
一顿google,最后搜索出来的也都是说kafka集群搭建配置与网络问题,与3.1的搜索内容基本一致。
放弃。
3.3.原理入手
当时尝试使用3.1与3.2这两个线索搜索到的信息去解决问题无果后,还是有点难受的。不知道从哪里入手了。
后来想了想,kafka与消费者直接的交互,kafka的集群信息等同步信息,都是维护在zookeeper上的。
好,一个个来,一个个看。
先看一下topic的情况
代码语言:javascript复制bash-4.4# kafka-topics.sh --zookeeper 127.0.0.1:2181 --topic baiyan-topic --describe
Topic: baiyan-topic PartitionCount: 1 ReplicationFactor: 1 Configs:
Topic: baiyan-topic Partition: 0 Leader: none Replicas: 1001 Isr: 1001
好了,这里发现一个端倪,leader为none,但是并没什么L用,从报错也能看出来。
继续到zookeeper的容器里面看一下kafka的信息
进入到bin目录
1.查看分区情况
代码语言:javascript复制ls /brokers/topics/baiyan-topic/partitions
[0]
2.查看broker的id情况
代码语言:javascript复制ls /brokers/ids
[1002]
至此,大家发现一些不一样的地方没有?
zookeeper上broker的id已经变成了1002
,但是kafka里面topic信息副本与Isr的节点信息还是1001
。
kafka需要从zookeeper上获取到broker的节点信息来构建集群,kafka无法在zookeeper上找到1002节点,因此leader为none,无法构建集群。
ok,至此已经知道了导致这个bug出现的原因是什么了~
3.4.原因剖析
3.3.我们已经知道了导致消息阻塞的原因是什么了。那么导致zk与kafka上broker信息不一致的原因是什么呢?
突破口其实也挺清晰的了,broker不一致,那就从决定brokerId生成的几个因素去看一下。
引用一下《深入理解kafka》作者朱小厮大佬的博客:Kafka参数broker.id详解
由上可知,决定brokerId主要来源于两个文件,server.properties与meta.properties
先看一下server.properties配置
-1则为自动分配,默认配置从1001开始起跳,符合kafka的topic信息为1001。
再看一下meta.properties
存在于server.properties的配置log.dirs目录下,查看配置
代码语言:javascript复制cluster.id=uHTKS_74RhW2_wKwbuwHxQ
version=0
broker.id=1002
好家伙,找到问题了!!!!
脚本修改ip后,修改了log.dirs,新生成了一个数据目录。重启kafka后,原先的topic内部的brokerid并未修改。而zk上,只要kafka节点下线了,1001节点数据被抹除,kafka重启后,新的log.dirs的数据目录生成。又因为server.properties上brokerId配置的是-1,由kakfa进行自增,从1001增加到1002,写入到的log.dirs目录下,并将1002节点注册到zk上,最终到了kafka与zk上互相不一致。
四.解决问题
知道了原因之后,解决问题的思路也就很清楚了嘛,只要保证ip修改后,新生成数据目录的brokerId与topic中brokerId保持一致即可。
方法一:
还原新的log.dirs目录下的meta.properties的broker.id属性为1001,在重启kafka,同步1001至zk。
太蠢了,治标不治本,集群模式下都不知道每个节点的id,需要人为介入。
方法二:
每个kafka节点指定server.properties内broker.id的值,不进行动态生成。
五.总结
本文分析kafka在宿主机修改了ip后,consumer无法消费到节点数据的问题。由浅入深的讲述了bug的排查过程。最终定位到broker.id不一致的bug。
我们因为测试环境是单机的,并没有指定broker.id,动态生成id导致了bug。
其实最终解决bug是比较简单的,改一下配置,重启就好了,但是排查过程比较艰辛。
这里回想起面试的时候应该也有不少的面试官会问kafka一些原理性的八股文,回头来想想也不是没有道理。
这里我强烈建议大家在使用的kafka的时候,不论是单节点的kafka还是kafka集群,都指定每个节点的id, 避免出现一些匪夷所思的bug。