完整教程:Apache Flink 实战

Apache Flink 实战:从入门到生产实践

目录


1. Flink 简介

Apache Flink 是一个开源的分布式流处理框架,专为高吞吐量、低延迟的实时数据处理而设计。Flink 提供了精确一次(Exactly-Once)的状态一致性保证,是目前最流行的流计算引擎之一。

1.1 Flink 的核心特性

  • 真正的流处理: Flink 采用原生流处理架构,而非微批处理
  • 精确一次语义: 通过分布式快照机制保证状态一致性
  • 事件时间处理: 支持基于事件时间的窗口计算
  • 低延迟高吞吐: 毫秒级延迟,每秒百万级事件处理能力
  • 灵活的窗口机制: 支持滚动、滑动、会话等多种窗口类型
  • 丰富的连接器: 支持Kafka、HDFS、MySQL、Elasticsearch等

1.2 Flink vs Spark Streaming

特性对比
┌────────────────┬──────────────────┬──────────────────┐
│   Feature      │   Flink          │  Spark Streaming │
├────────────────┼──────────────────┼──────────────────┤
│ 处理模型       │   流处理         │   微批处理       │
├────────────────┼──────────────────┼──────────────────┤
│ 延迟           │   毫秒级         │   秒级           │
├────────────────┼──────────────────┼──────────────────┤
│ 吞吐量         │   高             │   很高           │
├────────────────┼──────────────────┼──────────────────┤
│ 状态管理       │   原生支持       │   需要额外组件   │
├────────────────┼──────────────────┼──────────────────┤
│ 事件时间       │   一级支持       │   有限支持       │
└────────────────┴──────────────────┴──────────────────┘

2. Flink 核心概念

2.1 数据流与转换

Flink 程序的基本结构:

Source → Transformation → Sink
数据源        转换算子        输出
  │              │              │
  ├─ Kafka       ├─ Map         ├─ Kafka
  ├─ Socket     ├─ Filter      ├─ HDFS
  ├─ File       ├─ KeyBy       ├─ MySQL
  └─ Custom     ├─ Window      └─ Elasticsearch
                └─ Aggregate

2.2 并行度与数据分区

并行度示例 (Parallelism = 4)
Source(p=4)          Map(p=4)           Sink(p=4)
┌────────┐          ┌────────┐         ┌────────┐
│ Task 1 │─────────▶│ Task 1 │────────▶│ Task 1 │
├────────┤          ├────────┤         ├────────┤
│ Task 2 │─────────▶│ Task 2 │────────▶│ Task 2 │
├────────┤          ├────────┤         ├────────┤
│ Task 3 │─────────▶│ Task 3 │────────▶│ Task 3 │
├────────┤          ├────────┤         ├────────┤
│ Task 4 │─────────▶│ Task 4 │────────▶│ Task 4 │
└────────┘          └────────┘         └────────┘

2.3 时间语义

Flink 支持三种时间语义:

事件时间 (Event Time)
─────────────────────────────▶
  │      │      │      │
  t1     t2     t3     t4     实际事件发生的时间
处理时间 (Processing Time)
─────────────────────────────▶
     │      │      │      │
     t1'    t2'    t3'    t4' 系统处理事件的时间
摄入时间 (Ingestion Time)
─────────────────────────────▶
   │      │      │      │
   t1''   t2''   t3''   t4''  进入Flink的时间

3. Flink 架构设计

3.1 整体架构

Flink 集群架构
    Client
      │
      │ Submit Job
      ▼
┌─────────────────┐
│  JobManager     │ ◀──── Checkpoint Coordinator
│  (Master)       │
│                 │
│ - JobGraph      │
│ - Scheduling    │
│ - Coordination  │
└────────┬────────┘
         │
         │ Task Distribution
         │
    ┌────┴────┬─────────┬─────────┐
    ▼         ▼         ▼         ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│TaskMgr 1│ │TaskMgr 2│ │TaskMgr 3│
│         │ │         │ │         │
│ Task    │ │ Task    │ │ Task    │
│ Slots   │ │ Slots   │ │ Slots   │
└────┬────┘ └────┬────┘ └────┬────┘
     │           │           │
     └───────────┴───────────┘
         State Backend
         (RocksDB/Memory)

3.2 作业执行流程

Job Execution Flow
User Code                JobGraph               ExecutionGraph
┌──────────┐            ┌──────────┐           ┌──────────────┐
│ Source   │            │ Source   │           │ Source       │
│   ↓      │            │   ↓      │           │ (Task 1-4)   │
│ Map      │  ────▶     │ Map      │  ────▶    │    ↓         │
│   ↓      │   优化     │   ↓      │   调度    │ Map          │
│ KeyBy    │            │ KeyBy    │           │ (Task 1-4)   │
│   ↓      │            │   ↓      │           │    ↓         │
│ Window   │            │ Window   │           │ Window       │
│   ↓      │            │   ↓      │           │ (Task 1-4)   │
│ Sink     │            │ Sink     │           │    ↓         │
└──────────┘            └──────────┘           │ Sink         │
                                               │ (Task 1-4)   │
                                               └──────────────┘

4. 环境搭建与配置

4.1 Maven 依赖配置

// pom.xml
<properties>
  <flink.version>1.18.0</flink.version>
    <scala.binary.version>2.12</scala.binary.version>
      <java.version>11</java.version>
        </properties>
          <dependencies>
            <!-- Flink 核心依赖 -->
              <dependency>
                <groupId>org.apache.flink</groupId>
                  <artifactId>flink-streaming-java</artifactId>
                    <version>${flink.version}</version>
                      </dependency>
                        <!-- Flink 客户端 -->
                          <dependency>
                            <groupId>org.apache.flink</groupId>
                              <artifactId>flink-clients</artifactId>
                                <version>${flink.version}</version>
                                  </dependency>
                                    <!-- Kafka 连接器 -->
                                      <dependency>
                                        <groupId>org.apache.flink</groupId>
                                          <artifactId>flink-connector-kafka</artifactId>
                                            <version>${flink.version}</version>
                                              </dependency>
                                                <!-- Flink Table API -->
                                                  <dependency>
                                                    <groupId>org.apache.flink</groupId>
                                                      <artifactId>flink-table-api-java-bridge</artifactId>
                                                        <version>${flink.version}</version>
                                                          </dependency>
                                                            <!-- JSON 序列化 -->
                                                              <dependency>
                                                                <groupId>com.alibaba</groupId>
                                                                  <artifactId>fastjson</artifactId>
                                                                    <version>2.0.43</version>
                                                                      </dependency>
                                                                        </dependencies>

4.2 基础环境配置

import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.api.common.restartstrategy.RestartStrategies;
import org.apache.flink.api.common.time.Time;
public class FlinkEnvironmentSetup {
public static StreamExecutionEnvironment createEnvironment() {
// 方式1: 获取默认执行环境
StreamExecutionEnvironment env =
StreamExecutionEnvironment.getExecutionEnvironment();
// 方式2: 自定义配置
Configuration conf = new Configuration();
conf.setString("taskmanager.memory.process.size", "2g");
conf.setInteger("taskmanager.numberOfTaskSlots", 4);
StreamExecutionEnvironment customEnv =
StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(conf);
// 设置并行度
env.setParallelism(4);
// 设置重启策略
env.setRestartStrategy(
RestartStrategies.fixedDelayRestart(
3,  // 重启次数
Time.seconds(10)  // 重启间隔
)
);
// 启用检查点
env.enableCheckpointing(60000); // 每60秒一次
return env;
}
}

