版本为 3.9.1 ,处理方式比较 trick,目前仅在无流量的集群中验证过。
raft voter 列表的维护
在引入了KIP-853 功能验证机制后,对于 raft voter 节点的列表信息,是存储在 raft log 中的,无法通过外部的配置文件进行干预(或者可能是我没找到对应的地方)。raft log 中存储的信息样例如下:
1 | # 读取 __cluster_metadata-0 下最新的 checkpoint |
1 | >> tree image/cluster/controllers |
在 raft leader 存在时,可以通过脚本 kafka-metadata-quorum.sh的 add-controller 和 remove-controller来调整其中的 voter 列表信息,详细的操作流程见文档 Kafka KIP-853 功能验证
故障发生时的现场
故障现场
当前由于运维经验不足,缺少和 raft 相关的监控,导致一个目前未接流的集群出现了 raft 丢主的问题.


可以看到,从 10.41.113.145 挂掉之后,就无法选主了。当前实例列表如下,应该是有误的,因为 leader 已经挂了,所以 raft log 的信息不更新了,因为实际上已经挂了两个实例。在丢主后,当前没有找到通过外部方式来对 voter 进行干预的手段。
1 | nizhenyang@ningzhixian [11:30:39 AM] [~] |

broker0 日志
1 | [2025-12-11 11:32:54,328] INFO [RaftManager id=0] Candidate sent a voter key (Optional[ReplicaKey(id=0, directoryId=Optional[i4eqWHkOz8EdjmAvBv-RTA])]) in the VOTE request that doesn't match the local key (OptionalInt[0], CYh4bpOcyVnbJuhnrrzudw); rejecting the vote (org.apache.kafka.raft.KafkaRaftClient) |
可以看到,预期的 voter key 是 i4eqWHkOz8EdjmAvBv-RTA ,但是本地是 CYh4bpOcyVnbJuhnrrzudw
1 | 1000007.standard-build-kafka-hbe.www.hbe 1000007.standard-build-kafka-hbe.www.hbe $ cat metadata/meta.properties |
怀疑是 raft log 中存储的 voter 信息有误。在服务启动时,有一个前置的 agent 程序预期会将历史 brokerid=0 的信息全部移除,相关日志如下:
1 | time="2025-12-04T19:44:05+08:00" level=debug msg="executing command: ./bin/kafka-metadata-quorum.sh --bootstrap-server standard-build-kafka-hbe.www.hbe.serv:9092 describe --status" |
可以看到,此时 remove-controller (移除旧 voter 信息)执行失败了,老的 brokerid=0 的信息没有被移除,所以其中记录的 hostname 和投票使用的 id 都是错误。启动 agent 代码写的不严谨,缺少命令执行失败时的容错能力。
broker1 日志
1 | [2025-12-11 13:08:50,377] WARN [RaftManager id=1] Connection to node 3 (bddwd-ps-beehive-agent189478.bddwd.baidu.com/10.41.121.217:9093) could not be established. Node may not be available. (org.apache.kafka.clients.NetworkClient) |
可以看到,主要报错为 bddwd-ps-beehive-agent188790.bddwd.baidu.com 的 cluster id 不正确,看 brokerid 1 中打印的信息,这个预期应该是 broker4,但是实际上 broker4 已经被迁移走了,目前是被其它集群的 kafka 占用。也就是说,broker 4 新实例在启动的时候,也存在 voter 信息修改失败的问题。另外根据日志中的报错信息,发现访问的 broker0 和 broker3 上的实例也存在问题,元信息有误。broker0 的问题上面已经排查过了,是已知的问题。broker3 估计也是类似的问题,有待后续排查。
这里我们再确认一下 broker1 的元信息情况:
1 | # /home/disk3/nizhenyang/dev/kafka_new_ver_search/std_hbe_err/metadata_bak_20251211_broker_1/__cluster_metadata-0//00000000000024475200-0000012041.checkpoint |
选取最新的 checkpoint 加载,可以看到,相关的元信息都在

