Apache Flink 窗口处理函数全解析(增量 + 全量 + 混合)

Flink 窗口(Window) 是处理 无界流数据 时最核心的概念之一,它能将无限制的数据流按 时间或数量 切分成一个个有限的"数据桶",然后在这些"桶"里执行聚合计算。


一、什么是窗口处理函数

Flink 窗口处理函数定义了 窗口内数据如何被计算与输出。根据处理时机不同,可以分为:

类型 是否缓存窗口内所有数据 优点 典型函数
增量处理 低延迟、节省空间 reduceaggregate
全量处理 可访问全窗口数据 applyprocess

二、增量处理 --- 每条数据来就处理

🔹 reduce

  • 每条数据到达都会更新聚合结果
  • 输入 / 累加器 / 输出 类型一致
  • 不保存整个窗口数据,只累加状态
java 复制代码
.reduce(new ReduceFunction<SensorReading>() {
    @Override
    public SensorReading reduce(SensorReading a, SensorReading b) {
        // 计算最大温度
        return a.getTemperature() > b.getTemperature() ? a : b;
    }
})

⚠ 如果窗口只有一条数据,reduce() 不会被调用。


🔹 aggregate

更灵活的累加处理:

✔ 输入类型、累加器类型、输出类型可以不一致

✔ 可在累加器中做更复杂逻辑

核心方法:

  • createAccumulator():初始化累加器
  • add():每条记录到达调用
  • getResult():窗口触发时返回结果
  • merge():会话窗口需要合并状态

三、全量处理 --- 等窗口触发再处理

不同于增量处理,全量处理保留整个窗口数据,并在窗口结束时一次性计算:

🔹 apply

适合窗口中数据量不是特别大但需要全量访问的场景。

java 复制代码
.apply(new WindowFunction<...>() {
    @Override
    public void apply(...) {
        // 访问完整窗口数据操作
    }
});

🔹 process

最底层的全量处理函数,可以获取更多上下文信息,如窗口时间、watermark、状态等:

java 复制代码
.process(new ProcessWindowFunction<SensorReading, String, String, TimeWindow>() {
    @Override
    public void process(String key, Context context,
                        Iterable<SensorReading> elements,
                        Collector<String> out) {
        // 访问窗口全部元素
    }
});

这个函数比 apply 更强大。


四、为什么这些很重要?

  • 大部分真实业务中,我们既希望结果准确(全量处理),又希望响应快(增量处理)。
  • 通过增量处理先减少延迟,再用全量处理做更精细计算,可以在性能和准确性间取得平衡。

五、传感器温度实时窗口统计

事件模型 --- SensorReading

java 复制代码
public class SensorReading {
    private String sensorId;
    private Long timestamp;
    private Double temperature;

    public SensorReading() {}

    public SensorReading(String sensorId, Long timestamp, Double temperature) {
        this.sensorId = sensorId;
        this.timestamp = timestamp;
        this.temperature = temperature;
    }

    // getter / setter

    @Override
    public String toString() {
        return "SensorReading{" +
                "sensorId='" + sensorId + '\'' +
                ", timestamp=" + timestamp +
                ", temperature=" + temperature +
                '}';
    }
}

🔹 5.2 自定义模拟数据源 --- SensorSource

java 复制代码
public class SensorSource implements SourceFunction<SensorReading> {
    private volatile boolean running = true;

    @Override
    public void run(SourceContext<SensorReading> ctx) throws Exception {
        Random rand = new Random();

        while (running) {
            long timestamp = System.currentTimeMillis();
            // 5 个 sensor 并行发送
            for (int i = 0; i < 5; i++) {
                String sensorId = "sensor_" + i;
                double temp = 20 + rand.nextGaussian() * 10;
                ctx.collect(new SensorReading(sensorId, timestamp, temp));
            }
            Thread.sleep(200);
        }
    }

    @Override
    public void cancel() {
        running = false;
    }
}

主程序 --- 引入 Watermark + 多种窗口处理

java 复制代码
public class FlinkWindowDemo {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        DataStream<SensorReading> stream = env
                .addSource(new SensorSource())
                .assignTimestampsAndWatermarks(
                        WatermarkStrategy
                                .<SensorReading>forBoundedOutOfOrderness(Duration.ofSeconds(5))
                                .withTimestampAssigner((event, ts) -> event.getTimestamp())
                );

        // ------ 增量处理:reduce
        stream
                .keyBy(SensorReading::getSensorId)
                .window(TumblingEventTimeWindows.of(Duration.ofSeconds(10)))
                .reduce((a, b) -> a.getTemperature() > b.getTemperature() ? a : b)
                .print("Reduce Max Temp");

        // ------ 全量处理:process
        stream
                .keyBy(SensorReading::getSensorId)
                .window(TumblingEventTimeWindows.of(Duration.ofSeconds(10)))
                .process(new ProcessWindowFunction<SensorReading, String, String, TimeWindow>() {
                    @Override
                    public void process(String key, Context ctx,
                                        Iterable<SensorReading> elements,
                                        Collector<String> out) {
                        int count = 0;
                        double sum = 0;
                        for (SensorReading r : elements) {
                            count++;
                            sum += r.getTemperature();
                        }
                        out.collect(key + " avg=" + (sum / count) + ", count=" + count);
                    }
                })
                .print("Process Avg Temp");

        env.execute("Flink Window Demo");
    }
}

六、何时用哪种处理方式?

场景 推荐
实时性要求高 & 只需简单汇总 增量处理 (reduce, aggregate)
需要完整窗口统计 全量处理 (process, apply)
又要快响应又要丰富输出 混合模式

七、总结

✔ 增量处理 ------ 快、少空间,但无法访问全部数据

✔ 全量处理 ------ 能访问所有数据,结果丰富但占空间

✔ 混合模式 ------ 最灵活、兼顾性能和业务需求

相关推荐
武子康3 小时前
大数据-244 离线数仓 - Hive ODS 层建表与分区加载实战(DataX→HDFS→Hive)
大数据·后端·apache hive
武子康1 天前
大数据-243 离线数仓 - 实战电商核心交易增量导入(DataX - HDFS - Hive 分区
大数据·后端·apache hive
代码匠心3 天前
从零开始学Flink:Flink SQL四大Join解析
大数据·flink·flink sql·大数据处理
武子康4 天前
大数据-242 离线数仓 - DataX 实战:MySQL 全量/增量导入 HDFS + Hive 分区(离线数仓 ODS
大数据·后端·apache hive
SelectDB5 天前
易车 × Apache Doris:构建湖仓一体新架构,加速 AI 业务融合实践
大数据·agent·mcp
武子康5 天前
大数据-241 离线数仓 - 实战:电商核心交易数据模型与 MySQL 源表设计(订单/商品/品类/店铺/支付)
大数据·后端·mysql
IvanCodes5 天前
一、消息队列理论基础与Kafka架构价值解析
大数据·后端·kafka
武子康6 天前
大数据-240 离线数仓 - 广告业务 Hive ADS 实战:DataX 将 HDFS 分区表导出到 MySQL
大数据·后端·apache hive
字节跳动数据平台7 天前
5000 字技术向拆解 | 火山引擎多模态数据湖如何释放模思智能的算法生产力
大数据
武子康7 天前
大数据-239 离线数仓 - 广告业务实战:Flume 导入日志到 HDFS,并完成 Hive ODS/DWD 分层加载
大数据·后端·apache hive