5. Flink DataStream API 实战

5.1 实时日志分析系统

这是一个真实的生产场景:分析用户行为日志,统计每个用户的访问次数和最后访问时间。

import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.common.functions.ReduceFunction;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
import java.time.Duration;
/**
* 用户行为日志分析
*/
public class UserBehaviorAnalysis {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env =
StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(4);
// 从Kafka读取日志数据
DataStream<String> logStream = env
  .addSource(new FlinkKafkaConsumer<>(
    "user-behavior-log",
    new SimpleStringSchema(),
    getKafkaProperties()
    ));
    // 解析日志并统计
    DataStream<UserBehavior> behaviorStream = logStream
      .map(new LogParser())
      .assignTimestampsAndWatermarks(
      WatermarkStrategy
      .<UserBehavior>forBoundedOutOfOrderness(Duration.ofSeconds(5))
        .withTimestampAssigner((event, timestamp) -> event.timestamp)
        );
        // 按用户分组统计
        DataStream<UserStatistics> statistics = behaviorStream
          .keyBy(behavior -> behavior.userId)
          .window(TumblingEventTimeWindows.of(Time.minutes(5)))
          .reduce(new BehaviorReduceFunction());
          // 输出结果
          statistics.print("User Statistics");
          // 写入MySQL
          statistics.addSink(new UserStatisticsSink());
          env.execute("User Behavior Analysis Job");
          }
          /**
          * 用户行为实体类
          */
          public static class UserBehavior {
          public String userId;
          public String action;  // click, view, purchase
          public String itemId;
          public long timestamp;
          public UserBehavior() {}
          public UserBehavior(String userId, String action,
          String itemId, long timestamp) {
          this.userId = userId;
          this.action = action;
          this.itemId = itemId;
          this.timestamp = timestamp;
          }
          @Override
          public String toString() {
          return String.format("UserBehavior{userId='%s', action='%s', " +
          "itemId='%s', timestamp=%d}",
          userId, action, itemId, timestamp);
          }
          }
          /**
          * 用户统计实体类
          */
          public static class UserStatistics {
          public String userId;
          public long clickCount;
          public long viewCount;
          public long purchaseCount;
          public long lastAccessTime;
          public long windowStart;
          public long windowEnd;
          public UserStatistics() {}
          @Override
          public String toString() {
          return String.format(
          "UserStatistics{userId='%s', clicks=%d, views=%d, " +
          "purchases=%d, lastAccess=%d, window=[%d, %d]}",
          userId, clickCount, viewCount, purchaseCount,
          lastAccessTime, windowStart, windowEnd);
          }
          }
          /**
          * 日志解析器
          */
          public static class LogParser implements MapFunction<String, UserBehavior> {
            @Override
            public UserBehavior map(String log) throws Exception {
            // 日志格式: userId,action,itemId,timestamp
            String[] fields = log.split(",");
            return new UserBehavior(
            fields[0],
            fields[1],
            fields[2],
            Long.parseLong(fields[3])
            );
            }
            }
            /**
            * 行为聚合函数
            */
            public static class BehaviorReduceFunction
            implements ReduceFunction<UserBehavior> {
              @Override
              public UserBehavior reduce(UserBehavior v1, UserBehavior v2) {
              // 这里简化处理,实际应使用聚合函数
              v1.timestamp = Math.max(v1.timestamp, v2.timestamp);
              return v1;
              }
              }
              /**
              * Kafka 配置
              */
              private static Properties getKafkaProperties() {
              Properties props = new Properties();
              props.setProperty("bootstrap.servers", "localhost:9092");
              props.setProperty("group.id", "flink-consumer-group");
              props.setProperty("auto.offset.reset", "latest");
              return props;
              }
              }

5.2 实时告警系统

基于规则引擎的实时告警系统,常用于监控场景。

import org.apache.flink.api.common.state.ValueState;
import org.apache.flink.api.common.state.ValueStateDescriptor;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.functions.KeyedProcessFunction;
import org.apache.flink.util.Collector;
/**
* 实时告警检测
* 场景: 检测5分钟内同一用户登录失败超过3次
*/
public class RealTimeAlertSystem {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env =
StreamExecutionEnvironment.getExecutionEnvironment();
DataStream<LoginEvent> loginStream = env
  .addSource(new LoginEventSource())
  .assignTimestampsAndWatermarks(
  WatermarkStrategy
  .<LoginEvent>forBoundedOutOfOrderness(Duration.ofSeconds(3))
    .withTimestampAssigner((event, ts) -> event.timestamp)
    );
    // 检测连续失败登录
    DataStream<Alert> alerts = loginStream
      .keyBy(event -> event.userId)
      .process(new LoginFailDetector(3, 300000)); // 3次,5分钟
      alerts.print("Alerts");
      // 发送告警通知
      alerts.addSink(new AlertNotificationSink());
      env.execute("Real-Time Alert System");
      }
      /**
      * 登录事件
      */
      public static class LoginEvent {
      public String userId;
      public boolean success;
      public String ip;
      public long timestamp;
      public LoginEvent() {}
      public LoginEvent(String userId, boolean success,
      String ip, long timestamp) {
      this.userId = userId;
      this.success = success;
      this.ip = ip;
      this.timestamp = timestamp;
      }
      }
      /**
      * 告警信息
      */
      public static class Alert {
      public String userId;
      public String alertType;
      public String message;
      public long timestamp;
      public Alert(String userId, String alertType,
      String message, long timestamp) {
      this.userId = userId;
      this.alertType = alertType;
      this.message = message;
      this.timestamp = timestamp;
      }
      @Override
      public String toString() {
      return String.format("Alert{userId='%s', type='%s', " +
      "message='%s', time=%d}",
      userId, alertType, message, timestamp);
      }
      }
      /**
      * 登录失败检测器
      */
      public static class LoginFailDetector
      extends KeyedProcessFunction<String, LoginEvent, Alert> {
        private final int maxFailCount;
        private final long timeWindow;
        private ValueState<Integer> failCountState;
          private ValueState<Long> firstFailTimeState;
            public LoginFailDetector(int maxFailCount, long timeWindow) {
            this.maxFailCount = maxFailCount;
            this.timeWindow = timeWindow;
            }
            @Override
            public void open(Configuration parameters) throws Exception {
            failCountState = getRuntimeContext().getState(
            new ValueStateDescriptor<>("fail-count", Integer.class)
              );
              firstFailTimeState = getRuntimeContext().getState(
              new ValueStateDescriptor<>("first-fail-time", Long.class)
                );
                }
                @Override
                public void processElement(
                LoginEvent event,
                Context ctx,
                Collector<Alert> out) throws Exception {
                  if (event.success) {
                  // 登录成功,清空失败计数
                  failCountState.clear();
                  firstFailTimeState.clear();
                  return;
                  }
                  // 登录失败处理
                  Integer currentCount = failCountState.value();
                  Long firstFailTime = firstFailTimeState.value();
                  if (currentCount == null) {
                  // 第一次失败
                  failCountState.update(1);
                  firstFailTimeState.update(event.timestamp);
                  // 注册定时器,5分钟后清空状态
                  ctx.timerService().registerEventTimeTimer(
                  event.timestamp + timeWindow
                  );
                  } else {
                  // 检查是否在时间窗口内
                  if (event.timestamp - firstFailTime <= timeWindow) {
                  int newCount = currentCount + 1;
                  failCountState.update(newCount);
                  // 达到阈值,触发告警
                  if (newCount >= maxFailCount) {
                  out.collect(new Alert(
                  event.userId,
                  "LOGIN_FAIL",
                  String.format("用户 %s 在5分钟内登录失败 %d 次,IP: %s",
                  event.userId, newCount, event.ip),
                  event.timestamp
                  ));
                  }
                  } else {
                  // 超出时间窗口,重新开始计数
                  failCountState.update(1);
                  firstFailTimeState.update(event.timestamp);
                  ctx.timerService().registerEventTimeTimer(
                  event.timestamp + timeWindow
                  );
                  }
                  }
                  }
                  @Override
                  public void onTimer(
                  long timestamp,
                  OnTimerContext ctx,
                  Collector<Alert> out) throws Exception {
                    // 定时器触发,清空状态
                    failCountState.clear();
                    firstFailTimeState.clear();
                    }
                    }
                    }