其中 controller 的信息如下
1 | controllers: |
broker2 日志
broker2 已经被 stop 了,最后的相关日志
1 | [2025-12-11 12:58:53,397] INFO [RaftManager id=2] Voter key for VOTE or BEGIN_QUORUM_EPOCH request didn't match the receiver's replica key: bddwd-ps-beehive-agent189477.bddwd.baidu.com:9093 (id: 0 rack: null) (org.apache.kafka.raft.KafkaRaftClient) |
可以看到,也是连接 broker0 和 broker3 存在问题。接下来我们需要确认一下 broker2 的元数据。元数据备份地址位于 /home/disk3/nizhenyang/dev/kafka_new_ver_search/std_hbe_err/metadata_bak_20251211_broker_2
1 | controllers: |
broker3 日志
1 | [2025-12-11 13:57:09,187] ERROR [RaftManager id=3] Unexpected error INCONSISTENT_CLUSTER_ID in FETCH response: InboundResponse(correlationId=79250054, data=FetchResponseData(throttleTimeMs=0, errorCode=104, sessionId=0, responses=[], nodeEndpoints=[]), source=bddwd-ps-beehive-agent188790.bddwd.baidu.com:9093 (id: 4 rack: null)) (org.apache.kafka.raft.KafkaRaftClient) |
连接 broker4 的故障是已知问题,新的 broker4 启动时,移除信息失败了,然后原部署的机器上运行了其它 kafka 集群的实例。broker2 是 stop 的状态,所以连不上。估计是这些问题导致了 broker3 的 voter 启动失败,其它的 voter 连接存在问题。
broker4 日志
1 | [2025-12-11 12:49:17,658] INFO Registered kafka:type=kafka.Log4jController MBean (kafka.utils.Log4jControllerRegistration$) |
在自动生成的配置文件中,不包含 broker4。后面排查的原因是这个 voter 列表是由启动 agent 生成的,当时想通过强制指定 voter 列表,逐步移除 controller 来尝试进行止损,但是忽略了把 process.roles 设置为 broker 。
1 | # The role of this server. Setting this puts us in KRaft mode |
问题总结
根据日志分析有如下结论:
broker0 的问题是
存在实例迁移,迁移后执行
remove-controller移除历史 0 的信息失败本地的 dir id 和 metaadata 中的 dir id 对不上,dir id 是用于投票的,不一致会导致启动失败
broker1 的问题是:
连不上 broker 0 ,因为它的 ip 信息没有更新
连不上 broker 2,它已经被 stop 了
连 broker3 存在问题,我怀疑是因为 broker3 的 voter 状态存在问题,不过具体原因不太清楚
连不上 broker 4,因为它的 ip 信息没有更新,新实例都没启动
broker2 的问题是:
当前是 stop 的状态
连不上 broker0
连 broker3 存在问题
broker3 的问题是
连接 broker2 持续有问题
连不上 broker 4,因为它的 ip 信息没有更新,新实例都没启动
broker4 的问题是
存在实例迁移,迁移后执行
remove-controller移除历史 4 的信息失败自动生成的配置文件中,没有 brokerid 4 的配置
总之 voter 的信息全乱了,当务之急是尽快通过某种方式修正 raft log 中的 voter 的相关信息,使其可以选出 leader。
kafka 源码改造
我是没能力直接改 kafka 的源码或者用 scala 写工具的,不过可以借助大模型的能力,由我这边提需求,然后让大模型来进行前期调研和代码编写。由于代码较为复杂,且这个模块非常重要,所以使用的是能力比较强的某c姓国外大模型。以下内容包含对模型提问的 prompt 以及模型的回答:
想法1:编写 cli 工具修改落盘的 raft 日志
原理研究
1 | 当前 kraft kafka 集群存在由于 controller 元信息中 CurrentVoters 信息错误导致 raft 选主无法成功的问题。我们想设置一种机制,原地修改存储在本地的 metadata 信息,来校准其中的 voter 信息。首先,你需要结合源码分析当前通过脚本 kafka-metadata-quorum.sh 来进行 remove-controller 和 add-controller 的具体流程。 |
1 | 基于源码分析,我现在可以提供完整的分析报告: |
需求分析
1 | 在一个 kafka 实例停机的场景下,通过外部 cli 工具强制写入干预的 metadata 是否可行? |
1 | 基于深入的源码分析,我现在可以全面回答关于在 Kafka 实例停机场景下通过外部 CLI 工具强制写入干预 metadata 的可行性问题: |
remove/add-controller 流程确认
1 | 目前的问题是,raft 无法选主,导致无法使用 kafka-metadata-quorum.sh 来进行 add-controller 和 remove-controler 的操作 |
1 | 基于深入的源码分析,我现在可以提供针对 Raft 无法选主场景的完整解决方案: |
最终结论
首先,不可能完全格式化集群元信息,这个和重建集群没区别,会丢失存量的所有 topic 和 broker 信息,约等于丢失全部存量的数据,这是不可接受的。
其次,这要求所有节点完全停机,这也是不可能的,因为相关的 kafka 集群规模可能有上百个节点,全部停机,然后人工依次格式化集群信息的成本太高了。
综上,没法直接修改 metadata 的信息,所以没有实现相关写入工具的必要
想法2:干预 raft voter 列表初始化
也即在 voter 启动时,通过某种方式干预它获取到的 voter 列表,将问题 voter 移除,或者强制覆盖原有的 voter 信息。
原理研究
1 | 当前 kraft kafka 集群存在由于 controller 元信息中 CurrentVoters 信息错误导致 raft 选主无法成功的问题。我们需要为 kafka 打一个 patch,支持从配置文件中加载干预的 voter 列表,用以覆盖从集群元信息中读取的列表。首先,你需要结合源码分析 kafka 的 kraft 是如何加载和维护 voter 列表信息的 |
1 | 现在让我基于源码分析来回答您的问题。根据我对 Kafka KRaft 源码的深入分析,我将按照 AGENTS.md 的规范来阐述 voter 列表的加载和维护机制。 |
机制设计
1 | 如果想新增一个机制,支持在配置文件中强制配置 voter 列表的信息,用以覆盖 ElectionState 中的信息,包含 id、directoryId 和 endpoints 信息,应该如何进行设计? |
1 | 基于对源码的深入分析,我将设计一个完整的配置覆盖机制来解决 CurrentVoters 信息错误的问题。 |
初次源码修改
1 | 根据你设计的方案,对相关的文件进行修改 |
1 | ## 修改总结 |
迭代与提问
内容较多,省略模型输出
1 | 这里确认一下,在完成修改后,当调用 kafka-metadata-quorum.sh 的 describe --status 时,获取到的 voter 列表是被覆盖后的 voter 列表,还是原始的列表? |
1 | 那么此时调用 kafka-metadata-quorum.sh 的 remove-controller 和 add-controller ,可以对持久化状态中可能损坏的 CurrentVoters 进行操作吗? |
1 | 对于 AddVoterHandler 和 RemoveVoterHandler 中的一致性问题的问题可能存在问题。我这里添加干预 voter 列表的能力,是希望可以在持久化状态 CurrentVoters 损坏的情况下,让 raft 选出 leader,然后再执行 remove-controller 和 add-controller 对损坏的 CurrentVoters 进行修复。 |
1 | 额外新增日志,当使用配置干预的 voters 时,也同时打印持久化状态的 CurrentVoters 的信息到日志中,输出的信息和 ./bin/kafka-metadata-quorum.sh describe --status 输出的 CurrentVoters 信息保持一致 |
1 | 按照当前变更运行时,发现 controller.quorum.voters.override 的配置没有实际生效,确认一下当前配置加载的流程是否有误 |
1 | 在实际测试时,发现使用 controller.quorum.voters.override=2@10.144.79.15:8121:JdGYVjmQfoKkt2uw6J-OIw 启动后,通过脚本 ./bin/kafka-metadata-quorum.sh describe --status 拿到的信息中,CurrentVoters 只有 id 为 2 的节点了,这个符合预期吗? |
1 | 在增加节点 0 后(未配置 controller.quorum.voters.override,但是清理了__cluster_metadata-0 ),执行./bin/kafka-metadata-quorum.sh add-controller ,然后再通过 ./bin/kafka-metadata-quorum.sh describe ,发现其没有被提升为 voter |
add-controller 的问题
1 | 还是存在问题,执行 add-controller 后相关节点并没有被加入到 voter 中,确认一下在代码中,在开启 controller.quorum.voters.override 时, kafka server 端 add-controller 相关逻辑是否有生效。相关日志如下: |
1 | 现在我发现了关键问题!让我分析日志中的异常情况: |
add-controller 问题修复
1 | 我们希望仅在服务启动时使用 controller.quorum.voters.override 将 voter 信息,对于后续 add-controller、remove-controller 和 describe ,不受这个影响。因为我们的主要目标是校准历史问题的 voter 信息 |
1 | ## 启动时生效实现完成 |
实际请求 voter 列表的问题
1 | 在 id=2 的服务设置为 controller.quorum.voters.override=2@10.144.79.15:8121:UnB5WuzilhDgzMvw3IyZsQ 启动后,虽然有 Override CurrentVoters 的日志输出,但是看日志,kafka 依旧在尝试连接其他的节点,在多次连接失败后,id=2 的服务就退出了。 |
1 | ## 选举阶段问题修复完成 |
并发访问确认
1 | 确认一下当前新增配置项 controller.quorum.voters.override 涉及的代码是否考虑了并发场景的问题,例如是否有变量会被多个线程修改或者一个线程修改一个线程读取 |
1 | ## 并发安全性分析与修复完成 |
总结
至此,根据想法 2 实现了人工干预的配置项 controller.quorum.voters.override用于强制指定 voter 列表,以重新选出 leader 后,通过 add-controller校准 voter 列表信息。代码全部是 ai 写的。
实现细节总结 - summary by ai
1 | 当前已经完成了对于强制指定 voter 的功能添加。现在你需要基于之前的工作进行梳理,在这个功能关闭和开启时,voter 参与选举 leader 的流程 |
概述
本文档详细分析在 controller.quorum.voters.override 功能关闭和开启时,voter 参与选举 leader 的完整流程。
核心概念
VoterSet 来源
源码位置:
raft/src/main/java/org/apache/kafka/raft/internals/KRaftControlRecordStateMachine.java:183-195
是什么
VoterSet 是选举的核心数据结构,决定了哪些节点参与选举。
为什么
选举需要知道哪些节点是合法的 voter,以便:
确定选票请求的目标节点
计算多数票阈值
验证选举结果的合法性
怎么样
VoterSet 的来源有两种:
持久化状态:从 Raft 日志中恢复的历史 VoterSet
Override 配置:通过
controller.quorum.voters.override配置强制指定
1 | public VoterSet lastVoterSet() { |
场景一:Override 功能关闭(正常模式)
配置
1 | # 不配置 override,或配置为空 |
选举流程
1 | sequenceDiagram |
详细步骤
步骤1:初始化阶段
源码位置:
raft/src/main/java/org/apache/kafka/raft/KafkaRaftClient.java:478-510
1 | public void initialize(..., VoterSet overrideVoterSet) { |
步骤2:VoterSet 获取
源码位置:
raft/src/main/java/org/apache/kafka/raft/internals/KRaftControlRecordStateMachine.java:183-195
1 | public VoterSet lastVoterSet() { |
步骤3:选举状态初始化
源码位置:
raft/src/main/java/org/apache/kafka/raft/QuorumState.java:147-153
1 | private ElectionState readElectionState() { |
步骤4:网络连接初始化
源码位置:
raft/src/main/java/org/apache/kafka/raft/KafkaRaftClient.java:521-545
1 | // overrideVoterSet == null,使用静态配置 |
步骤5:选举执行
源码位置:
raft/src/main/java/org/apache/kafka/raft/KafkaRaftClient.java:711-715
1 | private void transitionToCandidate(long currentTimeMs) { |
源码位置:
raft/src/main/java/org/apache/kafka/raft/QuorumState.java:598-615
1 | public void transitionToCandidate() { |
选举结果
需要获得的选票:多数票(3节点需要2票)
网络连接:连接到所有 voter(节点0, 1, 2)
选举成功条件:获得至少2个节点的选票
场景二:Override 功能开启(紧急恢复模式)
配置
1 | # 配置 override,强制指定单节点 |
选举流程
1 | sequenceDiagram |
详细步骤
步骤1:配置解析
源码位置:
core/src/main/scala/kafka/raft/RaftManager.scala
1 | def startup(): Unit = { |
步骤2:初始化阶段
源码位置:
raft/src/main/java/org/apache/kafka/raft/KafkaRaftClient.java:478-510
1 | public void initialize(..., VoterSet overrideVoterSet) { |
步骤3:VoterSet 获取(选举阶段)
源码位置:
raft/src/main/java/org/apache/kafka/raft/internals/KRaftControlRecordStateMachine.java:183-195
1 | public VoterSet lastVoterSet() { |
步骤4:网络连接初始化
源码位置:
raft/src/main/java/org/apache/kafka/raft/KafkaRaftClient.java:521-545
1 | // overrideVoterSet != null,使用 override 配置 |
步骤5:单节点选举
源码位置:
raft/src/main/java/org/apache/kafka/raft/KafkaRaftClient.java:583-586
1 | // When there is only a single voter, become candidate immediately |
源码位置:
raft/src/main/java/org/apache/kafka/raft/QuorumState.java:330-337
1 | public boolean isOnlyVoter() { |
步骤6:成为 Leader 并应用 Override
源码位置:
raft/src/main/java/org/apache/kafka/raft/KafkaRaftClient.java:655-686
1 | private void onBecomeLeader(long currentTimeMs) { |
步骤7:Override 应用
源码位置:
raft/src/main/java/org/apache/kafka/raft/internals/KRaftControlRecordStateMachine.java:207-245
1 | public void applyOverrideVoterSet(LeaderState<?> leaderState, long currentTimeMs) { |
选举结果
需要获得的选票:多数票(1节点需要1票)
网络连接:只连接到 override 配置的节点(节点2)
选举成功条件:节点2给自己投票即可
后续状态:Override 配置转换为持久化状态,后续操作基于持久化状态
流程对比
关键差异对比表
|阶段|Override 关闭|Override 开启|
|-|-|-|
|VoterSet 来源|持久化状态|Override 配置|
|网络连接|连接所有 voter|只连接 override 节点|
|选举参与者|所有持久化 voter|只有 override 节点|
|多数票计算|基于持久化 VoterSet|基于 override VoterSet|
|成为 Leader 后|无额外操作|应用 override 到持久化状态|
|后续操作|基于持久化状态|基于新的持久化状态|
状态转换对比
1 | graph LR |
交互流程
Override 关闭时的关键路径
1 | flowchart TD |
关键步骤源码映射:
步骤1 →
RaftManager.scala:206- startup()步骤2 →
KafkaRaftClient.java:478- initialize()步骤3 →
KRaftControlRecordStateMachine.java:87- 构造函数步骤4 →
KRaftControlRecordStateMachine.java:183- lastVoterSet()步骤5 →
QuorumState.java:240- initialize()步骤6 →
KafkaRaftClient.java:711- transitionToCandidate()步骤7 →
KafkaRaftClient.java:655- onBecomeLeader()
Override 开启时的关键路径
1 | flowchart TD |
关键步骤源码映射:
步骤1 →
RaftManager.scala:206- startup()步骤2 →
QuorumConfig.java- parseVoterSetOverride()步骤3 →
KafkaRaftClient.java:478- initialize()步骤4 →
KRaftControlRecordStateMachine.java:100- 构造函数(带override)步骤5 →
KRaftControlRecordStateMachine.java:183- lastVoterSet()步骤6 →
QuorumState.java:330- isOnlyVoter()步骤7 →
KafkaRaftClient.java:583- 单节点立即选举步骤8 →
KafkaRaftClient.java:655- onBecomeLeader()步骤9 →
KRaftControlRecordStateMachine.java:197- shouldApplyOverrideVoterSet()步骤10 →
KRaftControlRecordStateMachine.java:207- applyOverrideVoterSet()
总结
Override 功能的核心作用
紧急恢复:在 CurrentVoters 损坏时,允许单节点启动并成为 Leader
一次性校准:Override 配置在成为 Leader 后立即转换为持久化状态
恢复正常:应用后自动清除 override,后续操作基于持久化状态
设计优势
最小侵入:只在选举阶段使用 override,不影响正常运行
自动转换:成为 Leader 后自动将 override 转换为持久化状态
兼容性好:后续的 add/remove-controller 操作完全正常工作
使用建议
仅用于紧急恢复:Override 配置应仅在 CurrentVoters 损坏时使用
恢复后移除配置:成功恢复后应从配置文件中移除 override 配置
逐步重建:使用 add-controller 命令逐步添加其他节点
本地测试
为了校验代码的正确性,在对线上集群进行止损前,搭建了一个线下集群用于测试。环境位于 44 机器上,路径为 /home/disk0/nizhenhyang/kafka-kraft-voter-fix-test
测试环境
c0 8100
c1 8110
c2 8120
b3 8140
包含三个 controller (voter)节点和一个普通(broker)节点
服务启动
cluster id SBYtyuhkTNGeZco8wQg5hw
c0
1 | export JAVA_HOME=$(pwd)/java17 |
c1
1 | export JAVA_HOME=$(pwd)/java17 |
c2
1 | export JAVA_HOME=$(pwd)/java17 |
b3
1 | export JAVA_HOME=$(pwd)/java17 |
读写数据
1 | kaf -b $(hostname -i):8100 topic create test_nzy --partitions 3 --replicas 3 |
破坏环境
1 | ./bin/kafka-metadata-quorum.sh --bootstrap-controller $(hostname -i):8101 describe --status |
1 | ClusterId: SBYtyuhkTNGeZco8wQg5hw |
1. 切换 c1 的 dir id
1 | mv metadata/ metadata_right_bak/ |
然后启动服务, 日志输出如下:
1 | [2025-12-11 15:28:33,297] INFO [RaftManager id=1] Leader sent a voter key (Optional[ReplicaKey(id=1, directoryId=Optional[u-IcQ0UoP7wPHs5S0JKXlQ])]) in the BEGIN_QUORUM_EPOCH request that doesn't match the local key (OptionalInt[1], u-IcQ0UoP7wPHs5S0JKXlw); returning INVALID_VOTER_KEY (org.apache.kafka.raft.KafkaRaftClient) |
此时 c0 的日志:
1 | [2025-12-11 15:28:58,298] INFO [RaftManager id=0] Voter key for VOTE or BEGIN_QUORUM_EPOCH request didn't match the receiver's replica key: 10.144.79.15:8111 (id: 1 rack: null) (org.apache.kafka.raft.KafkaRaftClient) |
现在的情况是,leader 依旧存在,但是其中存储的 c1 meta 信息是错误的。由于当前有两个 voter 存活,所以集群依然可以运行
1 | ./bin/kafka-metadata-quorum.sh --bootstrap-controller $(hostname -i):8101 describe --status |
此时集群的 leader 还是有的,可以读取到数据
1 | $ kaf -b $(hostname -i):8100 topic describe test_nzy |
2. stop c0
stop c0 ,模拟切主的情况,此时没有 leader 了, 因为当前集群元信息中有三个节点,一个节点挂了,一个节点的信息有误
1 | # c1 丢主 |
1 | # c2 丢主 |
新机制是否有效
选用 c2 测试,替换其使用的 jar 包,然后在配置文件中添加配置 `controller.quorum.voters.override=2@10.144.79.15:8121:8YTCnijW9E49vJRze4z5HA` 后重启服务
1 | ./bin/kafka-metadata-quorum.sh --bootstrap-controller $(hostname -i):8121 describe --status |
1 | # work @ bjyz-201605-m32-wise044 in /home/disk0/nizhenhyang/kafka-kraft-voter-fix-test/exec_env/c2 [16:36:44] |
1 | kaf nodes -b $(hostname -i):8120 |
1 | $ kaf -b $(hostname -i):8120 topic describe test_nzy |
可以看到,当前 voter 为 2,集群元信息均存在,且普通的 broker 自动加入进来了,之后清空 c0 和 c1 的 __cluster_metadata-0 后重启,并执行 add-controller命令,集群恢复正常
1 | # work @ bjyz-201605-m32-wise044 in /home/disk0/nizhenhyang/kafka-kraft-voter-fix-test/exec_env/c2 [19:25:57] |
1 | $ kaf nodes -b $(hostname -i):8120 |
结论
新机制是有效的,但需要注意,必要条件为,至少需要有一个正常的 voter 实例。大致的执行流程为:
找到一个运行时间最长的 voter,替换其 jar 包并添加
controller.quorum.voters.override=[broker_id]@[hostname]:[meta dir_id]配置后重启。启动时直接用./bin/kafka-server-start.sh -daemon xxxx启动。这一步重置了 voter 列表,仅保留当前的 voter。对于其他 voter,依次清空
__cluster_metadata-0后重启,并人工执行add-controller命令,加入 voter 。启动命令同上。对于一个启动的 voter ,将使用的 jar 包换回并移除配置
controller.quorum.voters.override后重启。根据观察,在 leader 恢复后,各 broker 节点也会自动恢复。
线上服务修复效果

相关 topic 的信息未丢失:

主要变跟
后续 TODO
其实感觉这种修复方式还是存在风险,因为没有在高流量的集群验证过。后续必须添加上对于 raft voter 状态的监控,具体怎么添加这个还需再想想。