Flink 电商实战案例:实时类目GMV统计

一、项目简介

本文手把手实现一套生产级可落地的 Flink 电商实时计算案例,基于 Flink 1.18 版本、Java 语言开发。

核心业务:实时采集电商订单流,关联商品维表,按类目5秒滚动窗口统计实时GMV、下单量、支付订单数

本案例解决新手常见痛点:

  • 杜绝错误的「系统时间代替窗口时间」写法
  • 实战 广播流维表JOIN(电商最常用宽表拼接方案)
  • 事件时间+水印处理乱序数据
  • 增量聚合+窗口函数分层计算(高性能)
  • 支持控制台打印 + 实时写入MySQL数据库双模式
  • 开启Checkpoint,支持Exactly-Once语义

二、技术栈

  • Flink 1.18.0(批流一体)
  • Java 8
  • 事件时间 + 水印(乱序数据处理)
  • 广播流 Broadcast(维表实时关联)
  • TumblingEventTimeWindow 滚动窗口
  • Aggregate + WindowFunction 组合增量聚合
  • Flink JdbcSink(生产级数据库写入)
  • Checkpoint 容错恢复、 Exactly-Once 入库

三、业务流程架构

模拟电商实时数据流链路:

  1. 自定义数据源持续生成模拟用户下单订单流
  2. 数据清洗:过滤无效订单、用户信息脱敏
  3. 商品维表通过广播流全局广播
  4. 订单流关联广播维表,生成订单商品宽表
  5. 按商品类目分组,5秒事件时间滚动窗口聚合
  6. 统计各类目:总下单数、支付订单数、区间GMV
  7. 结果支持控制台格式化打印 + 实时写入MySQL数据库

四、完整工程代码(新增数据库写入能力)

1. Maven 依赖 pom.xml(新增MySQL驱动、JDBC依赖)

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.ecommerce</groupId> <artifactId>flink-order-real</artifactId> <version>1.0.0</version> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <flink.version>1.18.0</flink.version> <scala.binary.version>2.12</scala.binary.version> <mysql.version>8.0.33</mysql.version> </properties> <dependencies> <!-- Flink 核心 --> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-java</artifactId> <version>{flink.version}\ \ \ \org.apache.flink\ \flink-streaming-java\ \{flink.version}</version> </dependency> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-clients</artifactId> <version>{flink.version}\ \ \ \org.apache.flink\ \flink-runtime-web\ \{flink.version}</version> </dependency> <!-- Flink JDBC 数据库写入核心依赖 --> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-jdbc</artifactId> <version>{flink.version}\ \ \

  • 支持失败重试:网络抖动、数据库卡顿自动重试,避免数据丢失
  • 支持Exactly-Once:配合Checkpoint + 唯一索引,彻底解决重复数据问题
  • 官方维护:生产环境标准方案,稳定无BUG

2. 幂等性入库核心

代码中使用 ON DUPLICATE KEY UPDATE 语法 + 数据库唯一索引:

  • 窗口触发、Checkpoint重试、任务重启时,不会重复插入数据
  • 重复窗口数据会自动覆盖更新,保证数据最终一致性

3. 本地运行 & 服务器集群运行日志区别

  • 本地IDE运行 :System.out.println 打印在IDE控制台,数据直接写入本地MySQL
  • Flink集群运行 :控制台打印日志输出到 taskmanager.out 文件,数据库写入逻辑完全不变,无需改代码

六、运行步骤

  • 本地MySQL创建 flink_db 数据库,执行上方建表语句
  • 修改代码中 MySQL 账号、密码、地址为自己的环境
  • 启动程序,控制台打印统计数据,同时自动入库MySQL
  • 执行SQL查询实时数据:SELECT * FROM category_gmv_stat;

|---------------------------------------------------------------------------------------------|
| java StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); |

Flink 机制:

  1. 本地 IDE 运行时 :自动创建 本地迷你单节点环境(LocalEnvironment)
  • 不连接任何远程 Flink 集群
  • 不依赖服务器、不依赖 Yarn、不依赖 Standalone 集群
  • 所有 Task、JobManager、TaskManager 全部在你本地 JVM 进程内模拟
  1. 代码里没有任何集群配置
  • 没有指定集群地址
  • 没有提交 Yarn/Standalone 集群
  • 没有打包 Job

如何改成【真正的 Flink 服务器集群执行】?

只需要 2 步,企业生产标准操作:

1. 代码无需改动(核心!!)

Flink 代码不用改一行

Flink 设计理念:一套代码,本地调试、集群运行无缝切换

2. 部署方式改变

步骤 1:maven 打包成 jar

|------------------------|
| bash mvn clean package |

步骤 2:服务器集群命令提交

|-----------------------------------------------------------------------------------------------------------------------------------------------------------|
| bash # Standalone 集群提交 flink run -m flink集群IP:8081 -p 1 flink-order-real-1.0.0.jar # Yarn 集群提交 flink run -yjm 1024m -ytm 2048m flink-order-real-1.0.0.jar |

此时任务就会跑在远端 Flink 服务器集群,由集群的 JobManager、TaskManager 调度执行。

七、生产环境优化方案

  • 替换数据源:改为Kafka/CDC读取真实订单数据
  • 状态后端优化:开启RocksDB状态后端,支持超大窗口状态
  • 调优批量参数:根据业务QPS调整 batchSize、批量间隔时间
  • 日志替换 :删除 System.out.println,替换为Slf4j日志框架
  • 参数外置:数据库地址、账号密码配置到配置文件,不硬编码