[消息中间件]kafka(伪集群模式+启动)

196 阅读5分钟

假如你是一名以python语言作为开发语言的程序员,

你想使用kafka这个功能强大的消息中间件来处理一些业务逻辑,你自己编写业务处理逻辑代码,比如生产环节,消费环节等.

因为你是使用python语言的, 所以你可能会用到confluent-kafka-python(基于C库librdkafka的高性能客户端), 或者是kafka-python(纯Python实现的客户端,该库包的安装方式是 pip install kafka-python, 而不是pip install kafka),

但是不管你用的是哪一个库包, 它们都仅仅是一个客户端库,它本身不包含Kafka服务端的实现逻辑。

也就是说, 如果你仅仅使用kafka-python这个客户端, 你并不能把消息发送到kafka中, 因为你并没有建立客户端和服务端之间的链接, 没有地方来承接这个消息.

所以, 你还是需要有一个Kafka服务端, 并且启动该服务, 来接收生产者发送的消息并传递给消费者。

也就是说, 你需要在本地搭建kafka环境, 并且把kafka服务启动起来.

我只有一台电脑,但是我想搭建一个kafka集群,所以我在一台机器上创建几个broker, 和正规的集群(有多台机器,每个机器是一个broker)不同,所以叫做伪集群.

在单台机器上搭建Kafka集群(称为伪集群)是完全可行的.

在Mac上使用KRaft模式(即无ZooKeeper)搭建Kafka 4.0.0伪集群,能获得更简单的架构和更快的启动速度。核心步骤是:准备环境 -> 下载安装 -> 生成集群UUID -> 为每个节点配置角色 -> 格式化存储 -> 依次启动 -> 验证与操作

以下是详细步骤:

1/准备环境与安装Kafka 4.0.0

(1)确保Java 8+:在终端输入 java -version 确认。如需安装,可运行 brew install openjdk@17

(2)下载Kafka 4.0.0

