Unexpected Offset
Errors
bash
[2025-04-28 08:22:01,606] INFO [MetadataLoader id=100] initializeNewPublishers: the loader is still catching up because we still don't know the high water mark yet. (org.apache.kafka.image.loader.MetadataLoader)
[2025-04-28 08:22:01,706] INFO [MetadataLoader id=100] initializeNewPublishers: the loader is still catching up because we still don't know the high water mark yet. (org.apache.kafka.image.loader.MetadataLoader)
[2025-04-28 08:22:01,792] ERROR Encountered fatal fault: Unexpected error in raft IO thread (org.apache.kafka.server.fault.ProcessTerminatingFaultHandler)
kafka.common.UnexpectedAppendOffsetException: Unexpected offset in append to __cluster_metadata-0. First offset 57876665 is less than the next offset 57876669. First 10 offsets in append: List(57876665, 57876666, 57876667, 57876668, 57876669, 57876670, 57876671, 57876672, 57876673, 57876674), last offset in append: 57912402. Log start offset = 57054787
at kafka.log.UnifiedLog.$anonfun$append$2(UnifiedLog.scala:797)
at kafka.log.UnifiedLog.append(UnifiedLog.scala:1724)
at kafka.log.UnifiedLog.appendAsFollower(UnifiedLog.scala:682)
at kafka.raft.KafkaMetadataLog.appendAsFollower(KafkaMetadataLog.scala:99)
at org.apache.kafka.raft.KafkaRaftClient.appendAsFollower(KafkaRaftClient.java:1145)
at org.apache.kafka.raft.KafkaRaftClient.handleFetchResponse(KafkaRaftClient.java:1127)
at org.apache.kafka.raft.KafkaRaftClient.handleResponse(KafkaRaftClient.java:1550)
at org.apache.kafka.raft.KafkaRaftClient.handleInboundMessage(KafkaRaftClient.java:1676)
at org.apache.kafka.raft.KafkaRaftClient.poll(KafkaRaftClient.java:2251)
at kafka.raft.KafkaRaftManager$RaftIoThread.doWork(RaftManager.scala:64)
at org.apache.kafka.server.util.ShutdownableThread.run(ShutdownableThread.java:127)
[2025-04-28 08:22:01,807] INFO [MetadataLoader id=100] initializeNewPublishers: the loader is still catching up because we still don't know the high water mark yet. (org.apache.kafka.image.loader.MetadataLoader)
bash
kafka.common.UnexpectedAppendOffsetException: Unexpected offset in append to __cluster_metadata-0. First offset 57876665 is less than the next offset 57876669.
简单讲就是:
Kafka 在 Raft 协议同步 __cluster_metadata-0
日志分区的时候,收到了 一个不连续的 offset,导致写入失败并触发了 fatal fault(致命错误),进而导致 broker 进程进入退出或崩溃流程。
具体解释:
__cluster_metadata-0
是 Kafka 3.x/4.x 使用 KRaft 模式(无 Zookeeper)时的重要内部元数据日志。- Raft 协议要求日志是严格连续的,不能跳号或者回退。
- 错误提示中:
- 当前 Broker 认为自己下一个要写入的 offset 是 57876669。
- 但是收到的 append 里,第一个 offset 是 57876665(比预期的小了 4)。
- 这违反了 Raft 的日志一致性要求,所以抛出
UnexpectedAppendOffsetException
。
造成这种错误的常见原因包括:
- 元数据日志损坏:比如磁盘写坏,或者之前异常宕机导致数据没同步好。
- 不正确的数据恢复:比如手动拷贝了 Kafka 数据目录,但 offset 不匹配。
- 集群中的 broker 节点数据不一致:一台 broker 滞后太多或者被强行回滚过数据。
- 强制降级/升级 Kafka 版本后元数据不兼容。
- 磁盘 snapshot 和实际日志 offset 对不上(更底层一点,比如 snapshot 57876669,但实际日志回到了 57876665)。
解决思路:
下面的操作之前,一定要做好完整的备份,尤其是 $KAFKA_LOG_DIR/__cluster_metadata-0/ 目录。
- 尝试修复(风险低):
- 删除这个 broker 上的
__cluster_metadata-0
对应的 local log 文件(通常在logs/__cluster_metadata-0
目录)。 - 重启 broker。
- Kafka 会根据 Raft 协议,从 leader(controller 节点)重新同步日志。
- 删除这个 broker 上的
- 强制清除日志并重新加入(风险中):
- 设置启动参数,跳过损坏日志(比如加
kafka.storage.force.truncate.log=true
,不过不同版本支持情况不同)。 - 重启。
- 设置启动参数,跳过损坏日志(比如加
- 最极端的恢复(风险高):
- 删除整个 broker 的数据目录(彻底清空),让它作为新节点重新加入 Raft 集群。
- 这种方式适合副本数够多(比如 3 副本集群,坏了 1 个)时使用。
当前状况
foobar-kafka-broker-0
Pod 起不来- 没办法
kubectl exec
进去 - 但是还能
kubectl
操作 Pod 资源本身,比如修改挂载的存储(PVC)
通过 PVC 把数据清理掉
因为 Kafka 的数据是挂在 PVC(持久卷)上的,所以我们可以不需要进去 Pod,直接在 Kubernetes 级别清理数据。
第 1 步:找到 foobar-kafka-broker-0 用的 PVC
执行:
bash
kubectl get pod foobar-kafka-broker-0 -o yaml | grep claimName
你会看到类似:
txt
claimName: data-foobar-kafka-broker-0
比如它叫 data-foobar-kafka-broker-0
。
第 2 步:挂载 PVC 到一个临时 Pod
因为 broker-0 自己起不来,我们搞一个临时容器来操作挂载的 PVC。
新建一个临时 pod(比如叫 debug-pod
):
yaml
apiVersion: v1
kind: Pod
metadata:
name: debug-pod
spec:
containers:
- name: debug
image: busybox
command:
- sh
- -c
- "sleep 3600"
volumeMounts:
- mountPath: /data
name: kafka-data
restartPolicy: Never
volumes:
- name: kafka-data
persistentVolumeClaim:
claimName: data-foobar-kafka-broker-0
保存成 debug-pod.yaml
,然后应用:
bash
kubectl apply -f debug-pod.yaml
第 3 步:清理数据
进入 debug-pod
:
bash
kubectl exec -it debug-pod -- sh
进去以后就能看到 Kafka 的数据了,比如在 /data
下面。
bash
mv $KAFKA_LOG_DIR/__cluster_metadata-0 $KAFKA_LOG_DIR/__cluster_metadata-0-bak
NFS 直接操作
bash
kubectl get pvc | grep kafka
bash
kubectl get pv xxx -o yaml