5.3 实时数据去重

生产环境中常见的需求:基于状态的精确去重。

import org.apache.flink.api.common.state.StateTtlConfig;
import org.apache.flink.api.common.state.ValueState;
import org.apache.flink.api.common.state.ValueStateDescriptor;
import org.apache.flink.api.common.time.Time;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.functions.KeyedProcessFunction;
import org.apache.flink.util.Collector;
/**
* 实时数据去重
* 场景: 订单去重,防止重复处理
*/
public class DataDeduplication {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env =
StreamExecutionEnvironment.getExecutionEnvironment();
DataStream<Order> orderStream = env
  .addSource(new OrderSource());
  // 按订单ID去重
  DataStream<Order> deduplicatedStream = orderStream
    .keyBy(order -> order.orderId)
    .process(new DeduplicationFunction(3600)); // 1小时内去重
    deduplicatedStream.print("Deduplicated Orders");
    env.execute("Data Deduplication Job");
    }
    /**
    * 订单实体
    */
    public static class Order {
    public String orderId;
    public String userId;
    public double amount;
    public long timestamp;
    public Order() {}
    public Order(String orderId, String userId,
    double amount, long timestamp) {
    this.orderId = orderId;
    this.userId = userId;
    this.amount = amount;
    this.timestamp = timestamp;
    }
    @Override
    public String toString() {
    return String.format("Order{orderId='%s', userId='%s', " +
    "amount=%.2f, timestamp=%d}",
    orderId, userId, amount, timestamp);
    }
    }
    /**
    * 去重处理函数
    */
    public static class DeduplicationFunction
    extends KeyedProcessFunction<String, Order, Order> {
      private final long ttlSeconds;
      private ValueState<Boolean> seenState;
        public DeduplicationFunction(long ttlSeconds) {
        this.ttlSeconds = ttlSeconds;
        }
        @Override
        public void open(Configuration parameters) throws Exception {
        // 配置状态TTL
        StateTtlConfig ttlConfig = StateTtlConfig
        .newBuilder(Time.seconds(ttlSeconds))
        .setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite)
        .setStateVisibility(
        StateTtlConfig.StateVisibility.NeverReturnExpired)
        .build();
        ValueStateDescriptor<Boolean> descriptor =
          new ValueStateDescriptor<>("seen", Boolean.class);
            descriptor.enableTimeToLive(ttlConfig);
            seenState = getRuntimeContext().getState(descriptor);
            }
            @Override
            public void processElement(
            Order order,
            Context ctx,
            Collector<Order> out) throws Exception {
              Boolean seen = seenState.value();
              if (seen == null || !seen) {
              // 第一次见到这个订单,输出并标记
              seenState.update(true);
              out.collect(order);
              }
              // 如果已经见过,直接丢弃(去重)
              }
              }
              }

6. Flink SQL 与 Table API

6.1 Flink SQL 基础

Flink SQL 提供了标准SQL接口,降低了流处理的使用门槛。

import org.apache.flink.table.api.EnvironmentSettings;
import org.apache.flink.table.api.TableEnvironment;
import org.apache.flink.table.api.TableResult;
/**
* Flink SQL 示例
*/
public class FlinkSQLExample {
public static void main(String[] args) {
// 创建 Table Environment
EnvironmentSettings settings = EnvironmentSettings
.newInstance()
.inStreamingMode()
.build();
TableEnvironment tableEnv = TableEnvironment.create(settings);
// 1. 创建 Kafka 源表
String createSourceTableSQL =
"CREATE TABLE user_behavior (\n" +
"  user_id STRING,\n" +
"  item_id STRING,\n" +
"  category_id STRING,\n" +
"  behavior STRING,\n" +
"  ts BIGINT,\n" +
"  event_time AS TO_TIMESTAMP(FROM_UNIXTIME(ts)),\n" +
"  WATERMARK FOR event_time AS event_time - INTERVAL '5' SECOND\n" +
") WITH (\n" +
"  'connector' = 'kafka',\n" +
"  'topic' = 'user_behavior',\n" +
"  'properties.bootstrap.servers' = 'localhost:9092',\n" +
"  'properties.group.id' = 'flink-sql-group',\n" +
"  'scan.startup.mode' = 'latest-offset',\n" +
"  'format' = 'json'\n" +
")";
tableEnv.executeSql(createSourceTableSQL);
// 2. 创建结果表(MySQL)
String createSinkTableSQL =
"CREATE TABLE user_behavior_count (\n" +
"  user_id STRING,\n" +
"  behavior STRING,\n" +
"  cnt BIGINT,\n" +
"  window_start TIMESTAMP(3),\n" +
"  window_end TIMESTAMP(3),\n" +
"  PRIMARY KEY (user_id, behavior, window_start) NOT ENFORCED\n" +
") WITH (\n" +
"  'connector' = 'jdbc',\n" +
"  'url' = 'jdbc:mysql://localhost:3306/flink_db',\n" +
"  'table-name' = 'user_behavior_count',\n" +
"  'username' = 'root',\n" +
"  'password' = 'password'\n" +
")";
tableEnv.executeSql(createSinkTableSQL);
// 3. 执行查询并写入结果表
String insertSQL =
"INSERT INTO user_behavior_count\n" +
"SELECT \n" +
"  user_id,\n" +
"  behavior,\n" +
"  COUNT(*) as cnt,\n" +
"  TUMBLE_START(event_time, INTERVAL '5' MINUTE) as window_start,\n" +
"  TUMBLE_END(event_time, INTERVAL '5' MINUTE) as window_end\n" +
"FROM user_behavior\n" +
"GROUP BY \n" +
"  user_id, \n" +
"  behavior, \n" +
"  TUMBLE(event_time, INTERVAL '5' MINUTE)";
TableResult result = tableEnv.executeSql(insertSQL);
// 等待作业完成
result.print();
}
}

6.2 Table API 实战

使用Table API进行更灵活的数据处理。

import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.table.api.Table;
import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;
import static org.apache.flink.table.api.Expressions.$;
/**
* Table API 示例
*/
public class FlinkTableAPIExample {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env =
StreamExecutionEnvironment.getExecutionEnvironment();
StreamTableEnvironment tableEnv =
StreamTableEnvironment.create(env);
// 创建数据流
DataStream<Tuple2<String, Integer>> dataStream = env
  .fromElements(
  Tuple2.of("Alice", 100),
  Tuple2.of("Bob", 200),
  Tuple2.of("Alice", 150),
  Tuple2.of("Charlie", 300),
  Tuple2.of("Bob", 250)
  );
  // 将 DataStream 转换为 Table
  Table inputTable = tableEnv.fromDataStream(
  dataStream,
  $("name"),
  $("amount")
  );
  // 使用 Table API 进行查询
  Table resultTable = inputTable
  .groupBy($("name"))
  .select(
  $("name"),
  $("amount").sum().as("total_amount"),
  $("amount").avg().as("avg_amount"),
  $("amount").count().as("count")
  )
  .filter($("total_amount").isGreater(200));
  // 将结果转换回 DataStream
  DataStream<Tuple2<Boolean, Row>> resultStream =
    tableEnv.toRetractStream(resultTable, Row.class);
    resultStream.print();
    env.execute("Table API Example");
    }
    }