* 前往 [Apache Kafka官网下载页](https://kafka.apache.org/downloads)。
* 下载 **`kafka_2.13-4.0.0.tgz`**(`2.13`是Scala版本,不影响使用)。
* 解压并移动到方便操作的目录,例如:
 ```bash
    tar -xzf ~/Downloads/kafka_2.13-4.0.0.tgz
    mv kafka_2.13-4.0.0 ~/kafka-kraft-cluster
    cd ~/kafka-kraft-cluster
 ```
* 添加到环境变量(.zshrc文件)
  ```bash
    export KAFKA_HOME="XXX"
    export PATH="$KAFKA_HOME/bin:$PATH"
  ```
    

(3)还有一种方式下载kafka

wget archive.apache.org/dist/kafka/…

这种方式有一个问题:就是耗时太长(建议使用上面官网下载方式)

2/配置节点

(1)node1.properties配置

# ==================== 基础配置 ====================
node.id=1
process.roles=broker,controller

# ==================== 监听器配置 ====================
listeners=PLAINTEXT://localhost:19092,CONTROLLER://localhost:9093
advertised.listeners=PLAINTEXT://localhost:19092
inter.broker.listener.name=PLAINTEXT

# ==================== 控制器配置 ====================
controller.quorum.voters=1@localhost:9093,2@localhost:9094,3@localhost:9095
controller.listener.names=CONTROLLER

# ==================== KRaft配置 ====================
controller.quorum.election.timeout.ms=1000
controller.quorum.fetch.timeout.ms=1000
controller.quorum.request.timeout.ms=2000

# ==================== 存储配置 ====================
log.dirs=/path/to/your/kafka-cluster/node1/logs
metadata.log.dir=/path/to/your/kafka-cluster/node1/kraft

# ==================== 日志配置 ====================
num.partitions=3
default.replication.factor=3
min.insync.replicas=2
offsets.topic.replication.factor=3
transaction.state.log.replication.factor=3
transaction.state.log.min.isr=2

# ==================== 服务器配置 ====================
num.io.threads=8
num.network.threads=3
num.replica.fetchers=2
socket.send.buffer.bytes=102400
socket.receive.buffer.bytes=102400
socket.request.max.bytes=104857600

# ==================== 日志保留策略 ====================
log.retention.hours=168
log.segment.bytes=1073741824
log.retention.check.interval.ms=300000

# ==================== ZooKeeper迁移配置 ====================
# 因为是纯KRaft模式,不需要以下配置
# zookeeper.connect=
# zookeeper.connection.timeout.ms=

(2)node2.properties配置

# ==================== 基础配置 ====================
node.id=2
process.roles=broker,controller

# ==================== 监听器配置 ====================
listeners=PLAINTEXT://localhost:19093,CONTROLLER://localhost:9094
advertised.listeners=PLAINTEXT://localhost:19093
inter.broker.listener.name=PLAINTEXT

# ==================== 控制器配置 ====================
controller.quorum.voters=1@localhost:9093,2@localhost:9094,3@localhost:9095
controller.listener.names=CONTROLLER

# ==================== 存储配置 ====================
log.dirs=/path/to/your/kafka-cluster/node2/logs
metadata.log.dir=/path/to/your/kafka-cluster/node2/kraft

# 其他配置与节点1相同...

(3)node3.properties配置

# ==================== 基础配置 ====================
node.id=3
process.roles=broker,controller

# ==================== 监听器配置 ====================
listeners=PLAINTEXT://localhost:19094,CONTROLLER://localhost:9095
advertised.listeners=PLAINTEXT://localhost:19094
inter.broker.listener.name=PLAINTEXT

# ==================== 控制器配置 ====================
controller.quorum.voters=1@localhost:9093,2@localhost:9094,3@localhost:9095
controller.listener.names=CONTROLLER

# ==================== 存储配置 ====================
log.dirs=/path/to/your/kafka-cluster/node3/logs
metadata.log.dir=/path/to/your/kafka-cluster/node3/kraft

# 其他配置与节点1相同...

3/格式化存储目录

(1)生成集群ID

重要:首次启动前,必须为每个节点格式化其数据目录。

    ./bin/kafka-storage.sh random-uuid
该命令会输出一个UUID(如 `W5oG0H4oTjO-b2SuNq5Dzg`),**请记下它**,后续步骤会用到。

(2)为所有节点格式化存储(假设集群ID为 W5oG0H4oTjO-b2SuNq5Dzg

# 格式化node1的存储目录
./bin/kafka-storage.sh format -t W5oG0H4oTjO-b2SuNq5Dzg -c config/node1.properties
# 格式化node2的存储目录
./bin/kafka-storage.sh format -t W5oG0H4oTjO-b2SuNq5Dzg -c config/node2.properties
# 格式化node3的存储目录
./bin/kafka-storage.sh format -t W5oG0H4oTjO-b2SuNq5Dzg -c config/node3.properties

4/分步启动集群

按顺序启动节点(请打开多个终端标签页):

  • 终端1 - 启动node1 (Controller)

    ./bin/kafka-server-start.sh config/node1.properties
    

    等待其日志输出“Ready to serve as the controller”,表示控制器已就绪。

  • 终端2 - 启动node2 (Broker)

    ./bin/kafka-server-start.sh config/node2.properties
    
  • 终端3 - 启动node3 (Broker)

    ./bin/kafka-server-start.sh config/node3.properties
    

5/一次性启动/关闭集群

(1)启动集群sh脚本:

  #!/bin/zsh                                                                      
  
  # 生成集群ID(首次运行时使用)
  CLUSTER_ID=$(./bin/kafka-storage.sh random-uuid)
  
  echo "集群ID: $CLUSTER_ID"
  
  # 格式化存储目录(首次运行需要)
  echo "格式化存储目录..."
  ./bin/kafka-storage.sh format -t $CLUSTER_ID -c config/node1.properties
  ./bin/kafka-storage.sh format -t $CLUSTER_ID -c config/node2.properties
  ./bin/kafka-storage.sh format -t $CLUSTER_ID -c config/node3.properties
  
  # 启动集群(在三个不同的终端中)
  echo "启动节点1..."
  ./bin/kafka-server-start.sh config/node1.properties &
 
  echo "等待5秒..."
  sleep 5
 
  echo "启动节点2..."
  ./bin/kafka-server-start.sh config/node2.properties &
  
  echo "等待5秒..."
  sleep 5
  
  echo "启动节点3..."
  ./bin/kafka-server-start.sh config/node3.properties &
 
  echo "集群启动完成!"

(2)关闭集群sh脚本:

#!/bin/bash
echo "Stopping Kafka KRaft Cluster..."

# 优雅停止
pkill -f 'kafka-server-start.sh'

# 强制停止(如果优雅停止无效)
# kill -9 $(ps aux | grep kraft-server.properties | grep -v grep | awk '{print $2}')

echo "Cluster stopped."

6/总结关键点

  1. 每个节点必须有唯一的端口组合(这里指的是listeners参数中的PLAINTEXT和CONTROLLER)

  2. controller.quorum.voters 必须完全相同

  3. 数据目录必须不同

  4. 节点ID必须唯一

  5. 不要复用端口,一台机器上的端口不能重复绑定

  6. controller.quorum.voters:必须包含所有controller节点, 如果不是controller,则不能添加

  7. process.roles:指定节点角色(broker, controller或两者)

  8. node.id:每个节点必须有唯一ID

  9. listeners:需要配置两个监听器,一个用于broker通信,一个用于controller通信

  10. 集群ID:所有节点必须使用相同的集群ID

7/故障排除和优化

(1)端口冲突

# 检查端口占用
lsof -i :19092,19093,19094,9093,9094,9095

# 如果端口被占用,修改配置文件中的端口号

(2)启动超时

# 增加选举超时时间
controller.quorum.election.timeout.ms=3000
controller.quorum.fetch.timeout.ms=3000

(3)性能优化建议

# JVM调整(在启动脚本中设置)
export KAFKA_HEAP_OPTS="-Xms2g -Xmx2g"
export KAFKA_JVM_PERFORMANCE_OPTS="-server -XX:+UseG1GC -XX:MaxGCPauseMillis=20 -XX:InitiatingHeapOccupancyPercent=35 -XX:+ExplicitGCInvokesConcurrent -Djava.awt.headless=true"

# 文件描述符限制(Mac)
sudo launchctl limit maxfiles 65536 65536
ulimit -n 65536

8/关键参数解释

  1. process.roles

    • broker,controller:节点同时承担两种角色
    • 纯controller节点:controller
    • 纯broker节点:broker
  2. controller.quorum.voters

    • 格式:node_id@host:port
    • 所有controller节点必须在此列出
    • 端口用于controller间通信
  3. listeners配置

    • PLAINTEXT:客户端连接端口
    • CONTROLLER:KRaft控制器间通信端口
  4. 存储路径

    • log.dirs:消息数据存储
    • metadata.log.dir:KRaft元数据存储(与数据分离)