点一下关注吧!!!非常感谢!!持续更新!!!
🚀 AI篇持续更新中!(长期更新)
AI炼丹日志-31- 千呼万唤始出来 GPT-5 发布!"快的模型 + 深度思考模型 + 实时路由",持续打造实用AI工具指南!📐🤖
💻 Java篇正式开启!(300篇)
目前2025年10月07日更新到: Java-141 深入浅出 MySQL Spring事务失效的常见场景与解决方案详解(3) MyBatis 已完结,Spring 已完结,Nginx已完结,Tomcat已完结,分布式服务正在更新!深入浅出助你打牢基础!
📊 大数据板块已完成多项干货更新(300篇):
包括 Hadoop、Hive、Kafka、Flink、ClickHouse、Elasticsearch 等二十余项核心组件,覆盖离线+实时数仓全栈! 大数据-278 Spark MLib - 基础介绍 机器学习算法 梯度提升树 GBDT案例 详解

章节内容
上节我们完成了如下的内容:
- Flink Window 背景总览
- Flink Window 滚动时间窗口
- 基于时间驱动
- 基于事件驱动

滑动时间窗口

滑动窗口是固定窗口更广义的一种形式,它通过引入滑动间隔实现了窗口的动态移动。滑动窗口由固定的窗口长度(window size)和滑动间隔(slide interval)两个关键参数组成,其中窗口长度决定了每个窗口包含的数据范围,而滑动间隔则控制着窗口的移动频率。
Flink 的滑动时间窗口(Sliding Window)是一种特别适用于流式数据处理场景的窗口机制。它能够在持续流动的数据流中,按照设定的时间范围定期进行数据计算和聚合。这种窗口机制广泛应用于实时监控、异常检测、趋势分析等需要对数据进行周期性分析的场景。例如,在电商平台中,可以使用窗口长度为1小时、滑动间隔为5分钟的滑动窗口来实时统计近一小时的销售额变化趋势。
具体实现时,滑动窗口会按照指定的窗口大小和滑动步长不断地在数据流上滑动。比如设置窗口大小为10分钟、滑动步长为5分钟,那么每个窗口会包含10分钟的数据,但每隔5分钟就会产生一个新的窗口。这样的设计使得相邻窗口之间存在5分钟的重叠数据,从而确保计算结果的连续性。对于每个窗口内的数据,Flink都会独立执行预定义的聚合函数(如sum、count、avg等)进行计算。
与固定窗口相比,滑动窗口的优势在于它能够提供更细粒度的时间覆盖,避免重要数据被窗口边界分割。但同时也会带来更高的计算开销,因为相同的数据可能会被多个窗口重复计算。在实际应用中,需要根据业务需求和数据特点,在计算精度和性能开销之间找到合适的平衡点。
类型特点
窗口长度固定,可以有重叠。滑动窗口是一种常用的数据处理模式,具有以下特点:
-
窗口重叠机制:
- 滑动窗口会有重叠部分,因此每个事件可能会被包含在多个窗口中
- 重叠程度由窗口大小和滑动步长决定。例如:
- 窗口大小为5分钟,滑动步长为1分钟,则重叠部分为4分钟
- 窗口大小为1小时,滑动步长为30分钟,则重叠部分为30分钟
-
典型应用场景:
- 滑动窗口更适合定期计算某个时间范围内的聚合值,像是:
- 移动平均值(如股票5日均线)
- 最近一段时间的活跃用户统计(如过去24小时内活跃用户数)
- 实时流量监控(如每分钟请求量)
- 传感器数据平滑处理(如温度传感器5秒均值)
- 滑动窗口更适合定期计算某个时间范围内的聚合值,像是:
-
实现方式:
- 基于时间:按固定时间间隔滑动(如每1分钟计算一次过去5分钟的数据)
- 基于事件数量:每收到N个事件就计算一次最近M个事件的数据
- 混合模式:同时考虑时间和事件数量触发计算
-
计算特点:
- 增量计算:可以复用重叠部分的计算结果,提高效率
- 边界处理:需要考虑窗口边界事件的归属问题
- 状态管理:需要维护窗口状态,特别是长窗口的场景
-
优势比较:
- 相比滚动窗口(无重叠),能提供更平滑的数据变化趋势
- 相比会话窗口(基于事件间隔),能保证定期输出计算结果
- 特别适合需要连续监控和实时响应的应用场景
关键参数
窗口大小(window size)
窗口大小定义了每个时间窗口所包含的数据范围,是流处理中最重要的参数之一。它表示系统处理数据时划分的时间段长度,通常以时间单位(秒、分钟、小时)表示。例如:
- 10秒窗口:系统每10秒收集的数据作为一个处理单元
- 1分钟窗口:每分钟的数据作为一个批次处理
窗口大小直接影响:
- 数据处理延迟:窗口越大,延迟越高
- 结果精确度:窗口越小,结果越实时
- 系统负载:窗口越小,计算频率越高
滑动步长(slide interval)
滑动步长决定了窗口移动的频率,即系统创建新窗口的时间间隔。这个参数控制着:
- 窗口重叠程度:当步长小于窗口大小时,会产生窗口重叠
- 计算触发频率:步长越小,计算触发越频繁
典型配置示例:
-
10秒窗口+5秒步长:
- 窗口1:00:00-00:10
- 窗口2:00:05-00:15
- 窗口3:00:10-00:20
- 这样每5秒就会产生一个包含10秒数据的新窗口
-
1分钟窗口+30秒步长:
- 窗口1:00:00-01:00
- 窗口2:00:30-01:30
- 窗口3:01:00-02:00
应用场景:
- 实时监控系统:5秒窗口+1秒步长,实现准实时的监控报警
- 每日统计报表:24小时窗口+24小时步长,每天生成一次完整报表
- 用户行为分析:30分钟窗口+5分钟步长,平衡实时性和计算开销
基于时间驱动
场景:我们可以每30秒计算一次最近一分钟用户购买的商品数
java
package icu.wzk;
public class SlidingWindow {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
DataStreamSource<String> dataStreamSource = env.socketTextStream("localhost", 9999);
SingleOutputStreamOperator<Tuple2<String, Integer>> mapStream = dataStreamSource
.map(new MapFunction<String, Tuple2<String, Integer>>() {
@Override
public Tuple2<String, Integer> map(String value) throws Exception {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
long timeMillis = System.currentTimeMillis();
int random = new Random().nextInt(10);
System.out.println("value: " + value + ", random: " + random +
", timestamp: " + format.format(timeMillis));
return Tuple2.of(value, random);
}
});
KeyedStream<Tuple2<String, Integer>, Tuple> keyedStream = mapStream
.keyBy(new KeySelector<Tuple2<String, Integer>, Tuple>() {
@Override
public Tuple getKey(Tuple2<String, Integer> value) throws Exception {
return Tuple1.of(value.f0);
}
});
WindowedStream<Tuple2<String, Integer>, Tuple, TimeWindow> timeWindow = keyedStream
.timeWindow(Time.seconds(10), Time.seconds(5));
timeWindow.apply(new MyTimeWindowFunction()).print();
env.execute("SlidingWindow");
}
}
基于事件驱动
java
package icu.wzk;
public class SlidingWindow {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
DataStreamSource<String> dataStreamSource = env.socketTextStream("localhost", 9999);
SingleOutputStreamOperator<Tuple2<String, Integer>> mapStream = dataStreamSource
.map(new MapFunction<String, Tuple2<String, Integer>>() {
@Override
public Tuple2<String, Integer> map(String value) throws Exception {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
long timeMillis = System.currentTimeMillis();
int random = new Random().nextInt(10);
System.out.println("value: " + value + ", random: " + random +
", timestamp: " + format.format(timeMillis));
return Tuple2.of(value, random);
}
});
KeyedStream<Tuple2<String, Integer>, Tuple> keyedStream = mapStream
.keyBy(new KeySelector<Tuple2<String, Integer>, Tuple>() {
@Override
public Tuple getKey(Tuple2<String, Integer> value) throws Exception {
return Tuple1.of(value.f0);
}
});
WindowedStream<Tuple2<String, Integer>, Tuple, GlobalWindow> globalWindow = keyedStream
.countWindow(3, 2);
globalWindow.apply(new MyCountWindowFuntion()).print();
env.execute("SlidingWindow");
}
}
会话窗口
由一系列事件组合一个指定时间长度timeout间隙组成,类似于Web应用的Session,也就是一段时间没有接收到新数据会生成新的窗口。 Session窗口分配器通过Session活动来对元素进行分组,Session窗口跟滚动窗口和滑动窗口相比,不会有重叠和固定的开始时间和结束时间的情况。 Session窗口在一个固定的时间周期内不再收到元素,即非活动间隔产生,那么这个窗口就会关闭。 一个Session窗口通过一个Session间隔来配置,这个Session间隔定义了非活跃周期的长度,当这个非活跃周期产生,那么当前的Session将关闭并且后续的元素将被分配到新的Session窗口去。
类型特点
会话窗口(Session Window)是一种特殊的时间窗口类型,具有以下具体特点:
-
非重叠性:
- 会话窗口之间完全独立,不会出现时间重叠
- 每个窗口都是单独存在的个体
- 不同于翻滚窗口的固定分割和滑动窗口的重叠特性
-
动态时间边界:
- 没有预设的固定开始和结束时间
- 窗口的开启由第一个到达的元素触发
- 窗口的关闭取决于配置的超时时间(Session Gap)
-
基于活动的窗口机制:
- 当超过配置的会话间隙(Session Gap)时间没有新元素到达时,当前会话窗口会自动关闭
- 例如:如果设置Session Gap为5分钟,当超过5分钟没有新数据,当前窗口就会结束
-
自适应分割:
- 后续到达的元素会触发新会话窗口的创建
- 每个新窗口都独立于之前的窗口
- 窗口持续时间完全取决于数据到达模式
应用场景示例:
- 用户行为分析:将用户连续的操作记录作为一个会话
- 网络会话管理:TCP连接的开始和结束
- 电商场景:用户的一次完整购物流程
技术实现特点:
- 需要维护每个键(key)的最新活动时间戳
- 需要定期检查超时的窗口
- 适合处理不规律到达的数据流
与常规窗口的对比:
- 翻滚窗口:固定大小,固定间隔
- 滑动窗口:固定大小,可重叠
- 会话窗口:动态大小,不重叠
基于时间驱动
java
package icu.wzk;
public class SessionWindow {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
DataStreamSource<String> dataStreamSource = env.socketTextStream("localhost", 9999);
SingleOutputStreamOperator<Tuple2<String, Integer>> mapStream = dataStreamSource
.map(new MapFunction<String, Tuple2<String, Integer>>() {
@Override
public Tuple2<String, Integer> map(String value) throws Exception {
return null;
}
});
KeyedStream<Tuple2<String, Integer>, Tuple> keyedStream = mapStream
.keyBy(new KeySelector<Tuple2<String, Integer>, Tuple>() {
@Override
public Tuple getKey(Tuple2<String, Integer> value) throws Exception {
return Tuple1.of(value.f0);
}
});
WindowedStream<Tuple2<String, Integer>, Tuple, TimeWindow> window = keyedStream
.window(ProcessingTimeSessionWindows.withGap(Time.seconds(10)));
window.apply(new MyTimeWindowFunction()).print();
env.execute("SessionWindow");
}
}