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)
又要快响应又要丰富输出 混合模式

七、总结

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

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

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

相关推荐
绝缘体12 小时前
企微scrm的核心功能有哪些?
大数据·企业微信
Gazer_S2 小时前
【Git 操作指南:分支同步与冲突批量解决】
大数据·git·elasticsearch
G皮T2 小时前
【Elasticsearch】OpenDistro/Elasticsearch 权限分类详解
大数据·elasticsearch·搜索引擎·全文检索·kibana·权限管理·opensearch
高频交易dragon2 小时前
配对交易策略大观
大数据·人工智能
007张三丰2 小时前
Git 常用使用规范与高效技巧
大数据·git·elasticsearch
weixin_457297102 小时前
Hadoop面试题
大数据·hadoop·分布式
Jackyzhe2 小时前
Flink源码阅读:Kafka Connector
大数据·flink·kafka
萤丰信息2 小时前
智慧园区新基建:“云-管-端”架构的破局之路与数智革命
大数据·人工智能·科技·安全·架构·智慧城市·智慧园区
TDengine (老段)2 小时前
TDengine R 语言连接器进阶指南
大数据·开发语言·数据库·r语言·时序数据库·tdengine·涛思数据