4. 环境搭建与工具链:Docker Compose搭建开发环境、Kafka集群部署、Flink Standalone模式部署、MinIO对象存储

说实话,做增量计算最怕什么?不是算法难,是环境搭不起来。

我见过太多团队,代码写好了,结果在测试环境跑不起来。Kafka连不上,Flink内存不够,MinIO权限报错……一查就是半天。所以这一章,咱们把环境一次性搭稳。

本章核心目标:用 Docker Compose 一键拉起全套开发环境,包含 Kafka 集群、Flink Standalone 集群、MinIO 对象存储。你跟着我走一遍,以后新项目直接复用这个模板。

4.1 整体架构概览

先看整体结构。我们这套环境包含四个核心组件:

  • Kafka:消息队列,负责接收实时数据流
  • Flink:计算引擎,做增量处理
  • MinIO:对象存储,存状态快照和中间结果
  • Docker Compose:编排工具,把上面三个串起来

它们之间的关系,我画了张图:

增量计算开发环境架构图 增量计算开发环境架构图 数据源(模拟器/日志) Kafka 集群(3 Broker) Topic: input-data / checkpoint-topic Flink Standalone 集群 1 JobManager + 2 TaskManager(4 Slot 每个) MinIO 对象存储 💡 关键交互 Flink 消费 Kafka 数据 Flink 状态快照存 MinIO MinIO 兼容 S3 API

你看,数据从左边进来,经过 Kafka 缓冲,Flink 消费计算,结果状态存到 MinIO。这套架构我用了两年多,稳定得很。

4.2 Docker Compose 编排文件

我个人习惯把所有服务写在一个 docker-compose.yml 里。这样一条命令就能拉起全部环境。

下面是我常用的模板:

version: '3.8'

services:
  # ---------- Kafka 集群 ----------
  zookeeper:
    image: confluentinc/cp-zookeeper:7.4.0
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
      ZOOKEEPER_TICK_TIME: 2000
    ports:
      - "2181:2181"

  kafka1:
    image: confluentinc/cp-kafka:7.4.0
    depends_on:
      - zookeeper
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 3
    ports:
      - "9092:9092"

  kafka2:
    image: confluentinc/cp-kafka:7.4.0
    depends_on:
      - zookeeper
    environment:
      KAFKA_BROKER_ID: 2
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9093
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 3
    ports:
      - "9093:9093"

  kafka3:
    image: confluentinc/cp-kafka:7.4.0
    depends_on:
      - zookeeper
    environment:
      KAFKA_BROKER_ID: 3
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9094
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 3
    ports:
      - "9094:9094"

  # ---------- Flink Standalone 集群 ----------
  flink-jobmanager:
    image: flink:1.18.0-scala_2.12
    ports:
      - "8081:8081"
    command: jobmanager
    environment:
      - JOB_MANAGER_MEMORY=2048m
      - |
        FLINK_PROPERTIES=
        jobmanager.rpc.address: flink-jobmanager

  flink-taskmanager:
    image: flink:1.18.0-scala_2.12
    depends_on:
      - flink-jobmanager
    command: taskmanager
    environment:
      - TASK_MANAGER_MEMORY=4096m
      - |
        FLINK_PROPERTIES=
        jobmanager.rpc.address: flink-jobmanager
        taskmanager.numberOfTaskSlots: 4

  # ---------- MinIO 对象存储 ----------
  minio:
    image: minio/minio:latest
    ports:
      - "9000:9000"
      - "9001:9001"
    environment:
      MINIO_ROOT_USER: admin
      MINIO_ROOT_PASSWORD: admin123
    command: server /data --console-address ":9001"
    volumes:
      - minio-data:/data

volumes:
  minio-data:

💡 我的经验:Kafka 的 ADVERTISED_LISTENERS 一定要配对。如果你在本地跑,用 localhost;如果在服务器上,改成对应的内网 IP。我曾经因为这里写错,折腾了俩小时。

4.3 Kafka 集群部署详解

Kafka 集群为什么用 3 个 Broker?说白了,为了高可用。你想想看,如果只有一个 Broker,它挂了整个系统就瘫了。3 个 Broker 配合复制因子 3,任何一个节点宕机都不丢数据。

启动之后,建议先验证一下:

# 进入 Kafka 容器
docker exec -it kafka1 bash

# 创建一个测试 topic
kafka-topics --bootstrap-server localhost:9092 \
  --create --topic test-topic \
  --partitions 3 --replication-factor 3