6.3 实时数仓场景:维度表关联

/**
* 实时数仓场景:事实表关联维度表
* 场景: 订单流关联用户维度信息
*/
public class DimensionTableJoin {
public static void main(String[] args) {
EnvironmentSettings settings = EnvironmentSettings
.newInstance()
.inStreamingMode()
.build();
TableEnvironment tableEnv = TableEnvironment.create(settings);
// 1. 创建订单事实表(Kafka)
tableEnv.executeSql(
"CREATE TABLE orders (\n" +
"  order_id STRING,\n" +
"  user_id STRING,\n" +
"  product_id STRING,\n" +
"  amount DECIMAL(10, 2),\n" +
"  order_time TIMESTAMP(3),\n" +
"  WATERMARK FOR order_time AS order_time - INTERVAL '5' SECOND\n" +
") WITH (\n" +
"  'connector' = 'kafka',\n" +
"  'topic' = 'orders',\n" +
"  'properties.bootstrap.servers' = 'localhost:9092',\n" +
"  'format' = 'json'\n" +
")"
);
// 2. 创建用户维度表(MySQL,使用Lookup Join)
tableEnv.executeSql(
"CREATE TABLE user_dim (\n" +
"  user_id STRING,\n" +
"  user_name STRING,\n" +
"  age INT,\n" +
"  city STRING,\n" +
"  level STRING,\n" +
"  PRIMARY KEY (user_id) NOT ENFORCED\n" +
") WITH (\n" +
"  'connector' = 'jdbc',\n" +
"  'url' = 'jdbc:mysql://localhost:3306/dim_db',\n" +
"  'table-name' = 'user_dim',\n" +
"  'lookup.cache.max-rows' = '5000',\n" +
"  'lookup.cache.ttl' = '10min'\n" +
")"
);
// 3. 关联查询并输出
String querySQL =
"SELECT \n" +
"  o.order_id,\n" +
"  o.user_id,\n" +
"  u.user_name,\n" +
"  u.city,\n" +
"  u.level,\n" +
"  o.product_id,\n" +
"  o.amount,\n" +
"  o.order_time\n" +
"FROM orders AS o\n" +
"LEFT JOIN user_dim FOR SYSTEM_TIME AS OF o.order_time AS u\n" +
"ON o.user_id = u.user_id";
Table result = tableEnv.sqlQuery(querySQL);
// 输出到控制台或其他Sink
tableEnv.executeSql(
"CREATE TABLE enriched_orders (\n" +
"  order_id STRING,\n" +
"  user_id STRING,\n" +
"  user_name STRING,\n" +
"  city STRING,\n" +
"  level STRING,\n" +
"  product_id STRING,\n" +
"  amount DECIMAL(10, 2),\n" +
"  order_time TIMESTAMP(3)\n" +
") WITH (\n" +
"  'connector' = 'print'\n" +
")"
);
result.executeInsert("enriched_orders");
}
}

7. 状态管理与容错机制

7.1 状态类型与使用

Flink 状态分类
State
  │
  ├─ Keyed State (键控状态)
  │   ├─ ValueState        单值状态
  │   ├─ ListState         列表状态
  │   ├─ MapState        映射状态
  │   ├─ ReducingState     归约状态
  │   └─ AggregatingState  聚合状态
  │
  └─ Operator State (算子状态)
      ├─ ListState         列表状态
      └─ UnionListState    联合列表状态
import org.apache.flink.api.common.state.*;
import org.apache.flink.api.common.typeinfo.TypeHint;
import org.apache.flink.api.common.typeinfo.TypeInformation;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.functions.KeyedProcessFunction;
import org.apache.flink.util.Collector;
import java.util.ArrayList;
import java.util.List;
/**
* 状态管理综合示例
* 场景: 用户行为画像计算
*/
public class StateManagementExample {
public static class UserProfileFunction
extends KeyedProcessFunction<String, UserAction, UserProfile> {
  // ValueState: 存储用户总消费金额
  private ValueState<Double> totalAmountState;
    // ListState: 存储最近的行为记录
    private ListState<UserAction> recentActionsState;
      // MapState: 存储每个商品类目的购买次数
      private MapState<String, Integer> categoryCountState;
        @Override
        public void open(Configuration parameters) throws Exception {
        // 初始化 ValueState
        ValueStateDescriptor<Double> totalAmountDescriptor =
          new ValueStateDescriptor<>("total-amount", Double.class);
            totalAmountState = getRuntimeContext()
            .getState(totalAmountDescriptor);
            // 初始化 ListState
            ListStateDescriptor<UserAction> recentActionsDescriptor =
              new ListStateDescriptor<>("recent-actions", UserAction.class);
                recentActionsState = getRuntimeContext()
                .getListState(recentActionsDescriptor);
                // 初始化 MapState
                MapStateDescriptor<String, Integer> categoryCountDescriptor =
                  new MapStateDescriptor<>(
                    "category-count",
                    String.class,
                    Integer.class
                    );
                    categoryCountState = getRuntimeContext()
                    .getMapState(categoryCountDescriptor);
                    }
                    @Override
                    public void processElement(
                    UserAction action,
                    Context ctx,
                    Collector<UserProfile> out) throws Exception {
                      // 更新总消费金额
                      Double currentTotal = totalAmountState.value();
                      if (currentTotal == null) {
                      currentTotal = 0.0;
                      }
                      totalAmountState.update(currentTotal + action.amount);
                      // 更新最近行为(保留最近10条)
                      List<UserAction> recentActions = new ArrayList<>();
                        for (UserAction a : recentActionsState.get()) {
                        recentActions.add(a);
                        }
                        recentActions.add(action);
                        if (recentActions.size() > 10) {
                        recentActions.remove(0);
                        }
                        recentActionsState.update(recentActions);
                        // 更新类目计数
                        Integer count = categoryCountState.get(action.category);
                        if (count == null) {
                        count = 0;
                        }
                        categoryCountState.put(action.category, count + 1);
                        // 构建用户画像
                        UserProfile profile = new UserProfile();
                        profile.userId = action.userId;
                        profile.totalAmount = totalAmountState.value();
                        profile.recentActionCount = recentActions.size();
                        profile.favoriteCategory = getFavoriteCategory();
                        profile.updateTime = System.currentTimeMillis();
                        out.collect(profile);
                        }
                        /**
                        * 获取最喜欢的类目
                        */
                        private String getFavoriteCategory() throws Exception {
                        String favorite = null;
                        int maxCount = 0;
                        for (Map.Entry<String, Integer> entry : categoryCountState.entries()) {
                          if (entry.getValue() > maxCount) {
                          maxCount = entry.getValue();
                          favorite = entry.getKey();
                          }
                          }
                          return favorite;
                          }
                          }
                          /**
                          * 用户行为
                          */
                          public static class UserAction {
                          public String userId;
                          public String action;
                          public String category;
                          public double amount;
                          public long timestamp;
                          }
                          /**
                          * 用户画像
                          */
                          public static class UserProfile {
                          public String userId;
                          public double totalAmount;
                          public int recentActionCount;
                          public String favoriteCategory;
                          public long updateTime;
                          @Override
                          public String toString() {
                          return String.format(
                          "UserProfile{userId='%s', totalAmount=%.2f, " +
                          "recentActions=%d, favorite='%s', updateTime=%d}",
                          userId, totalAmount, recentActionCount,
                          favoriteCategory, updateTime
                          );
                          }
                          }
                          }

