ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Raft与KRaft深度对比:从共识算法原理到Kafka元数据迁移实战

Raft与KRaft深度对比:从共识算法原理到Kafka元数据迁移实战 分布式系统圈子里Raft和KRaft这俩名字放在一起经常把人绕晕。如果你去搜“Raft”和“KRaft”的对比大概率会看到一堆含糊其辞的说法什么“KRaft就是Kafka的Raft实现”“Raft是算法KRaft是协议”……听着都对但真正部署或者学习的时候又感觉隔了一层。我最近刚好在一个内部项目里把Kafka集群从ZooKeeper模式迁移到了KRaft模式又跟组里小伙伴一起把Raft共识算法的实现从头读了一遍踩了不少坑也顺手整理了一套自己的理解。这篇博文我不想抄官方文档也不想堆概念就想用工程师的实际视角把Raft和KRaft的关系、区别、以及实操中那些让人头疼的细节一次性讲清楚。这个内容适合谁如果你正在用Kafka、etcd、Consul这类依赖共识算法的中间件或者你是刚接触分布式系统、想搞明白“选主”“日志复制”到底是怎么回事再或者你已经在用KRaft模式但遇到集群异常却不知道怎么排查这篇文章都能给你一些实在的参考。我不会回避复杂点但会用生活化的类比和真实的参数案例来讲尽量让有三年经验和三周经验的人都能看明白。1. 追根溯源先说清楚Raft算法到底是什么1.1 为什么分布式系统非要“共识”不可分布式系统的本质矛盾在于多台机器一起干活但任何一台都可能宕机、网络可能抖动、消息可能丢失或乱序。这个时候就需要一个机制让所有机器在某个状态上达成一致——比如“谁是主节点”“这条日志到底提交了没有”。这个机制就是共识算法。Raft是2014年由Diego Ongaro和John Ousterhout提出的共识算法它的定位非常明确既要解决Paxos“看得懂但写不出正确实现”的痛点又要给工程师提供一个可以被拆成独立模块、能真正落地到工程里的方案。Raft把问题拆成了三个相对独立的子问题领导人选举Leader Election在集群中选出一个Leader所有的写请求都交给它处理。日志复制Log ReplicationLeader把一条条操作日志广播到所有Follower过半数的节点确认后这条日志才算“提交”。安全性Safety在任何网络分区、节点宕机的情况下保证已经提交的日志不会被覆盖并且最多只有一个Leader。用生活化的类比来说Raft就像一小队人在野外生存他们需要有人拍板选Leader任何重要决定都要记在本子上日志并且过半的人在场确认后才算生效提交。这套机制看起来简单但里面每一个细节都对失败场景做了极其严谨的设计。1.2 Raft的几个核心细节没搞懂就会踩坑Raft在第一眼看上去很简单但你真正去读它的论文或源码时有几个细节非常容易被忽略而这些细节恰恰决定了Raft的工程可靠性。任期Term与心跳超时。Raft把时间切成一段段“任期”每个任期最多有一个Leader。Follower如果在一段时间内没收到Leader的心跳就会发起新一轮选举。这个心跳超时不是写死的而是建议在某个范围内随机化通常150-300ms用来降低多个Follower同时发起选举导致选票被瓜分的概率。我实测下来超时时间设置得太短会造成频繁无主状态太长又会拖慢故障恢复速度这属于典型的“经验参数”后面会细说。选举限制Election Restriction。这个知识点最容易被新手忽略不是任何人都能当Leader。候选人必须包含所有已经提交的日志条目才能赢得选举。这意味着如果Follower的日志落后太多它即使拿到选票也无法当选。这个限制保证了Leader的日志是“最新最全”的避免一个日志残缺的节点当选后把集群状态拉回旧状态。日志匹配特性Log Matching。Leader在发送日志时会带上自己当前日志的索引和任期号。如果Follower发现自己日志中对应位置的条目不匹配它会拒绝该日志并告诉Leader自己上次匹配的位置。这本质上是一种“尝试-回退-再试”的机制虽然效率不是最优但胜在简单可靠。等差额被补齐后Leader再广播后续日志。我曾经在写Raft demo的时候犯过一个典型的错误在日志复制过程中只比对任期号没有比对日志索引结果造成了日志覆盖的严重事故。从那以后我意识到Raft的正确性是靠一堆“看似多余”的限制组合起来的少一个都不行。2. KRaft到底改了什么Kafka的元数据革命2.1 Kafka在KRaft之前和KRaft之后聊完Raft我们来看KRaft。KRaft不是一个独立的算法它的全称是Kafka Raft Metadata mode本质上是Kafka从2.8版本开始提供的一种运行模式用Raft共识算法来管理Kafka集群自身的元数据替代对ZooKeeper的依赖。在KRaft出现之前Kafka的架构是双系统架构Kafka负责消息存储和流转ZooKeeper负责集群元数据管理——比如谁是这个集群的Controller、哪些Broker在线、Topic的分区被分配到哪里、ISR列表是什么。这种架构在生产环境里最多的问题就是“跨界运维”ZooKeeper集群需要独立部署、独立监控它对网络和磁盘抖动极度敏感。网上那句经典的吐槽“Kafka的稳定性取决于ZooKeeper的稳定性”我深有体会。我们曾经遇到过一次ZooKeeper集群的JVM Full GC直接引发了Kafka集群的Leader切换风暴消息堆积了几百万条排查了一整夜。KRaft模式的核心是把元数据当作一条只追加的日志由Kafka的Controller节点通过Raft算法共同维护这条日志。整个集群只有一个Controller Quorum所有Broker通过读取这条元数据日志来感知集群状态变化。架构从“Kafka ZooKeeper”双核心变成“Kafka KRaft”单核心部署上直接少了一套外部依赖系统。2.2 KRaft里的“Controller Quorum”怎么理解KRaft的核心组件是Controller Quorum它是一组专门跑元数据共识的节点不一定是Broker可以只部署3个、5个这样的小集群。Controller节点之间用Raft选主选出一个Active Controller负责把元数据变更比如创建Topic、分区Leader切换写进一条特殊的主题——__cluster_metadata。如果说ZooKeeper模式是“元数据存放在外部系统并通过外部系统的一致性来保证正确性”KRaft则是“元数据存放在Kafka自己内部通过Raft日志的追加和复制来保证元数据的一致性”。这条元数据日志支持快照机制节点重启后可以从最近快照恢复而不是像以前那样完整回放所有历史元数据变更。我在摸清KRaft之前一直有个迷惑Raft算法要求Leader收到过半节点确认后才提交日志那元数据变更不就会有延迟吗实际上KRaft的设计很聪明它针对Kafka的元数据特征做了适应——元数据变更的频率远低于消息数据本身的写入所以Raft日志提交的延迟完全可控。并且KRaft里的Raft实现做了单线程写入优化一次只允许一个日志条目被提交这避免了多日志并发带来的复杂性也让实现变得更容易验证。2.3 KRaft 和 KIP-500提到KRaft就绕不开KIP-500。KIP-500是Kafka社区在2020年提出来的改进提案目标是“用KRaft模式替换ZooKeeper模式”。从Kafka 2.8开始KRaft以Early Access形式出现3.3版本后已经可以生产使用4.0版本则彻底移除了ZooKeeper模式的支持。这里我想特别说一句很多博客把KRaft说成“Kafka自己实现了Raft”这个说法不够精确。准确地说Kafka的KRaft模式是一种专门针对元数据管理场景的Raft变体。它跟普通Raft的区别在于Controller Quorum只需要处理元数据写入不处理消息数据本身所以负载模式完全不同。日志紧凑性设计不太一样元数据日志支持“快照 日志段”双模式清理而一般Raft的实现里日志长到一定程度就直接做快照截断。KRaft支持弹性选举也就是Controller节点之间不需要固定数量的静态配置节点可以动态加入或退出。这些细节直接影响了我们做生产部署时的选型判断。3. 六大维度的正面PKRaft vs KRaft3.1 核心定位差异首先要明确拿Raft和KRaft做“对比”时两边并不是同一层的概念。Raft是一种通用的共识算法它本身不关心应用场景etcd、Consul、TiKV、以及各类自研存储里都有Raft的实现KRaft则是Kafka基于Raft思想构建的一个具体架构模式它只服务于Kafka的元数据管理场景。换句话说你可以在任何一个需要共识的分布式系统里实现Raft但KRaft是Kafka的专属名词。这个区别很重要因为很多人在面试或方案讨论的时候把这两个概念并列比较结果越聊越乱。正确的提问方式应该是Raft算法如何被应用到Kafka的元数据管理上KRaft对标准Raft做了哪些场景化改造3.2 选主机制对比Raft的选主逻辑是通用的节点状态在Follower、Candidate、Leader之间迁移选举基于任期和随机心跳超时。这个过程在任何Raft库中都是一致的它保证了大多数节点可用时一定能选出Leader而且不会同时出现两个有效Leader。KRaft的选主在本质上继承了这套逻辑但它对选主后的行为做出了新定义。在KRaft模式下Active Controller不仅负责处理写入请求ActAsLeader还必须把元数据变更写入分区日志。这带来一个之前很少被讨论的细节——如果Controller Quorum中的Leader和某个Broker上的元数据版本不一致Broker会拒绝提供服务直到它跟上最新版本。从运维角度看这个机制保证了“同一时刻只有一个逻辑真源”但代价是元数据同步延迟可能导致Broker启动时间变长。我在迁移后的第一次滚动重启时就发现某些Broker因为要追赶元数据日志启动时长从原来的几十秒变成两三分钟后来给启动脚本加了超时重试才算稳下来。3.3 日志复制对比标准Raft里日志复制的核心是Leader将日志条目广播到所有Follower并获得过半确认后才提交。这条规则对所有类型的写请求是一视同仁的。但在实际系统里如果每条写请求都要经过两轮RPC一轮复制、一轮确认吞吐和延迟都会很紧张。因此几乎所有工程化的Raft实现都会做批量复制和流水线化优化。KRaft里的元数据日志复制的特殊之处在于它不需要像标准Raft那样对所有请求做批量优化因为元数据写入天生就是低频的。但它在复制时增加了版本校验。这个校验我印象特别深如果你在集群中混用了不同版本的Kafka二进制新旧节点的元数据日志schema可能不一致导致复制请求被拒绝整个集群的Controller Quorum无法形成。这一点在ZooKeeper模式时代没有这么突出。3.4 一致性语义对比标准Raft能在线性一致性的前提下保证“如果Leader确认了写请求这个写请求之后必然对所有读可见”。要达成线性一致性读请求通常也必须经过Leader或者在Follower上读到“租约”标记过的状态。KRaft中的语义略微有些特殊元数据的读写也是一致的但读写路径和普通Raft不同。KRaft把读请求分成了两类一类是从Active Controller直接读取的“最新元数据”另一类是Broker从自己本地的元数据缓存中读取的“接近最新元数据”。Broker对外提供的消息读写服务依赖的是本地缓存的元数据所以它并不能做到严格意义上的线性一致性——但因为元数据是最终收敛的且所有系统组件都遵循同样的收敛路径这在Kafka的使用场景下是完全安全的。理解了这个区别你就明白为什么KRaft集群在Controller切换后元数据缓存会有一个短暂的“收敛窗口”这是设计使然不是bug。网上有些人一发现Broker日志里有“Metadata not available”就紧张得不行其实很多时候只是切换潮汐里的正常现象。3.5 部署运维对比Raft本身只是一个算法库怎么依赖它取决于你选择哪套实现。etcd、Consul、TiKV这些系统各自封装Raft的部署方式千差万别。所以在“部署运维”这个维度上真正可比的对象是“ZooKeeper模式下的Kafka”和“KRaft模式下的Kafka”。我直接给结论在具备生产条件的前提下KRaft模式的部署和运维复杂度显著低于ZooKeeper模式。不需要再额外维护一套ZooKeeper集群不需要担心ZooKeeper的3节点还是5节点配置不需要处理“ZooKeeper连接断开导致Controller易主”的连锁问题。从广义上讲KRaft集群需要的服务器数量少了网络故障域也缩小了。但代价也很现实KRraft的监控体系比ZooKeeper时代单薄。我查遍了社区常用的监控面板目前针对KRaft的元数据日志延迟、Controller Quorum健康度的指标很多都需要自己手动采集和绘制。Kafka提供的JMX指标虽然够用但在告警规则设计上还是得自己踩坑积累经验。3.6 生态成熟度与生产落地对比到这里必须说一句公道话Raft的生态成熟度远高于KRaft。Raft诞生快十年了etcd、Consul、TiDB、CockroachDB里都有大量生产实践的积累各种语言的Raft实现加在一起超过上百个疑难杂症基本都有方案和讨论。而KRaft从2.8发布到4.0移除ZooKeeper模式满打满算也就几年时间。虽然核心功能已经稳定但在一些极端场景下比如超大集群、高频元数据变更、Controller网络分区你的排障经验库没有那么多现成答案可抄。我个人的建议是新项目优先考虑KRaft因为少一套ZooKeeper依赖在未来两三年里能省掉大量运维成本存量项目如果不是有特别的动力不必着急迁移把风险评估做透最重要。KRaft不是银弹但它显然是Kafka走向云原生架构的必经之路。4. 实操侧记三节点KRaft集群的迁移全过程4.1 环境准备与节点规划我这次迁移的集群是3个Broker节点每节点16C32G存储是云硬盘操作系统是CentOS 7类系统Kafka版本直接选了3.6.0——这个版本对KRaft的成熟度已经比较放心了。架构规划为3个Broker节点同时兼任Controller也就是说Controller Quorum也是3节点。这里有个重要的经验想分享在KRaft模式下Broker和Controller可以混部也可以分开部署。如果你想要高隔离性建议Controller单独部署如果你资源有限也可以像我们一样混部。但混部意味着Controller节点的资源消耗会被Broker的流量波动干扰尤其是元数据日志量大的时候要做好资源上限隔离。对大多数中等规模集群来说混部完全够用我们三个节点扛住了日均百万级消息量Controller的CPU和内存压力并不大。4.2 配置文件的核心参数配置KRaft模式需要重点关注以下参数我把我们最终落地的配置简化后贴出来process.rolesbroker,controller node.id1 controller.quorum.voters1192.168.1.11:9093,2192.168.1.12:9093,3192.168.1.13:9093 listenersPLAINTEXT://192.168.1.11:9092,CONTROLLER://192.168.1.11:9093 advertised.listenersPLAINTEXT://192.168.1.11:9092 controller.listener.namesCONTROLLER log.dirs/data/kraft-logs需要注意的细节有几个process.roles如果是broker,controller说明这个节点同时参与数据服务和元数据服务token严格区分大小写写错了节点起不来。node.id必须是全局唯一的。我一开始随手配成了相同ID结果两个节点互相认为对方是非法节点Controller Quorum一直无法凑齐日志报错非常误导人。controller.quorum.voters这个参数是KRaft模式的灵魂它列出了所有Controller节点的ID和地址。这里我强烈建议不要用主机名直接写IP减少DNS解析环节出问题的概率。如果你以后要扩展节点这个列表要同步更新到所有节点。4.3 格式化元数据分区目录KRaft模式不像ZooKeeper模式那样需要先启动ZooKeeper再启动Kafka但它有个前置步骤格式化集群元数据分区。在Kafka 3.6版本中需要先执行kafka-storage.sh random-uuid这条命令会生成一个UUID用于标识集群的cluster id。你拿到这个UUID后再在所有节点上执行kafka-storage.sh format -t uuid -c config/kraft/server.properties这里有个非常容易踩的坑format操作会把log.dirs目录下现有的数据全部清空。如果你不小心对一台已经有Broker数据的机器执行了这个命令那就只能从头再来了。我建议在执行之前先备份配置和目录列表并且用ls确认目标目录是否为空。另一个坑是如果集群可以形成Quorum后你想重新生成UUID并格式化那么所有节点都必须使用同一个新UUID不能混用。混用会导致Controller Quorum内部数据互相不认集群完全不可用。4.4 启动与验证流程格式化完成后启动顺序没有那么严格但我还是习惯先启动Controller节点再启动Broker节点。在混部架构里其实就是按照节点顺序依次执行bin/kafka-server-start.sh config/kraft/server.properties启动之后用日志和命令检查状态。我最常用的查看命令是bin/kafka-metadata-quorum.sh --bootstrap-server 192.168.1.11:9092 describe --status这个命令会显示当前活跃Controller、Quorum中的投票者列表和元数据日志水位。我查看了三个节点上的输出确认它们都指向同一个Active Controller并且日志水位logEndOffset一致才算真正完成了迁移验证。4.5 迁移后要补的监控迁移完成后不能立刻认为大功告成。KRaft模式需要新的监控指标来保证可观测性我重点盯了几个kafka.server:typeKafkaRequestHandlerPool,nameRequestHandlerAvgIdlePercent——这是经典的攒活儿率指标如果长时间低于30%说明Broker卡住了。kafka.controller:typeKafkaController,nameActiveControllerCount——值为1才正常如果值为0说明没有Controller。kafka.server:typebroker-topic-metrics,nameMetadataLoadErrorCount——元数据加载错误数应该持续为0。要想看元数据日志的同步延迟在JMX里没有现成的直观指标需要借助kafka-dump-log.sh查看__cluster_metadata日志段的位移差值。这个比较绕但确实能反映Quorum的健康程度。我建议生产环境至少在告警项里加上“ActiveControllerCount ! 1”和“元数据日志同步滞后超过某个阈值”两条规则。这两条基本上能覆盖KRaft集群最危险的故障场景。5. 常见故障与排查思路实录5.1 Controller Quorum无法形成节点日志反复报“disconnected”这个故障我在搭建测试集群时遇到过。三台机器部署好了但任何一台的日志里都在循环打印连接被断开的信息Quorum始终无法成立。排查思路是这样的先确认controller.quorum.voters里的ID和地址是否跟每台机器的node.id和监听地址完全一致再看防火墙是不是挡住了CONTROLLER监听端口最后检查节点之间是否都能互通9093端口。我这里最终发现是安全组规则只放通了9092数据端口漏掉了9093控制端口。搞定了网络放行Quorum立刻稳定。5.2 Broker启动后一直报“Metadata not available”这个错误在KRaft模式下很常见。它通常意味着Broker还没能从Controller那里拉到完整的元数据快照。一般过一段时间会自动恢复但如果长时间不恢复就要看Broker的元数据日志目录是不是损坏了。可以用kafka-storage.sh format重置Broker的日志目录然后重启Broker。注意重置前要确认该节点上的分区数据不重要或者可以重新从Leader复制回来。实际使用中我发现这类问题经常发生在Broker和Controller节点时钟偏差比较大的情况下。Raft算法对时钟有一定容忍度但时钟跳跃还是会影响心跳判断建议用NTP强制同步所有节点的时钟。5.3 滚动重启后Topic分区Leader分布异常我们在一次日常滚动重启后发现部分分区的Leader全部集中到了同一台Broker上负载明显不均。排查下来原因也挺让人哭笑不得重启时间间隔太短上一个Broker还在优雅停机时Controller已经完成了Leader切换结果切到了一个还没来得及跟上元数据日志的节点上。虽然不影响可用性但负载热点很讨厌。解决办法也很简单滚动重启时每台机器间隔至少等两分钟让元数据同步完全跟上再继续下一台。这个案例我觉得很有代表性它说明KRaft模式下“优雅停机”的速度和以前ZooKeeper模式不一样了运维操作的节奏也要跟着变。养成“停一台、等一下、观察一下”的习惯比什么高级命令都管用。6. 当我们在说“Raft mod”的时候到底在说什么搜索热词里出现了“raft mod”很多朋友可能会好奇这是不是指某个游戏模组。其实在分布式系统语境下它更常指Raft算法的各种变体和扩展实现。这个话题值得聊一下因为理解Raft的变体才能真正理解KRaft为什么是“Kafka里的Raft”而不是“标准Raft的简单套用”。Raft诞生后工程界针对不同场景做了大量变体优化。最典型的有Multi-Raft一个节点组内同时跑多个Raft组每个组有独立的日志通过路由把不同数据映射到不同Raft组。这个模式在大型存储系统里普遍采用用来突破单条日志的吞吐瓶颈。Raft with Pre-Vote在网络分区恢复后节点必须获得大多数节点的预投票才进入完整选举避免网络抖动导致Term反复增长。Raft with Joint Consensus在集群成员变更时允许新旧成员列表有一段时间并行工作避免中途出现双Leader。KRaft从严格意义上说也是一个“Raft mod”——它的基础还是Raft选举和日志复制但针对“Controller节点只处理元数据”这一特殊场景做了诸多调整。如果你已经掌握了标准Raft的原理再去看KRaft的源码会发现大量“熟悉但微妙不同”的地方。这就是Raft生态最有趣的部分它不止是一个算法更像是一套分布式共识的设计语言每个团队都在用自己的方式重新组合这些语言。从实践角度看我建议大家在学习的时候不要把“Raft mod”当作杂七杂八的配置文件而是要从“它解决了标准Raft的什么缺陷”或“它优化了什么场景”这个角度去思考。比如看到Pre-Vote想一想为什么原始设计在旧Leader恢复时会有Term爆炸看到Multi-Raft想一想单Raft组的吞吐瓶颈在哪里。这样学下来你看KRaft配置就不再是记参数而是真的在理解系统设计。7. 关于生产选型我自己的几个判断严格来说这篇文章的主体到这里已经可以收尾了但有几个实操后的延伸思考我还是想写出来。因为技术方案没有绝对的好坏关键是是否贴合自己的场景。如果你现在还在纠结要不要把存量Kafka集群从ZooKeeper迁移到KRaft我的建议是先梳理自己对ZooKeeper这套外部依赖的痛苦程度。如果你运维Kafka已经三年以上厌倦了“搭ZooKeeper、调ZooKeeper、救ZooKeeper”的循环那KRaft带来的架构简化是实实在在的救命稻草。如果你所在团队的集群规模特别大比如几千个Broker那我建议再等等等到社区里有了足够多的大规模KRaft生产案例再动手毕竟元数据日志在超大集群里如何优雅扩展仍然是被持续验证的问题。如果你还在学习阶段我的体会是先把标准Raft吃透再去碰KRaft。Raft的论文不长加上MIT 6.824的课程资料一个周末就能把原理过一遍。然后拿Kafka的官方KRaft配置自己搭一个三节点集群用kill -9杀Leader的方式做故障演练观察一下Controller怎么切换、分区Leader怎么恢复、消息不丢是怎么办到的。这一套实操做完你对“共识算法”这个词的理解会远超看一百篇博客。
返回列表