# 查看 topic 详情
kafka-topics --bootstrap-server localhost:9092 \
  --describe --topic test-topic

输出应该类似这样:

Topic: test-topic  PartitionCount: 3  ReplicationFactor: 3
  Topic: test-topic  Partition: 0  Leader: 1  Replicas: 1,2,3  Isr: 1,2,3
  Topic: test-topic  Partition: 1  Leader: 2  Replicas: 2,3,1  Isr: 2,3,1
  Topic: test-topic  Partition: 2  Leader: 3  Replicas: 3,1,2  Isr: 3,1,2

看到 Isr 里三个 Broker 都在,说明集群正常。

⚠️ 避坑指南:我曾经在生产环境遇到过一个问题——Kafka 的 offsets.topic.replication.factor 没设成 3,结果某个 Broker 挂了之后,消费者组无法提交 offset,导致重复消费。所以这个参数一定要和 Broker 数量一致。

4.4 Flink Standalone 模式部署

Flink 的部署模式有好几种,为什么我选 Standalone?

原因很简单:开发环境不需要 YARN 或 Kubernetes。Standalone 模式轻量、启动快、调试方便。你本地开发用这个最合适。

启动之后,访问 http://localhost:8081 就能看到 Flink Web UI。我建议你重点关注这几个指标:

指标 正常值 说明
Task Slots 8(2个 TaskManager × 4 Slot) 并行度上限
JobManager 内存 2 GB 管理调度用,够用就行
TaskManager 内存 4 GB 每个 TaskManager,根据数据量调整
可用 TaskManager 2 至少 2 个才能体现分布式

提交一个测试任务试试:

# 下载 Flink 客户端(如果本地没有)
wget https://archive.apache.org/dist/flink/flink-1.18.0/flink-1.18.0-bin-scala_2.12.tgz
tar -xzf flink-1.18.0-bin-scala_2.12.tgz

# 提交一个示例任务
./flink-1.18.0/bin/flink run \
  -m localhost:8081 \
  ./flink-1.18.0/examples/streaming/SocketWindowWordCount.jar \
  --hostname localhost --port 9999

💡 个人习惯:我会在 flink-conf.yaml 里把 taskmanager.numberOfTaskSlots 设成 4,而不是默认的 1。为什么?因为增量计算任务通常需要多个算子并行,Slot 太少会导致任务排队,白白浪费资源。

4.5 MinIO 对象存储配置

MinIO 在这套环境里扮演什么角色?说白了,就是 Flink 的「状态后端」。Flink 做增量计算时,需要定期把状态快照保存下来。如果存本地磁盘,容器一重启就没了。存 MinIO,数据持久化,还能跨集群共享。

启动之后,访问 http://localhost:9001,用 admin / admin123 登录。然后创建一个 Bucket:

# 用 mc 客户端操作
docker exec -it minio bash

# 安装 mc
wget https://dl.min.io/client/mc/release/linux-amd64/mc
chmod +x mc
./mc alias set myminio http://localhost:9000 admin admin123

# 创建 bucket
./mc mb myminio/flink-checkpoints
./mc mb myminio/flink-savepoints

然后在 Flink 的 flink-conf.yaml 里配置状态后端:

state.backend: filesystem
state.checkpoints.dir: s3://flink-checkpoints
state.savepoints.dir: s3://flink-savepoints
s3.access-key: admin
s3.secret-key: admin123
s3.endpoint: http://minio:9000

⚠️ 注意:Flink 连接 MinIO 需要依赖 flink-s3-fs-hadoop 插件。记得把 jar 包放到 flink/lib 目录下。我刚开始用的时候忘了这一步,结果状态后端一直报错,查了半天才发现。

4.6 一键启动与验证

所有配置都准备好了,启动就一行命令:

docker-compose up -d

等个 30 秒左右,验证所有服务:

服务 验证方式 预期结果
Kafka docker exec kafka1 kafka-broker-api-versions --bootstrap-server localhost:9092 返回 API 版本列表
Flink 浏览器访问 http://localhost:8081 看到 Flink Web UI
MinIO 浏览器访问 http://localhost:9001 看到 MinIO 控制台

🎯 最终检查:在 Flink Web UI 里提交一个简单的 Kafka 消费任务,看能不能从 Kafka 读到数据,然后状态快照能不能写到 MinIO。如果能跑通,恭喜你,环境搭建完成!

这套环境我用了快三年,从 Flink 1.13 一直升级到 1.18,基本没出过问题。你把它保存好,以后每个新项目都能复用。