7.2 Checkpoint 与 Savepoint

Checkpoint 机制
Time
  │
  │  正常处理数据
  ├──────────────────────▶
  │
  │  触发 Checkpoint
  ├─ ① JobManager 向所有 Source 发送 Barrier
  │     │
  │     ├─ Source ────▶ Barrier ────▶
  │     │                │
  │     └────────────────┼────────────▶
  │                      │
  │  ② Operator 收到 Barrier,保存状态
  │     │
  │     ├─ Map ──── snapshot state ──▶ State Backend
  │     │
  │     ├─ KeyBy ── snapshot state ──▶ State Backend
  │     │
  │     └─ Sink ─── snapshot state ──▶ State Backend
  │
  │  ③ 所有状态保存完成
  ├─ Checkpoint Complete
  │
  ▼
import org.apache.flink.contrib.streaming.state.RocksDBStateBackend;
import org.apache.flink.runtime.state.StateBackend;
import org.apache.flink.runtime.state.filesystem.FsStateBackend;
import org.apache.flink.runtime.state.memory.MemoryStateBackend;
import org.apache.flink.streaming.api.CheckpointingMode;
import org.apache.flink.streaming.api.environment.CheckpointConfig;
/**
* Checkpoint 配置
*/
public class CheckpointConfiguration {
public static void configureCheckpoint(StreamExecutionEnvironment env) {
// 1. 启用 Checkpoint,间隔 60 秒
env.enableCheckpointing(60000);
// 2. 获取 Checkpoint 配置
CheckpointConfig checkpointConfig = env.getCheckpointConfig();
// 3. 设置精确一次语义
checkpointConfig.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
// 4. Checkpoint 之间的最小间隔(避免频繁checkpoint)
checkpointConfig.setMinPauseBetweenCheckpoints(30000);
// 5. Checkpoint 超时时间
checkpointConfig.setCheckpointTimeout(600000);
// 6. 允许的最大并发 Checkpoint 数
checkpointConfig.setMaxConcurrentCheckpoints(1);
// 7. 任务取消时保留 Checkpoint
checkpointConfig.setExternalizedCheckpointCleanup(
CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION
);
// 8. 允许的 Checkpoint 失败次数
checkpointConfig.setTolerableCheckpointFailureNumber(3);
// 9. 配置 State Backend
configureStateBackend(env);
}
/**
* 配置状态后端
*/
private static void configureStateBackend(StreamExecutionEnvironment env) {
// 方式1: MemoryStateBackend (开发测试)
// StateBackend memoryBackend = new MemoryStateBackend(5 * 1024 * 1024);
// 方式2: FsStateBackend (小状态生产环境)
StateBackend fsBackend = new FsStateBackend(
"hdfs://namenode:9000/flink/checkpoints",
true  // 异步快照
);
// 方式3: RocksDBStateBackend (大状态生产环境,推荐)
// StateBackend rocksDBBackend = new RocksDBStateBackend(
//     "hdfs://namenode:9000/flink/checkpoints",
//     true  // 增量 Checkpoint
// );
env.setStateBackend(fsBackend);
}
/**
* 从 Savepoint 恢复
*/
public static void restoreFromSavepoint() {
// 启动命令:
// flink run -s hdfs://namenode:9000/flink/savepoints/savepoint-xxx \
//     -c com.example.MyFlinkJob \
//     myFlinkJob.jar
System.out.println("从 Savepoint 恢复作业...");
}
/**
* 创建 Savepoint
*/
public static void createSavepoint() {
// 创建命令:
// flink savepoint <jobId> hdfs://namenode:9000/flink/savepoints
  // 取消作业并创建 Savepoint:
  // flink cancel -s hdfs://namenode:9000/flink/savepoints <jobId>
    System.out.println("创建 Savepoint...");
    }
    }

8. 窗口机制详解

8.1 窗口类型

窗口类型图解
1. 滚动窗口 (Tumbling Window)
   [00:00-00:05) [00:05-00:10) [00:10-00:15)
   ├─────────┤  ├─────────┤  ├─────────┤
   无重叠,无间隙
2. 滑动窗口 (Sliding Window)
   [00:00-00:10)
   ├─────────────┤
        [00:05-00:15)
        ├─────────────┤
             [00:10-00:20)
             ├─────────────┤
   有重叠
3. 会话窗口 (Session Window)
   ─●─●──●────────●─●──────────●─────▶
   [  窗口1  ] [  窗口2  ] [ 窗口3 ]
   根据间隔动态划分
4. 全局窗口 (Global Window)
   [         所有数据         ]
   ├───────────────────────────┤
   需要自定义触发器

8.2 窗口实战示例

