10多年前的游戏后端消息队列从 Kafka 0.x 用到 3.x,之前都是使用ZooKeeper集群模式部署的。这次测 Kafka 3.8.0 KRaft 模式踩了几个坑,单独开一篇记录。
一、为什么用 KRaft
之前 Kafka 依赖 ZooKeeper 做元数据管理,部署麻烦还要额外部署 ZK 集群。KRaft 模式下 Kafka 自己搞定选主,不再需要 ZooKeeper,Docker 一把梭就行。
单节点够用是因为这次只是测试环境验证,生产游戏服日均千万级消息还是要上多节点集群。
二、Docker Compose 部署
version: '3.8'
services:
kafka:
image: apache/kafka:3.8.0
container_name: kafka
network_mode: host
environment:
KAFKA_NODE_ID: 1
KAFKA_PROCESS_ROLES: broker,controller
KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://<服务器IP>:9092
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@0.0.0.0:9092
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
KAFKA_LOG_DIRS: /opt/kafka/logs
KAFKA_AUTO_CREATE_TOPICS_ENABLE: "false"
volumes:
- kafka-data:/opt/kafka/logs
volumes:
kafka-data:
关键参数说明:
| 参数 | 值 | 作用 |
|---|---|---|
KAFKA_PROCESS_ROLES |
broker,controller |
单节点同时担任 broker 和 controller |
KAFKA_ADVERTISED_LISTENERS |
<服务器IP>:9092 |
这里踩坑了,详见第四节 |
KAFKA_AUTO_CREATE_TOPICS_ENABLE |
false |
禁止自动创建 topic,避免乱序问题 |
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR |
1 |
单节点设为 1 |
启动:
docker-compose up -d
# 等待 60 秒让 KRaft 选主完成(首次启动需要 generate UUID)
docker exec kafka bash -c "sleep 60 && echo 'KRaft ready'"
三、初始化 Topic
先说清楚这次的业务需求:
business:业务日志,12 分区,7 天保留system:系统日志,6 分区,7 天保留- 日均消息量:千万级
cat > init-topics.sh << 'EOF'
#!/bin/bash
TOPIC_BUSINESS="business"
TOPIC_SYSTEM="system"
PARTITIONS_BUSINESS=12
PARTITIONS_SYSTEM=6
RETENTION_MS=604800000 # 7天
KAFKA_BIN="/opt/kafka/bin"
CONTAINER_NAME="kafka"
# 手动创建 __consumer_offsets(KRaft 单节点不自动创建)
docker exec ${CONTAINER_NAME} ${KAFKA_BIN}/kafka-topics.sh \
--create \
--topic __consumer_offsets \
--partitions 50 \
--config cleanup.policy=compact \
--if-not-exists
# 创建业务 topic
docker exec ${CONTAINER_NAME} ${KAFKA_BIN}/kafka-topics.sh \
--create \
--topic ${TOPIC_BUSINESS} \
--partitions ${PARTITIONS_BUSINESS} \
--replication-factor 1 \
--config retention.ms=${RETENTION_MS}
# 创建系统 topic
docker exec ${CONTAINER_NAME} ${KAFKA_BIN}/kafka-topics.sh \
--create \
--topic ${TOPIC_SYSTEM} \
--partitions ${PARTITIONS_SYSTEM} \
--replication-factor 1 \
--config retention.ms=${RETENTION_MS}
echo "Topic 初始化完成"
docker exec ${CONTAINER_NAME} ${KAFKA_BIN}/kafka-topics.sh --list
EOF
chmod +x init-topics.sh
./init-topics.sh
注意:__consumer_offsets 必须手动创建,KRaft 单节点模式不会自动生成。这是导致消费者组无法工作的最常见原因。
四、踩坑记录
坑 1:advertised.listeners 配置错误
症状:本地测试生产者发消息成功,但消费者组始终无法注册,kafka consumer 日志里没有任何 poll 操作。
原因:测试服 SSH 进去后用 localhost:9092 发消息,而 advertised.listeners 配置的是容器内部地址,外部客户端拿到的是错误的引导地址,后续请求全部走不通。
排查过程:
# 查看消费者组状态——无注册信息
docker exec kafka /opt/kafka/bin/kafka-consumer-groups.sh \
--bootstrap-server localhost:9092 \
--list
# 对比测试服发消息时用的配置
# acks=0, retries=0,通过 localhost:9092 发送 —— 消息静默丢失了
解决:把 advertised.listeners 改成测试服真实 IP,重新部署,重启 GameServer 用正确的 bootstrap.servers 验证链路。
坑 2:__consumer_offsets 不存在
症状:消费者组无法启动,日志显示没有 kafkaInit、subscribe、poll 任何一步。
原因:KRaft 单节点默认不自动创建 __consumer_offsets 内部 topic,导致消费者组元数据无处存储。
解决:手动创建
docker exec kafka /opt/kafka/bin/kafka-topics.sh \
--create \
--topic __consumer_offsets \
--partitions 50 \
--config cleanup.policy=compact
创建后消费者组注册成功,开始正常消费 business(24条)和 system(4条)消息。
坑 3:TOPIC_AUTHORIZATION_FAILED
症状:clog 服务报 errorCode=17,consumer 没有权限访问 日志服system 和 日志服business 这两个 topic。
原因:consumer 的 ACL 权限配置不完整,缺少对业务 topic 的访问权限。
解决:检查并补全 consumer 的 topic 授权,确保有 read 权限。
五、验证集群
cat > verify.sh << 'EOF'
#!/bin/bash
CONTAINER_NAME="kafka"
KAFKA_BIN="/opt/kafka/bin"
echo "=== 检查 broker 是否就绪 ==="
docker exec ${CONTAINER_NAME} ${KAFKA_BIN}/kafka-broker-api-versions.sh \
--bootstrap-server localhost:9092
echo "=== 列出所有 topic ==="
docker exec ${CONTAINER_NAME} ${KAFKA_BIN}/kafka-topics.sh \
--list --bootstrap-server localhost:9092
echo "=== 测试生产者 ==="
docker exec ${CONTAINER_NAME} ${KAFKA_BIN}/kafka-console-producer.sh \
--bootstrap-server localhost:9092 \
--topic business \
--property "parse.key=true" \
--property "key.separator=:"
echo "=== 测试消费者 ==="
docker exec -d ${CONTAINER_NAME} ${KAFKA_BIN}/kafka-console-consumer.sh \
--bootstrap-server localhost:9092 \
--topic business \
--from-beginning \
--property "print.key=true"
EOF
chmod +x verify.sh
./verify.sh
六、参数自动优化依据
根据日均千万级消息量自动计算的参数:
| 参数 | 值 | 计算逻辑 |
|---|---|---|
num.network.threads |
8 | CPU 核数 × 2 |
num.io.threads |
16 | CPU 核数 × 4 |
socket.send.buffer.bytes |
2097152 | 2MB |
socket.receive.buffer.bytes |
2097152 | 2MB |
log.retention.hours |
168 | 7天 |
log.retention.bytes |
-1 | 不设上限 |
七、总结
- KRaft 模式下
__consumer_offsets必须手动创建,这是和 ZooKeeper 模式最大的区别 - **
advertised.listeners必须是外部可访问的地址**,不能用localhost或容器内部地址 - 消费者权限要单独配置,不能只配 broker 访问权限
评论区