12.2 部署与运维

eventd v3 的部署与运维:Flink Standalone 与 Docker 两种部署方式、TaskManager 内存与 Slot 配置、dev/staging/prod 环境管理、Savepoint 升级与滚动重启步骤,以及作业、Kafka lag、Redis 连接的日常检查清单。

部署模式选择

eventd v3 的三个模块有不同的部署方式:

模块部署方式理由
eventd-cleansingJAR + systemd / DockerKafka Streams 是库,不是集群
eventd-analyticsFlink Standalone / DockerFlink 作业提交到集群
eventd-panelDocker / K8s Deployment无状态 Web 服务

Standalone 部署

# 1. JobManager
flink-daemon.sh start jobmanager

# 2. TaskManager(每台机器)
flink-daemon.sh start taskmanager

# 3. 提交作业
flink run \
    --class com.plume.eventd.AnalyticsJob \
    --parallelism 4 \
    --detached \
    eventd-analytics-1.0.jar

# 4. 查看作业
flink list

TaskManager 内存配置

# flink-conf.yaml
taskmanager.memory.process.size: 4g         # TM 总内存
taskmanager.memory.managed.fraction: 0.4    # 40% 给托管内存(RocksDB)
taskmanager.memory.network.fraction: 0.15   # 网络缓冲
taskmanager.memory.jvm-overhead.fraction: 0.2  # JVM 开销

managed / network 的比例是相对 Total Flink Memory,jvm-overhead 是相对进程总内存;直接相加会算错,完整的拆分见 14.2 故障排查 。

Docker 容器化

基础镜像统一用 Java 17。Flink 1.20 官方验证的是 Java 11 和 17;JDK 21 及以上属于未验证组合,可能在提交作业或反序列化时失败。Java 应用用 eclipse-temurin:17-jdk-alpine,Flink 侧直接用官方镜像的 flink:1.20-scala_2.12-java17 变体,别自己往 flink:1.20-scala_2.12(默认 Java 11)里换 JDK。

eventd-cleansing Dockerfile

FROM eclipse-temurin:17-jdk-alpine
WORKDIR /app
COPY target/eventd-cleansing-1.0.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]

eventd-analytics Dockerfile

# 直接用 Flink 官方镜像:里面的 flink 客户端、JAVA_HOME 和依赖都已就绪
FROM flink:1.20-scala_2.12-java17
WORKDIR /opt/flink/usrlib
COPY target/eventd-analytics-1.0.jar app.jar

# 提交作业(假设 JobManager 在 flink-jobmanager:8081)
CMD ["flink", "run", \
     "-m", "flink-jobmanager:8081", \
     "-c", "com.plume.eventd.AnalyticsJob", \
     "app.jar"]

不要在 eclipse-temurin 基础镜像里临时下载 Flink 发行包:-alpine 镜像默认没有 wget,而且每次启动都要重新下载,构建结果不可复现。

eventd-panel Dockerfile

FROM eclipse-temurin:17-jdk-alpine
WORKDIR /app
COPY target/eventd-panel-1.0.jar app.jar
EXPOSE 8083
ENTRYPOINT ["java", "-jar", "app.jar"]

docker-compose.yml

services:
  kafka:
    image: confluentinc/cp-kafka:7.8.0
    hostname: kafka
    ports:
      - "9092:29092"          # 宿主机 9092 → 容器内 PLAINTEXT_HOST 监听
    environment:
      # 7.4 起默认 KRaft,不再需要单独的 Zookeeper 容器。
      # 这是单节点演示配置;生产至少 3 个 controller。
      CLUSTER_ID: MkU3OEVBNTcwNTJENDM2Qk
      KAFKA_NODE_ID: 1
      KAFKA_PROCESS_ROLES: broker,controller
      KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:29093
      KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
      # 两个数据监听器:容器之间用 kafka:9092,宿主机用 localhost:9092。
      # 只配一个监听器时,另一侧拿到的是不可解析的地址,客户端会一直重连
      KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:29093,PLAINTEXT_HOST://0.0.0.0:29092
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:9092
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
      KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
      KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

  flink-jobmanager:
    image: flink:1.20-scala_2.12-java17
    ports:
      - "8081:8081"
    command: jobmanager
    environment:
      - JOB_MANAGER_RPC_ADDRESS=flink-jobmanager

  flink-taskmanager:
    image: flink:1.20-scala_2.12-java17
    command: taskmanager
    environment:
      - JOB_MANAGER_RPC_ADDRESS=flink-jobmanager
      - TASK_MANAGER_NUMBER_OF_TASK_SLOTS=4

  cleansing:
    build: ./eventd-cleansing
    environment:
      # 容器内不能写 localhost:localhost 指向容器自己
      - KAFKA_BOOTSTRAP_SERVERS=kafka:9092
    depends_on:
      - kafka

  analytics:
    build: ./eventd-analytics
    environment:
      - KAFKA_BOOTSTRAP_SERVERS=kafka:9092
      - REDIS_HOST=redis
    depends_on:
      - kafka
      - redis
      - flink-jobmanager

  panel:
    build: ./eventd-panel
    environment:
      - REDIS_HOST=redis
      - SPRING_DATA_REDIS_HOST=redis
    ports:
      - "8083:8083"
    depends_on:
      - redis