import org.apache.flink.api.common.functions.AggregateFunction;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.windowing.assigners.*;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
/**
* 窗口综合示例
* 场景: 实时交易监控系统
*/
public class WindowExample {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env =
StreamExecutionEnvironment.getExecutionEnvironment();
DataStream<Transaction> transactions =
  env.addSource(new TransactionSource());
  // 示例1: 滚动窗口 - 每分钟交易统计
  DataStream<TransactionStatistics> minuteStats = transactions
    .assignTimestampsAndWatermarks(
    WatermarkStrategy
    .<Transaction>forBoundedOutOfOrderness(Duration.ofSeconds(5))
      .withTimestampAssigner((tx, ts) -> tx.timestamp)
      )
      .keyBy(tx -> tx.merchantId)
      .window(TumblingEventTimeWindows.of(Time.minutes(1)))
      .aggregate(new TransactionAggregator());
      minuteStats.print("Minute Stats");
      // 示例2: 滑动窗口 - 最近5分钟,每分钟更新
      DataStream<TransactionStatistics> slidingStats = transactions
        .keyBy(tx -> tx.merchantId)
        .window(SlidingEventTimeWindows.of(
        Time.minutes(5),  // 窗口大小
        Time.minutes(1)   // 滑动步长
        ))
        .aggregate(new TransactionAggregator());
        // 示例3: 会话窗口 - 用户活跃会话分析
        DataStream<UserSession> sessions = transactions
          .keyBy(tx -> tx.userId)
          .window(EventTimeSessionWindows.withGap(Time.minutes(30)))
          .process(new SessionProcessor());
          env.execute("Window Example");
          }
          /**
          * 交易实体
          */
          public static class Transaction {
          public String transactionId;
          public String userId;
          public String merchantId;
          public double amount;
          public String status;  // SUCCESS, FAILED
          public long timestamp;
          public Transaction() {}
          public Transaction(String transactionId, String userId,
          String merchantId, double amount,
          String status, long timestamp) {
          this.transactionId = transactionId;
          this.userId = userId;
          this.merchantId = merchantId;
          this.amount = amount;
          this.status = status;
          this.timestamp = timestamp;
          }
          }
          /**
          * 交易统计
          */
          public static class TransactionStatistics {
          public String merchantId;
          public long windowStart;
          public long windowEnd;
          public long totalCount;
          public long successCount;
          public long failedCount;
          public double totalAmount;
          public double avgAmount;
          public double maxAmount;
          public double minAmount;
          @Override
          public String toString() {
          return String.format(
          "Stats{merchant='%s', window=[%d,%d], " +
          "total=%d, success=%d, failed=%d, " +
          "amount=%.2f, avg=%.2f, max=%.2f, min=%.2f}",
          merchantId, windowStart, windowEnd,
          totalCount, successCount, failedCount,
          totalAmount, avgAmount, maxAmount, minAmount
          );
          }
          }
          /**
          * 交易聚合器
          */
          public static class TransactionAggregator
          implements AggregateFunction<Transaction,
          TransactionAccumulator,
          TransactionStatistics> {
          @Override
          public TransactionAccumulator createAccumulator() {
          return new TransactionAccumulator();
          }
          @Override
          public TransactionAccumulator add(
          Transaction tx,
          TransactionAccumulator acc) {
          acc.merchantId = tx.merchantId;
          acc.count++;
          acc.totalAmount += tx.amount;
          if ("SUCCESS".equals(tx.status)) {
          acc.successCount++;
          } else {
          acc.failedCount++;
          }
          if (acc.maxAmount == null || tx.amount > acc.maxAmount) {
          acc.maxAmount = tx.amount;
          }
          if (acc.minAmount == null || tx.amount < acc.minAmount) {
          acc.minAmount = tx.amount;
          }
          return acc;
          }
          @Override
          public TransactionStatistics getResult(TransactionAccumulator acc) {
          TransactionStatistics stats = new TransactionStatistics();
          stats.merchantId = acc.merchantId;
          stats.totalCount = acc.count;
          stats.successCount = acc.successCount;
          stats.failedCount = acc.failedCount;
          stats.totalAmount = acc.totalAmount;
          stats.avgAmount = acc.totalAmount / acc.count;
          stats.maxAmount = acc.maxAmount != null ? acc.maxAmount : 0.0;
          stats.minAmount = acc.minAmount != null ? acc.minAmount : 0.0;
          return stats;
          }
          @Override
          public TransactionAccumulator merge(
          TransactionAccumulator a,
          TransactionAccumulator b) {
          a.count += b.count;
          a.successCount += b.successCount;
          a.failedCount += b.failedCount;
          a.totalAmount += b.totalAmount;
          if (b.maxAmount != null &&
          (a.maxAmount == null || b.maxAmount > a.maxAmount)) {
          a.maxAmount = b.maxAmount;
          }
          if (b.minAmount != null &&
          (a.minAmount == null || b.minAmount < a.minAmount)) {
          a.minAmount = b.minAmount;
          }
          return a;
          }
          }
          /**
          * 交易累加器
          */
          public static class TransactionAccumulator {
          public String merchantId;
          public long count = 0;
          public long successCount = 0;
          public long failedCount = 0;
          public double totalAmount = 0.0;
          public Double maxAmount = null;
          public Double minAmount = null;
          }
          /**
          * 用户会话
          */
          public static class UserSession {
          public String userId;
          public long sessionStart;
          public long sessionEnd;
          public int transactionCount;
          public double totalAmount;
          }
          /**
          * 会话处理器
          */
          public static class SessionProcessor
          extends ProcessWindowFunction<Transaction,
          UserSession,
          String,
          TimeWindow> {
          @Override
          public void process(
          String userId,
          Context context,
          Iterable<Transaction> transactions,
            Collector<UserSession> out) {
              UserSession session = new UserSession();
              session.userId = userId;
              session.sessionStart = context.window().getStart();
              session.sessionEnd = context.window().getEnd();
              session.transactionCount = 0;
              session.totalAmount = 0.0;
              for (Transaction tx : transactions) {
              session.transactionCount++;
              session.totalAmount += tx.amount;
              }
              out.collect(session);
              }
              }
              }

9. Flink CDC 实践

Flink CDC (Change Data Capture) 是实时数据集成的重要工具,可以实时捕获数据库的变更。

9.1 MySQL CDC 示例

import com.ververica.cdc.connectors.mysql.source.MySqlSource;
import com.ververica.cdc.connectors.mysql.table.StartupOptions;
import com.ververica.cdc.debezium.JsonDebeziumDeserializationSchema;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
/**
* Flink CDC 示例
* 场景: 实时同步 MySQL 数据到 Elasticsearch
*/
public class FlinkCDCExample {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env =
StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);  // CDC 通常设置为1
// 配置 MySQL CDC Source
MySqlSource<String> mySqlSource = MySqlSource.<String>builder()
  .hostname("localhost")
  .port(3306)
  .databaseList("my_database")
  .tableList("my_database.users", "my_database.orders")
  .username("root")
  .password("password")
  .startupOptions(StartupOptions.initial())  // 从最新位置开始
  .deserializer(new JsonDebeziumDeserializationSchema())
  .build();
  // 创建数据流
  DataStreamSource<String> cdcStream = env
    .fromSource(
    mySqlSource,
    WatermarkStrategy.noWatermarks(),
    "MySQL CDC Source"
    );
    // 处理 CDC 数据
    DataStream<ChangeEvent> processedStream = cdcStream
      .map(new CDCParser())
      .filter(event -> event != null);
      processedStream.print("CDC Events");
      // 写入目标系统
      processedStream.addSink(new ElasticsearchSink());
      env.execute("Flink CDC Job");
      }
      /**
      * CDC 事件
      */
      public static class ChangeEvent {
      public String database;
      public String table;
      public String operation;  // INSERT, UPDATE, DELETE
      public String before;     // 变更前数据
      public String after;      // 变更后数据
      public long timestamp;
      @Override
      public String toString() {
      return String.format(
      "ChangeEvent{db='%s', table='%s', op='%s', time=%d}",
      database, table, operation, timestamp
      );
      }
      }
      /**
      * CDC 数据解析器
      */
      public static class CDCParser implements MapFunction<String, ChangeEvent> {
        @Override
        public ChangeEvent map(String value) throws Exception {
        // 使用 FastJSON 解析 Debezium 格式的数据
        JSONObject json = JSON.parseObject(value);
        if (json == null) {
        return null;
        }
        ChangeEvent event = new ChangeEvent();
        // 解析元数据
        JSONObject source = json.getJSONObject("source");
        if (source != null) {
        event.database = source.getString("db");
        event.table = source.getString("table");
        event.timestamp = source.getLong("ts_ms");
        }
        // 解析操作类型
        String op = json.getString("op");
        switch (op) {
        case "c":
        event.operation = "INSERT";
        break;
        case "u":
        event.operation = "UPDATE";
        break;
        case "d":
        event.operation = "DELETE";
        break;
        case "r":
        event.operation = "READ";
        break;
        default:
        event.operation = "UNKNOWN";
        }
        // 解析数据
        event.before = json.getString("before");
        event.after = json.getString("after");
        return event;
        }
        }
        }

9.2 实时数据同步架构

CDC 实时同步架构
MySQL                    Flink CDC                 Target Systems
┌──────────┐            ┌──────────┐              ┌──────────────┐
│          │  Binlog    │          │              │              │
│  users   │───────────▶│  Source  │──────────────│ Elasticsearch│
│          │            │          │              │              │
├──────────┤            ├──────────┤              ├──────────────┤
│          │  Binlog    │          │  Transform   │              │
│  orders  │───────────▶│  Parser  │──────────────│    Kafka     │
│          │            │          │              │              │
├──────────┤            ├──────────┤              ├──────────────┤
│          │  Binlog    │          │  Enrich      │              │
│ products │───────────▶│ Enrich   │──────────────│     HDFS     │
│          │            │          │              │              │
└──────────┘            └──────────┘              └──────────────┘
特点:
- 低延迟: 毫秒级数据同步
- 一致性: 保证数据一致性
- 全量+增量: 支持历史数据和实时变更
- 多目标: 可同时写入多个目标系统

