首页 / 资讯中心 / 文章详情

Kafka伪集群搭建实战:单机模拟3节点,搞定副本同步与故障转移

Kafka伪集群搭建实战:单机模拟3节点,搞定副本同步与故障转移 ★ FEATURED ARTICLE
做后端开发或者大数据方向的人早晚会碰到Kafka。Kafka大多同学是在虚拟机、单机上面学起来的用得最多的demo是单节点跑一个broker建个topic测试生产消费这当然没问题但一旦涉及到副本同步、消费组再平衡、故障转移这些真正有含金量的机制单节点直接失灵因为副本都没有你让谁来同步。要找两台以上的机器搭集群对很多开发同学或者小团队来说硬件和网络环境都不一定允许。Kafka伪集群就是解决这个矛盾的一台机器多个broker进程模拟出集群的各种行为。我这次在Linux开发机上完整搭建并验证了3节点伪集群整个部署过程、关键配置和踩坑记录都在下面直接照着做就行。1. 整体设计与方案拆解伪集群为什么可行1.1 伪集群的本质多个进程共享一台机器Kafka集群的最小单元是brokerbroker本质上就是一个JVM进程。真正决定集群身份的不是物理机器而是三样东西broker.id节点唯一标识、listeners对外服务端口、log.dirs数据存储目录。只要这三样各自独立一台Linux机器上同时跑三个broker进程在客户端视角看来就是一个标准的3节点Kafka集群。这就像一栋楼里住了三户人家各自有门牌号、各自有门户、各自有房间厨房和卫生间虽然共享但每户的隐私是隔离的。Kafka客户端只需要知道这里有三户并不会关心它们在不在同一栋楼里。理解了这一点你就明白伪集群并不是什么黑魔法它只是在进程级隔离层面模拟出了集群该有的身份和通信关系。伪集群和真集群的区别我习惯用下面这张表来概括维度伪集群真集群物理机器数量1台3台及以上资源隔离进程级隔离内存在同一物理机上共享物理隔离资源独立部署成本低20分钟可完成需要硬件、网络、运维配合故障模拟能力可以模拟进程崩溃可以模拟进程崩溃、机器宕机、网络分区适用场景开发、测试、学习生产、压测、高可用验证所以在决定用伪集群还是真集群之前先想明白目标是什么。如果想验证Kafka自身机制和你的业务代码逻辑伪集群完全够用如果要验证网络层面、运维层面、硬件故障层面的行为伪集群有明显局限。这个边界我在后面故障演练部分还会再提。1.2 从ZooKeeper模式到KRaft模式怎么选早期Kafka依赖ZooKeeper保存元数据、做控制器选举3.x版本引入了KRaft模式。在我实测的版本Kafka 3.7.0里KRaft已经可以作为生产模式使用官方也明确ZooKeeper模式处于替换路径上。两者对比非常明显传统ZooKeeper模式需要额外维护一套ZK集群部署链路更长。伪集群要在Kafka三节点之外再造一个ZK哪怕单机ZK复杂度直接翻倍。KRaft模式元数据全部放到Kafka自己的内部日志里controller角色由broker兼任。没有外部依赖执行一条格式化命令就能启动干净利落。伪集群部署我强烈建议直接用KRaft。原因很简单伪集群本来就是轻量路线再搭一套ZK就失去了伪集群的意义。而且从学习角度说先新后旧直接契合Kafka未来的版本方向。如果你手头的老项目还在用ZooKeeper模式那也可以用对应ZK版本去搭但那种部署方式网上教程很多我不展开这篇重点把KRaft伪集群的细节讲透。2. 环境准备版本组合与目录规划2.1 版本搭配与基础软件安装我用的组合是kafka_2.13-3.7.0搭配JDK 17。Scala版本选2.13是当前主流JDK用17是因为Kafka 3.x对17的兼容性已经非常稳定GC参数和内存管理也更省心。下载后直接解压到/opt下命令如下tar -zxf kafka_2.13-3.7.0.tgz -C /opt mv /opt/kafka_2.13-3.7.0 /opt/kafka mkdir -p /opt/kafka/logs chown -R kafka:kafka /opt/kafka这里有几个容易被忽略的点我先啰嗦一遍检查系统是否装了JDK用java -version确认。很多人机器上只有一个旧JDK 8启动3.x会直接报UnsupportedClassVersionError这不是Kafka的问题是JDK版本太老。建议不要用root账号跑Kafka创建一个普通用户kafka后面所有命令都用这个用户执行。一方面安全另一方面Kafka对文件权限敏感用root跑完之后目录属主混乱后续排查问题非常闹心。下载之前确认网络能访问官方下载页如果公司内网有代理下载时要带代理参数否则文件下到一半断掉甚至拿回一个坏包解压时才报错浪费大量时间。还有一个很多人不知道的细节Kafka发行包里的bin目录下有大量shell脚本很多教程直接让你运行kafka-server-start.sh实际上如果你用普通用户跑要先确保bin目录有执行权限。我曾经遇到从挂载分区解压过来的场景权限位全乱了一执行就Permission denied。所以解压完最好执行ls -l bin | head提前确认。2.2 目录规划配置与数据隔离伪集群比真集群更讲究目录规划因为多个broker共用一台机器一旦配置和数据串在一起后面排查问题的成本会成倍增加。我习惯这样组织/opt/kafka ├── config # 原始配置目录 ├── bin # 脚本 ├── logs # 框架自身日志输出目录 └── data # 各broker数据目录 ├── broker1 ├── broker2 └── broker3 /etc/kafka-conf # 自定义broker配置目录 ├── server.1.properties ├── server.2.properties └── server.3.properties /var/log/kafka # 各broker独立运行日志 ├── broker1.log ├── broker2.log └── broker3.log为什么不改原始config/server.properties而要单独建目录原因很简单Kafka官方模板里写满了注释和示例线条很粗伪集群需要的是三个内容干净、差异明确的配置文件。把模板复制出来改既保留原始文件作对照又能在配置出错时一眼看出三个文件的差异尤其适合调试场景。数据目录我建议放在独立挂载点或至少独立分区上不要和Kafka二进制文件放同一块小盘。伪集群虽然是单机但多个broker同时写日志段和索引文件磁盘占用增长很快。如果系统盘满了不只Kafka出问题整个服务器都会跟着遭殃。我测试环境吃过这个亏跑了一段时间压测/opt所在分区被日志段塞满最后无奈清理topic数据才恢复。3. 伪集群核心配置三个broker的差异点详解3.1 生成集群唯一ID并格式化存储目录KRaft模式有两个前置动作生成集群ID、格式化各broker的存储目录。集群ID相当于Kafka元数据日志的根指纹每个节点必须属于同一个集群ID否则启动时节点之间互相不认识直接报错。KAFKA_CLUSTER_ID$(/opt/kafka/bin/kafka-storage.sh random-uuid) echo $KAFKA_CLUSTER_ID /opt/kafka/bin/kafka-storage.sh format -t $KAFKA_CLUSTER_ID -c /etc/kafka-conf/server.1.properties /opt/kafka/bin/kafka-storage.sh format -t $KAFKA_CLUSTER_ID -c /etc/kafka-conf/server.2.properties /opt/kafka/bin/kafka-storage.sh format -t $KAFKA_CLUSTER_ID -c /etc/kafka-conf/server.3.propertiesformat命令执行前务必确认目标log.dirs目录是空目录或者尚未初始化的目录因为format会对目录做清理。如果误指向了已有数据的目录数据会被清掉。这个命令在伪集群环境用起来毫无压力但在真实集群里是有操作纪律的一定要养成配置前先核对log.dirs的习惯。3.2 三个broker的配置文件差异化对照下面给出三个配置文件核心内容我简化了注释实际使用时可以再加业务需要的参数server.1.propertiesprocess.rolesbroker,controller node.id1 controller.quorum.voters1localhost:9093,2localhost:19093,3localhost:29093 listenersPLAINTEXT://localhost:9092,CONTROLLER://localhost:9093 advertised.listenersPLAINTEXT://localhost:9092 controller.listener.namesCONTROLLER inter.broker.listener.namePLAINTEXT log.dirs/opt/kafka/data/broker1 num.partitions3 default.replication.factor3 offsets.topic.replication.factor3 transaction.state.log.replication.factor3 transaction.state.log.min.isr2 min.insync.replicas2server.2和server.3不用重复贴完整配置差异只有node.id、listeners、advertised.listeners、log.dirs四行。这里单独列出来方便直接对照使用# server.2.properties node.id2 listenersPLAINTEXT://localhost:19092,CONTROLLER://localhost:19093 advertised.listenersPLAINTEXT://localhost:19092 log.dirs/opt/kafka/data/broker2# server.3.properties node.id3 listenersPLAINTEXT://localhost:29092,CONTROLLER://localhost:29093 advertised.listenersPLAINTEXT://localhost:29092 log.dirs/opt/kafka/data/broker3注意controller.quorum.voters这一行在三个文件里必须保持完全一致。我见过有同事复制配置时把三个文件里的controller.quorum.voters写成了不同写法结果启动后元数据无法达成一致节点之间互相踢来踢去。KRaft模式对配置一致性要求比ZooKeeper模式更严格因为集群元数据日志要靠投票组形成共识任何一个节点写错投票列表都会让整个集群的元数据状态陷入分裂。下面这个表格把关键字段的作用和伪集群注意点梳理一遍字段作用伪集群注意点process.roles节点角色三个节点都配broker,controllernode.id节点唯一标识三个文件必须不同controller.quorum.voters控制器投票成员列表三个文件必须一致listeners监听端口列表PLAINTEXT端口是业务端口CONTROLLER端口是内部控制器通信端口advertised.listeners对外广播的地址客户端和broker间通信依赖它最容易配错log.dirs数据存储目录三个文件必须指向不同目录重点强调advertised.listeners。很多教程只写listeners不写advertised默认情况下它会fallback到listeners的值。如果listeners里用了localhost本地连接没问题但如果你在listeners里写了内网IP客户端却用localhost连接就会出现元数据拿得到、实际请求连不通的奇怪表现。伪集群环境建议统一都用localhost省掉一大部分主机名解析问题。3.3 端口分配策略与冲突规避3节点伪集群需要6个端口我用的是这样一套规则broker业务端口(PLAINTEXT)控制器端口(CONTROLLER)broker190929093broker21909219093broker32909229093端口递增是我比较推荐的做法。9092是Kafka默认端口保留给第一个broker另外两个用19092、29092这种加10000、加20000的方式好处是好记、一眼能对得上节点。控制器端口同理。启动前用ss -lntp检查端口是否被占用。常见的情况是开发机同时装了MySQL、Redis之类的服务端口段很紧张冲突之后Kafka启动会报Address already in use。这时候先确认谁占了端口再决定是停服务还是给Kafka换端口。伪集群换端口很简单因为三个配置文件的端口是统一规划的整体平移一档就行。4. 启动流程与集群状态验证4.1 启动三个broker并确认进程健康KRaft模式启动顺序没有ZooKeeper模式那么严格不需要先启动另一套外部组件。直接依次启动三个进程su - kafka /opt/kafka/bin/kafka-server-start.sh -daemon /etc/kafka-conf/server.1.properties /opt/kafka/bin/kafka-server-start.sh -daemon /etc/kafka-conf/server.2.properties /opt/kafka/bin/kafka-server-start.sh -daemon /etc/kafka-conf/server.3.properties-daemon参数让进程后台运行。启动后第一件事是看日志不要急着去看进程。每个broker都有独立日志如果出现KafkaServer id1 started字样说明broker已经完成启动如果只有RaftServer启动成功但broker没有注册多半是controller quorum没有形成要重点看metadata相关报错。用jps或者ps -ef | grep kafka确认三个进程都在。这个阶段如果发现只有两个进程说明第三个没起来去查对应配置文件里的端口和日志目录有没有冲突。我建议启动之后保留一份启动明细记录每个broker的PID、端口、日志目录因为后面做故障演练或者杀掉进程时能快速定位目标不用每次现查。4.2 用topic创建与describe确认集群可服务进程起来不代表集群可用Kafka伪集群正确性的第一道验证关卡是创建topic。/opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:9092 \ --create --topic test-pseudo-cluster --partitions 3 --replication-factor 3然后describe/opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:9092 \ --describe --topic test-pseudo-cluster正常输出大致是这样Topic: test-pseudo-cluster PartitionCount: 3 ReplicationFactor: 3 Topic: test-pseudo-cluster Partition: 0 Leader: 1 Replicas: 1,2,3 Isr: 1,2,3 Topic: test-pseudo-cluster Partition: 1 Leader: 2 Replicas: 2,3,1 Isr: 2,3,1 Topic: test-pseudo-cluster Partition: 2 Leader: 3 Replicas: 3,1,2 Isr: 3,1,2这个输出说明几点PartitionCount是3分区创建成功。ReplicationFactor是3每个分区有3个副本。IsrIn-Sync Replicas与Replicas一致都是3个所有副本都处于同步状态。Partition 0的Leader是1Partition 1的Leader是2Partition 2的Leader是3说明controller把leader均匀分散到了三个broker这是多节点身份生效的最直观证据。如果describe看到Isr只有1个基本可以断定副本同步没起来问题大概率出在advertised.listeners或者节点间网络访问上后面排障部分细说。4.3 生产消费一通到底的功能验证topic创建OK之后再用生产消费链路验证一遍# 终端A从broker2端口生产消息 /opt/kafka/bin/kafka-console-producer.sh --bootstrap-server localhost:19092 \ --topic test-pseudo-cluster # 终端B从broker3端口消费消息 /opt/kafka/bin/kafka-console-consumer.sh --bootstrap-server localhost:29092 \ --topic test-pseudo-cluster --from-beginning --group test-group在终端A输入几行消息终端B能实时收到说明客户端无论连接哪个broker都能通过元数据请求找到分区leader完成读写集群整体服务链路是通的。我习惯在这个阶段顺手做一次简单的端到端检查生产50条消息消费端统计是否完整收到并且记录消息所在分区方便后面观察消费组分配和故障转移时的行为。5. 业务场景实测消费组、故障转移与数据可靠性5.1 消费组内多消费者连接哪个broker都行先做一个常见验证一个group下挂两个消费者往topic里发一批消息观察消息如何在两个消费者之间分配。Kafka消费组再平衡的机制可以这样理解组里有一个协调者负责给成员分配分区。只要客户端配置相同的group.id不管连的是9092、19092还是29092协调者都会按当前组成员数量重新分配分区。伪集群环境特别适合观察这个现象因为三个broker各自都是协调者的候选节点你可以在不同端口上启动消费者看它如何被拉进同一个组。实测下来两个消费者会各自分到一部分分区三个消费者时基本上一个消费者拿一个分区。这种分配在单节点环境下也能看到但多broker场景下你能更清楚地感受到连接不同端口、其实在操作同一个集群这件事。对新手理解消费组模型伪集群是最好的教学道具。5.2 故障演练kill一个broker观察集群自我修复这是伪集群最吸引人的验证场景。找到broker1的进程号直接kill -9模拟进程崩溃PID$(ps -ef | grep server.1 | grep -v grep | awk {print $2}) kill -9 $PID等待几秒后再次describe/opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:19092 \ --describe --topic test-pseudo-cluster观察输出原本leader是1的分区现在leader切换到了2或3Replicas不变Isr里少了刚被kill的节点1。这就是Kafka的故障转移leader节点挂掉之后controller从ISR中选择新leader由于伪集群剩余的2个controller还满足quorum要求集群仍然可以正常服务。继续执行生产消费命令消息读写都正常。再启动broker1等它重新加入集群过一段时间describeIsr又变回三个节点。这个过程完整演示了Kafka的高可用机制。注意kill之后至少等10秒再做验证因为Kafka的会话超时、控制器选举、leader切换有一个时间窗口太早验证容易误判成集群不可用。5.3 伪集群的边界什么场景不建议用伪集群能验证很多事但有些场景它永远验证不了。无法模拟网络分区和机器宕机。kill进程只能模拟进程级故障Kafka客户端看到的是某个broker断了但它无法区分进程崩溃和网络不可达这两种故障的处理策略差别很大。没有真正的物理资源隔离。三个broker共享CPU、内存、磁盘压测时资源竞争会让性能指标失真。如果你要测Kafka集群能扛多少TPS伪集群的数据只能当参考不能作为生产容量规划依据。磁盘故障无法模拟。三个broker的数据目录可能在同一块磁盘上磁盘挂了全都挂伪集群在这方面和单节点一样脆弱。所以伪集群最适合学习Kafka机制、开发环境联调、验证业务代码的幂等性和消费组逻辑。如果目标是生产容量评估、跨机房容灾、真实故障预案演练请老老实实用真集群。6. 高频问题排查与避坑实录6.1 端口被占用与进程残留Kafka伪集群最常遇到的第一类问题就是端口冲突。启动时报Address already in use十有八九是上一轮进程没杀干净或者别的服务正在监听。排查步骤用下面这个命令足够ss -lntp | grep -E 9092|19092|29092|9093|19093|29093如果端口是之前Kafka进程残留直接kill -9再清理掉log.dirs里的临时lock文件和异常文件。Kafka在启动后会生成锁文件进程被kill -9时无法执行shutdown hook这些文件会遗留下来重启时可能报锁冲突。如果端口被其他服务占用把Kafka端口整体换一档同时改三个配置文件。注意kill -9之后jps可能仍显示一些Java进程要确认PID再动手不要误杀其他业务服务。6.2 客户端连不上或连接超时这个问题我碰到不下十次。现象kafka-topics.sh --list能列出topic但生产消费时报TimeoutException或者消费者一直连不上协调者。根因通常落在advertised.listeners与listeners不一致上。简单说broker内部用listeners监听请求但broker在元数据里告诉客户端请连我这里使用的是advertised.listeners。如果advertised.listeners写的是机器内网IP而客户端命令行用了localhost就会出现拿到元数据、请求发错地址的怪毛病。伪集群最稳妥的做法所有broker的advertised.listeners都写成localhost加对应端口客户端命令也统一使用localhost。这样整个集群的地址体系完全自洽不会被主机名解析、网卡多IP这些问题干扰。6.3 副本同步不起来ISR缩水怎么办创建了replication-factor3的topic但describe出来Isr始终只有2个甚至1个。我遇到过几次有一次是broker3的日志目录权限不对broker无法写入于是它一直处于非同步副本状态。排查思路从日志和资源两方面入手先看掉队broker的日志有没有ReplicaFetcher相关报错。检查log.dirs属主和磁盘剩余空间执行df -h和ls -ld分别看。看replica.lag.time.max.ms配置默认30秒。如果因为GC停顿太频繁导致副本抓取超时也会出现ISR抖动。伪集群三个JVM在同一台机器上GC对副本同步的影响比真集群更明显可以适当调大这个参数。还要注意min.insync.replicas和replication.factor的搭配。伪集群3副本环境里min.insync.replicas通常设成2比较合理。如果设成3一旦某个broker重启期间有写入请求就会报NotEnoughReplicas产品会直接体验成写不进去。6.4 伪集群资源调优JVM堆与文件句柄最后聊伪集群的资源层经验。三个broker进程默认各自是1G堆内存16G内存的机器上没问题但如果你只有8G内存三个进程加系统开销内存会很紧张。建议在启动脚本里显式设置export KAFKA_HEAP_OPTS-Xms512m -Xmx512m这样避免三个broker互相争抢也让GC行为更可预期。但注意堆设太小会让Kafka在压测时频繁Full GC反而容易触发前面说的副本同步超时所以按内存总量取一个平衡值。8G机器建议每个broker 512M到768M16G机器建议1G到1.5G。文件句柄也是容易被忽略的点。Kafka生产环境建议把ulimit -n调大伪集群同样需要。我在测试环境把nofile调到10240之后各种莫名其妙的Too many open files就消失了。伪集群是把单机资源放大使用的场景所有系统层面的配额都要按三倍单broker的思维来准备。最后分享一个我自己的习惯我会把整套伪集群的配置和启动命令放到一个deploy目录里用脚本管理而不是每次手动敲。脚本里包含生成集群ID、格式化、启动、创建测试topic、检查状态这几个动作。这样无论是复现问题还是给同事演示都能几秒钟拉起一个环境。踩过几次坑之后你会发现伪集群最大的价值不是省钱而是它能让你对Kafka的元数据、副本、故障转移这些抽象概念形成直观手感这种手感是看十篇文档都换不来的。
阅读完成 · 觉得有帮助?
咨询建站