找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

5488

积分

0

好友

731

主题
发表于 2 小时前 | 查看: 3| 回复: 0

10多年前的游戏后端消息队列从 Kafka 0.x 用到 3.x,之前都是老老实实上 ZooKeeper 集群模式。这次测试 Kafka 3.8.0 的 KRaft 模式,本以为能省点心,结果还是踩了几个坑,单独开一篇记录一下,方便后续查阅。

一、为什么选择 KRaft 模式

以前 Kafka 强依赖 ZooKeeper 来做元数据管理,部署的时候不仅要搞定 Broker,还得额外维护一套 ZK 集群,链路长、出问题的概率也高。KRaft 模式下,Kafka 自己通过内部选举搞定 Controller 选主,彻底摆脱了对 ZooKeeper 的依赖。想用 Docker 快速拉起一个测试环境?一把梭就行。

当然了,这次用单节点纯粹是为了测试环境验证。生产环境的游戏服日均消息量是千万级的,真要上线还是得老老实实上多节点集群。

二、Docker Compose 部署配置

完整 docker-compose.yml 如下:

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 天保留
  • 日均消息量:千万级

初始化脚本 init-topics.sh 内容如下:

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 这个内部 Topic 必须手动创建,KRaft 单节点模式可不会像以前 ZooKeeper 模式那样自动帮你生成。这是后续导致消费者组无法工作的最常见原因,没有之一。

四、踩坑记录

坑 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 不存在

症状:消费者组无法启动,日志里没有任何 kafkaInitsubscribepoll 的痕迹,像是根本没连上。

原因:KRaft 单节点下,__consumer_offsets 这个消费者组元数据存储的内部 Topic 不会自动创建,导致协调者(Group Coordinator)没地方写元数据。

解决:手动执行创建命令即可:

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,消费者拿不到 systembusiness 这两个 Topic 的访问权限。

原因:消费者实例的 ACL 权限配置不完整,光有 Broker 连接权限没用,还缺对业务 Topic 的 Read 授权。

解决:检查并补全消费者对相关 Kafka Topic 的 Read 权限配置。

五、验证集群

编写一个验证脚本 verify.sh,一键检查集群状态:

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 不设上限

七、总结

  1. KRaft 模式下 __consumer_offsets 必须手动创建,这是和传统 ZooKeeper 模式部署体验上最大的区别。
  2. advertised.listeners 一定要填外部可访问的真实地址,千万别图省事用 localhost 或容器内部 IP,否则消费者根本找不到回来的路。
  3. 消费者权限要单独配全,别以为配了 Broker 连接权限就万事大吉,Topic 级别的 Read 权限也得跟上。

如果在技术选型时想了解更多关于 Kafka 或其他消息中间件的实践经验,可以在云栈社区与大家交流探讨。




上一篇:Agent 设计九个关键:状态外化、上下文压缩与多智能体协同
下一篇:AI大模型缓存揭秘:KV Cache与Prompt Caching到底缓存什么?
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-5-23 05:42 , Processed in 0.627079 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表