9.3 Flink SQL CDC

/**
* 使用 Flink SQL CDC
*/
public class FlinkSQLCDC {
public static void main(String[] args) {
EnvironmentSettings settings = EnvironmentSettings
.newInstance()
.inStreamingMode()
.build();
TableEnvironment tableEnv = TableEnvironment.create(settings);
// 1. 创建 MySQL CDC 源表
tableEnv.executeSql(
"CREATE TABLE mysql_users (\n" +
"  id BIGINT,\n" +
"  name STRING,\n" +
"  age INT,\n" +
"  email STRING,\n" +
"  create_time TIMESTAMP(3),\n" +
"  update_time TIMESTAMP(3),\n" +
"  PRIMARY KEY (id) NOT ENFORCED\n" +
") WITH (\n" +
"  'connector' = 'mysql-cdc',\n" +
"  'hostname' = 'localhost',\n" +
"  'port' = '3306',\n" +
"  'username' = 'root',\n" +
"  'password' = 'password',\n" +
"  'database-name' = 'my_database',\n" +
"  'table-name' = 'users',\n" +
"  'scan.startup.mode' = 'initial'\n" +
")"
);
// 2. 创建 Elasticsearch 结果表
tableEnv.executeSql(
"CREATE TABLE es_users (\n" +
"  id BIGINT,\n" +
"  name STRING,\n" +
"  age INT,\n" +
"  email STRING,\n" +
"  create_time TIMESTAMP(3),\n" +
"  update_time TIMESTAMP(3),\n" +
"  PRIMARY KEY (id) NOT ENFORCED\n" +
") WITH (\n" +
"  'connector' = 'elasticsearch-7',\n" +
"  'hosts' = 'http://localhost:9200',\n" +
"  'index' = 'users'\n" +
")"
);
// 3. 实时同步数据
tableEnv.executeSql(
"INSERT INTO es_users SELECT * FROM mysql_users"
);
}
}

10. 生产环境优化与最佳实践

10.1 性能优化清单

性能优化检查清单
1. 并行度设置
   ├─ 合理设置全局并行度
   ├─ 针对算子单独设置并行度
   └─ 避免数据倾斜
2. 资源配置
   ├─ TaskManager 内存配置
   ├─ Slot 数量配置
   └─ 网络缓冲区配置
3. 状态管理
   ├─ 选择合适的 State Backend
   ├─ 启用增量 Checkpoint
   └─ 配置状态 TTL
4. 序列化
   ├─ 使用高效的序列化器
   └─ 避免 Kryo 序列化大对象
5. 算子优化
   ├─ 算子链接 (Operator Chaining)
   ├─ 减少 Shuffle 操作
   └─ 使用 Filter 提前过滤
6. Checkpoint 优化
   ├─ 合理设置 Checkpoint 间隔
   ├─ 使用增量 Checkpoint
   └─ 配置并发 Checkpoint

10.2 生产环境配置示例

/**
* 生产环境配置最佳实践
*/
public class ProductionConfiguration {
public static StreamExecutionEnvironment createProductionEnv() {
Configuration conf = new Configuration();
// 1. TaskManager 配置
conf.setString("taskmanager.memory.process.size", "4g");
conf.setString("taskmanager.memory.managed.size", "1g");
conf.setInteger("taskmanager.numberOfTaskSlots", 4);
// 2. 网络配置
conf.setInteger("taskmanager.network.numberOfBuffers", 2048);
conf.setString("taskmanager.network.memory.fraction", "0.1");
// 3. RocksDB 配置(大状态场景)
conf.setString("state.backend", "rocksdb");
conf.setString("state.backend.incremental", "true");
conf.setString("state.backend.rocksdb.predefined-options", "SPINNING_DISK_OPTIMIZED");
StreamExecutionEnvironment env =
StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(conf);
// 4. Checkpoint 配置
env.enableCheckpointing(300000);  // 5分钟
CheckpointConfig checkpointConfig = env.getCheckpointConfig();
checkpointConfig.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE);
checkpointConfig.setMinPauseBetweenCheckpoints(120000);  // 2分钟
checkpointConfig.setCheckpointTimeout(600000);  // 10分钟
checkpointConfig.setMaxConcurrentCheckpoints(1);
checkpointConfig.setExternalizedCheckpointCleanup(
CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION
);
// 5. 重启策略
env.setRestartStrategy(
RestartStrategies.failureRateRestart(
3,  // 每个时间段内最多重启3次
Time.minutes(5),  // 时间段长度
Time.seconds(30)  // 重启延迟
)
);
// 6. 并行度
env.setParallelism(8);  // 根据集群资源调整
// 7. 启用对象重用(减少GC压力)
env.getConfig().enableObjectReuse();
return env;
}
/**
* 反压处理策略
*/
public static void handleBackpressure() {
// 1. 监控反压指标
// 2. 增加并行度
// 3. 优化算子逻辑
// 4. 增加资源配置
// 5. 调整 Checkpoint 间隔
}
}

10.3 数据倾斜处理

import org.apache.flink.api.common.functions.Partitioner;
import org.apache.flink.api.java.functions.KeySelector;
/**
* 数据倾斜处理
*/
public class DataSkewHandling {
/**
* 方案1: 自定义分区器
*/
public static class CustomPartitioner implements Partitioner<String> {
  @Override
  public int partition(String key, int numPartitions) {
  // 为热点 key 添加随机后缀
  if (isHotKey(key)) {
  int random = ThreadLocalRandom.current().nextInt(10);
  return (key + "_" + random).hashCode() % numPartitions;
  }
  return key.hashCode() % numPartitions;
  }
  private boolean isHotKey(String key) {
  // 判断是否为热点 key
  return key.equals("popular_item");
  }
  }
  /**
  * 方案2: 两阶段聚合
  */
  public static void twoPhaseAggregation(DataStream<Event> stream) {
    // 第一阶段: 局部聚合(添加随机前缀)
    DataStream<Tuple2<String, Long>> localAgg = stream
      .map(event -> {
      // 为 key 添加随机前缀
      String newKey = event.key + "_" +
      ThreadLocalRandom.current().nextInt(10);
      return Tuple2.of(newKey, 1L);
      })
      .keyBy(t -> t.f0)
      .sum(1);
      // 第二阶段: 全局聚合(去除随机前缀)
      DataStream<Tuple2<String, Long>> globalAgg = localAgg
        .map(t -> {
        // 去除随机前缀
        String originalKey = t.f0.substring(0, t.f0.lastIndexOf('_'));
        return Tuple2.of(originalKey, t.f1);
        })
        .keyBy(t -> t.f0)
        .sum(1);
        globalAgg.print();
        }
        /**
        * 方案3: 使用 Rebalance
        */
        public static void useRebalance(DataStream<Event> stream) {
          DataStream<Event> rebalanced = stream
            .rebalance()  // 轮询分配
            .map(new EventProcessor());
            rebalanced.print();
            }
            public static class Event {
            public String key;
            public String value;
            }
            public static class EventProcessor implements MapFunction<Event, Event> {
              @Override
              public Event map(Event event) {
              return event;
              }
              }
              }

11. 监控与故障排查

