kill -9 模拟消费者崩溃,观察会话超时触发的 Rebalance 与分区接管。max.poll.interval.ms 和分区分配停滞。隔离边界:只使用本机 Docker Compose 的 lab-kafka 项目和 127.0.0.1:9092,不要连接或修改任何生产 Kafka 集群。需要 Docker Compose v2,预留约 1 GB 内存。
docker version --format '{{.ServerVersion}}' && docker compose version
mkdir -p /tmp/lab-kafka
cat > /tmp/lab-kafka/docker-compose.yml <<'EOF'
name: lab-kafka
services:
kafka:
image: apache/kafka:3.7.0
container_name: lab-kafka
ports: ["127.0.0.1:9092:9092"]
environment:
KAFKA_NODE_ID: 1
KAFKA_PROCESS_ROLES: broker,controller
KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://127.0.0.1:9092
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@localhost:9093
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0
EOF
docker compose -f /tmp/lab-kafka/docker-compose.yml up -d
for i in $(seq 1 60); do
docker exec lab-kafka /opt/kafka/bin/kafka-topics.sh \
--bootstrap-server localhost:9092 --list >/dev/null 2>&1 && break
sleep 2
done
第一次启动 KRaft 需要初始化存储并拉取镜像,等待循环最多约 2 分钟。
先建 3 分区 topic,再启动两个同组消费者;c1 会话超时 30 秒,c2 为 10 秒。
docker exec lab-kafka /opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:9092 \
--create --if-not-exists --topic lab-rebalance --partitions 3 --replication-factor 1
docker exec -d lab-kafka sh -c 'echo $$ > /tmp/c1.pid; exec /opt/kafka/bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic lab-rebalance --group lab-group --consumer-property session.timeout.ms=30000 --consumer-property max.poll.interval.ms=30000 > /tmp/c1.log 2>&1'
docker exec -d lab-kafka sh -c 'echo $$ > /tmp/c2.pid; exec /opt/kafka/bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic lab-rebalance --group lab-group --consumer-property session.timeout.ms=10000 --consumer-property max.poll.interval.ms=30000 > /tmp/c2.log 2>&1'
sleep 8
docker exec lab-kafka /opt/kafka/bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group lab-group --members --verbose
确认两个成员分别拿到分区后,直接杀死 c2 进程,模拟没有 LeaveGroup 的崩溃:
docker exec lab-kafka sh -c 'kill -9 $(cat /tmp/c2.pid)'
sleep 3
docker exec lab-kafka /opt/kafka/bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group lab-group --members --verbose
sleep 12
docker exec lab-kafka /opt/kafka/bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group lab-group --members --verbose
docker exec lab-kafka /opt/kafka/bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group lab-group --state
docker exec lab-kafka sh -c 'tail -20 /tmp/c1.log'
预期初始两个成员分别持有 2 个和 1 个分区。c2 被杀后 3 秒,group 仍可能显示 Stable 且 c2 仍挂着分区,这是 broker 还没等到会话超时,属于“看起来没变化”的窗口。约 10 秒后 group 经历 PreparingRebalance/CompletingRebalance,最终 c1 接管全部 3 个分区;c1 日志出现再次入组和 assignment 变化的记录。只杀进程不走 LeaveGroup 时,分区不会立即释放。
max.poll.interval.ms 超限,还是消费停滞但成员仍存活。不同原因对应不同的 group 状态和成员列表。--describe --members --verbose 看成员数、每个成员的分配和 group 状态;用 --state 确认是否处于 PreparingRebalance/CompletingRebalance。JoinGroup、LeaveGroup、RejoinGroup、Heartbeat。kill -9 不会发送 LeaveGroup,必须等 session.timeout.ms 到期;正常关闭会更快触发 rebalance。max.poll.interval.ms 和单次 poll 的处理耗时。超过上限时客户端会离开组并重新加入,日志常见 MAX_POLL_EXCEEDED 或 rebalance 记录。Stable 就认为消费者健康。成员可能因心跳线程还在而保持存活,但业务线程已经卡住;反过来,Rebalance 期间短暂 PreparingRebalance 并不一定是故障。另一个误判是认为分区数固定就一定能均匀分配,成员数多于分区数时必然有人分不到。重启被杀的消费者,让它重新加入组:
docker exec -d lab-kafka sh -c 'echo $$ > /tmp/c2.pid; exec /opt/kafka/bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic lab-rebalance --group lab-group --consumer-property session.timeout.ms=10000 --consumer-property max.poll.interval.ms=30000 > /tmp/c2.log 2>&1'
sleep 10
docker exec lab-kafka /opt/kafka/bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group lab-group --members --verbose
预期成员数恢复为 2,分区重新分配为 2/1。生产中要保证消费者优雅停机(先停止 poll、提交 offset、发送 LeaveGroup),并根据处理耗时设置 max.poll.interval.ms,避免频繁 rebalance。
docker exec lab-kafka sh -c 'kill -9 $(cat /tmp/c1.pid) $(cat /tmp/c2.pid) 2>/dev/null || true'
docker compose -f /tmp/lab-kafka/docker-compose.yml down -v --remove-orphans
rm -rf /tmp/lab-kafka
只删除本实验的 Compose 项目和本地数据卷;不要对生产 Kafka 执行 kill 或删除 topic。
kill -9 后分区没有立刻转移,释放时间由什么参数决定?max.poll.interval.ms 设得过小或过大分别有什么风险?kill -9 后,为什么 group 不会立即 rebalance?session.timeout.ms 与 max.poll.interval.ms 分别约束什么?