depends_on 只保证容器的启动顺序,不保证 Kafka 已能建 Topic、JobManager 已能接受提交;analytics 容器第一次 flink run 很可能因为 JM 还没起来而失败。生产环境要配 healthcheck + restart: on-failure,或者在启动脚本里轮询 http://flink-jobmanager:8081/overview 直到就绪。

Savepoint 与作业升级

触发 Savepoint

# 1. 获取当前作业 ID
flink list

# 2. 触发 Savepoint
flink savepoint <job-id> file:///var/flink/savepoints

# 3. 停止作业
flink cancel <job-id>

# 4. 从 Savepoint 启动新版本
flink run \
    --fromSavepoint file:///var/flink/savepoints/savepoint-xxxxx \
    --class com.plume.eventd.AnalyticsJob \
    eventd-analytics-1.1.jar

Savepoint 升级注意事项

兼容的变更:
  ✅ 新增算子(新增分支)
  ✅ 修改业务逻辑(不改变状态类型)
  ✅ 调整并行度

不兼容的变更:
  ❌ 删除算子
  ❌ 修改 KeyedState 的类型
  ❌ 修改窗口类型

如果变更不兼容:

  1. 先确认这部分状态可以丢,再考虑 --allowNonRestoredState。它会让 Flink 跳过无法映射的状态,作业能起来,但计数类状态会从 0 重新累计,而且只在日志里留一条警告。
  2. 对账、计费这类不能丢状态的场景,正确做法是让新作业从明确的 Kafka offset 重放,重算结果,而不是静默丢弃旧状态。

配置分层

eventd v3 的环境配置:

config/
├── application-dev.yaml     # 开发环境
├── application-staging.yaml # 测试环境
└── application-prod.yaml    # 生产环境

不同环境的差异:

配置devstagingprod
Kafka brokerlocalhost:9092kafka-staging:9092kafka-prod:9092
Flink parallelism1412
Checkpoint interval60s30s30s
Redislocalhost:6379redis-staging:6379redis-prod:6379
SSE connections1005005000

启动时指定环境:

# -D 必须写在 -jar 之前,否则会被当成传给应用的参数而失效
java -Dspring.profiles.active=prod -jar app.jar

运维检查清单

每日检查:

# Flink 作业运行状态
curl http://flink-jobmanager:8081/jobs

# Kafka Topic 消费进度(lag)
kafka-consumer-groups.sh --bootstrap-server kafka:9092 \
    --describe --group eventd-analytics

# Redis 内存使用
redis-cli INFO memory

# 面板 HTTP 健康检查
curl http://panel:8083/api/health

每周检查:

# Checkpoint 大小趋势
# → 持续增长说明 TTL 未生效或状态泄漏

# 死信队列消息数
kafka-run-class.sh kafka.tools.GetOffsetShell \
    --bootstrap-server kafka:9092 --topic eventd-dead-letter

# Flink 反压状态(Web UI)

监控告警

# prometheus/alerts.yml
groups:
  - name: eventd
    rules:
      - alert: FlinkCheckpointFailed
        # 该指标以 Gauge 暴露累计失败数(实现类是 FailedCheckpointsCounter
        # implements Gauge<Long>),不是 Counter;直接写 > 0 会让告警永远不恢复。
        # 值单调递增,所以 increase() 可用;看最近 10 分钟有没有新增失败
        expr: increase(flink_jobmanager_job_numberOfFailedCheckpoints[10m]) > 0
        for: 1m

      - alert: KafkaConsumerLag
        # kafka_exporter 的指标名与标签;用 JMX exporter 时名字不同。
        # 标签是 consumergroup,不是 group;写成 group= 会得到空向量
        expr: kafka_consumergroup_lag{consumergroup="eventd-analytics"} > 10000
        for: 5m

      - alert: RedisMemoryHigh
        # redis_exporter 没有直接给百分比,需要自己算;
        # 没配 maxmemory 时 redis_memory_max_bytes 为 0,除法会得到 NaN、告警永远不触发
        expr: redis_memory_max_bytes > 0
              and redis_memory_used_bytes / redis_memory_max_bytes * 100 > 80
        for: 5m

      - alert: PanelConnectionsHigh
        expr: eventd_panel_sse_connections > 4500
        for: 1m

小结

操作命令/方法
提交作业flink run --class ...
触发 Savepointflink savepoint <job-id>
恢复作业flink run --fromSavepoint ...
停止作业flink cancel <job-id>
查看日志JobManager/TaskManager 日志文件、Web UI 或 docker logs
健康检查Web UI / REST API

下一节 12.3 v3 验收 :全链路可运行、指标正确性、延迟达标。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「java」更多文章

  1. 15.3 全书总结与学习路径回顾
  2. 15.2 响应式流处理的演进趋势
  3. 15.1 引擎选型指南