11.1 关键监控指标

Flink 监控指标体系
1. 作业级指标
   ├─ 作业状态 (Running/Failed/Canceled)
   ├─ 重启次数
   ├─ 运行时间
   └─ Checkpoint 成功率
2. 算子级指标
   ├─ 处理速度 (Records/sec)
   ├─ 延迟 (Latency)
   ├─ 反压状态 (Backpressure)
   └─ 水位线延迟 (Watermark Delay)
3. TaskManager 指标
   ├─ CPU 使用率
   ├─ 内存使用率
   ├─ 网络 I/O
   └─ GC 频率
4. Checkpoint 指标
   ├─ Checkpoint 大小
   ├─ Checkpoint 耗时
   ├─ Checkpoint 失败次数
   └─ 状态大小
5. Kafka 指标 (如果使用)
   ├─ Consumer Lag
   ├─ 消费速率
   └─ 分区分布

11.2 监控配置

import org.apache.flink.metrics.Counter;
import org.apache.flink.metrics.Histogram;
import org.apache.flink.metrics.Meter;
import org.apache.flink.streaming.api.functions.ProcessFunction;
import org.apache.flink.util.Collector;
/**
* 自定义监控指标
*/
public class CustomMetrics {
public static class MetricsProcessFunction
extends ProcessFunction<Event, Event> {
  private transient Counter eventCounter;
  private transient Meter eventMeter;
  private transient Histogram latencyHistogram;
  @Override
  public void open(Configuration parameters) throws Exception {
  // 注册 Counter
  this.eventCounter = getRuntimeContext()
  .getMetricGroup()
  .counter("events_processed");
  // 注册 Meter
  this.eventMeter = getRuntimeContext()
  .getMetricGroup()
  .meter("events_per_second", new MeterView(60));
  // 注册 Histogram
  this.latencyHistogram = getRuntimeContext()
  .getMetricGroup()
  .histogram("latency", new DescriptiveStatisticsHistogram(1000));
  }
  @Override
  public void processElement(
  Event event,
  Context ctx,
  Collector<Event> out) throws Exception {
    long startTime = System.currentTimeMillis();
    // 处理事件
    processEvent(event);
    // 更新指标
    eventCounter.inc();
    eventMeter.markEvent();
    long latency = System.currentTimeMillis() - startTime;
    latencyHistogram.update(latency);
    out.collect(event);
    }
    private void processEvent(Event event) {
    // 业务逻辑
    }
    }
    public static class Event {
    public String id;
    public String data;
    public long timestamp;
    }
    }

11.3 常见问题排查

/**
* Flink 故障排查指南
*/
public class TroubleshootingGuide {
/**
* 问题1: Checkpoint 超时
* 原因: 状态太大、网络慢、反压
* 解决:
* 1. 增加 Checkpoint 超时时间
* 2. 增大 Checkpoint 间隔
* 3. 使用 RocksDB 增量 Checkpoint
* 4. 优化状态大小
*/
public static void checkpointTimeout() {
System.out.println("Checkpoint Timeout 排查:");
System.out.println("1. 检查 Checkpoint 配置");
System.out.println("2. 查看状态大小");
System.out.println("3. 检查网络状况");
System.out.println("4. 查看是否有反压");
}
/**
* 问题2: 反压 (Backpressure)
* 原因: Sink 写入慢、算子处理慢、资源不足
* 解决:
* 1. 增加并行度
* 2. 优化算子逻辑
* 3. 增加资源配置
* 4. 优化 Sink 写入性能
*/
public static void backpressure() {
System.out.println("Backpressure 排查:");
System.out.println("1. 查看 Web UI 反压指标");
System.out.println("2. 检查 Sink 性能");
System.out.println("3. 分析算子处理速度");
System.out.println("4. 查看资源使用情况");
}
/**
* 问题3: OOM (Out Of Memory)
* 原因: 状态太大、对象未释放、配置不合理
* 解决:
* 1. 增加 TaskManager 内存
* 2. 配置状态 TTL
* 3. 使用 RocksDB State Backend
* 4. 优化代码,避免内存泄漏
*/
public static void outOfMemory() {
System.out.println("OOM 排查:");
System.out.println("1. 分析 GC 日志");
System.out.println("2. 检查状态大小");
System.out.println("3. 查看内存配置");
System.out.println("4. 使用 MAT 分析 Heap Dump");
}
/**
* 问题4: Kafka Consumer Lag 高
* 原因: 消费速度慢、Kafka 分区不均
* 解决:
* 1. 增加 Source 并行度
* 2. 调整 Kafka 分区数
* 3. 优化处理逻辑
* 4. 检查网络连接
*/
public static void kafkaLag() {
System.out.println("Kafka Lag 排查:");
System.out.println("1. 查看消费速率");
System.out.println("2. 检查分区分布");
System.out.println("3. 分析处理性能");
System.out.println("4. 查看反压情况");
}
}

12. 总结

12.1 Flink 技术栈总览

Flink 生态系统
应用层
├─ Flink SQL / Table API
├─ DataStream API
├─ DataSet API (批处理)
└─ CEP (复杂事件处理)
连接器层
├─ Kafka Connector
├─ JDBC Connector
├─ Elasticsearch Connector
├─ HBase Connector
├─ CDC Connectors
└─ Custom Connectors
运行时层
├─ JobManager (作业管理)
├─ TaskManager (任务执行)
├─ Checkpoint (容错)
└─ State Backend (状态存储)
资源管理层
├─ Standalone
├─ YARN
├─ Kubernetes
└─ Mesos

12.2 最佳实践总结

  1. 架构设计

    • 合理划分任务并行度
    • 选择合适的时间语义
    • 设计良好的状态管理策略
  2. 性能优化

    • 启用 Operator Chaining
    • 使用高效的序列化器
    • 合理配置 Checkpoint 间隔
    • 处理数据倾斜问题
  3. 容错保障

    • 配置合适的重启策略
    • 使用 Exactly-Once 语义
    • 定期创建 Savepoint
    • 监控 Checkpoint 成功率
  4. 生产部署

    • 选择 RocksDB 作为 State Backend
    • 配置资源隔离
    • 设置告警监控
    • 建立运维流程
  5. 代码规范

    • 合理使用状态
    • 避免在算子中使用阻塞操作
    • 正确处理序列化问题
    • 编写单元测试

12.3 学习资源

  • 官方文档: https://flink.apache.org
  • GitHub: https://github.com/apache/flink
  • 中文社区: https://flink-china.org
  • 实战项目: Flink CDC、Flink ML、Flink Stateful Functions

12.4 未来展望

Apache Flink 正在不断发展,主要方向包括:

  1. Streaming SQL: 更强大的流式 SQL 能力
  2. Machine Learning: 流式机器学习集成
  3. Kubernetes Native: 更好的云原生支持
  4. Python API: 更完善的 Python 支持
  5. 实时数仓: 构建实时数据仓库

附录: 常用命令

# 提交作业
flink run -c com.example.MainClass -p 4 myapp.jar
# 查看作业列表
flink list
# 取消作业
flink cancel <jobId>
  # 创建 Savepoint
  flink savepoint <jobId> <savepointPath>
    # 从 Savepoint 恢复
    flink run -s <savepointPath> -c com.example.MainClass myapp.jar
      # 查看 Checkpoint 信息
      flink checkpoint-stats <jobId>
        # 修改并行度
        flink modify <jobId> -p 8

posted @ 2026-01-09 19:38  clnchanpin  阅读(33)  评论(0)    收藏  举报