你是一家名叫“川流阁”的火锅店店长。店门口的客人从早到晚络绎不绝,永远不会在某一天突然停下——这就像 Flink 面对的“无界数据流”。但你必须交报表:午市来了多少人、晚市翻了几次台、某位熟客今天来了几趟。数据永远在流动,计算却需要“开始”和“结束”。于是你把没有尽头的客流切成一段段有头有尾的班次,这就是窗口。班次可以固定(滚动窗口)、可以重叠(滑动窗口),也可以按一桌客人的聚会来界定(会话窗口)。这背后还有一位掌握“最后下单钟”的主厨,用水位线告诉所有人:早于这个时刻的订单,我都接完了。迟到客人也有预案——允许延迟的宽限,以及侧输出的外卖架。读完这篇,你将明白 Flink 如何把“读不完”的流水变成“算得清”的报表。

岗位关联

掌握窗口机制,你就能把“读不完”的实时数据变成“算得清”的业务指标,这是通往实时计算与实时 AI 特征工程的第一把钥匙。

目录
  1. 可视化演示
  2. 窗口概念:给无界流画边界
  3. 滚动窗口:固定班次不重叠
  4. 滑动窗口:移动的聚光灯
  5. 会话窗口:一桌客人的聚会
  6. 窗口分配触发:领位员和厨房铃
  7. 水位线机制:主厨的最后下单钟
  8. 迟到数据处理:迟到的客人怎么办
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。

本节配有交互动效(进入视口后自动加载,约 3KB)。

系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T

窗口概念:给无界流画边界

“川流阁”没有打烊的一天,门口的红灯笼永远亮着,客人如流水般涌进来。可店长每周一要给总部交报表:午市来了多少人、晚市卖了多少锅底。客人是无穷的,统计却必须有头有尾。于是店长把一天切成“午市 11:00–13:00、晚市 17:00–21:00”这样的班次——这些班次就是“窗口”。

在 Flink 的世界里,数据流就像进店客人一样永远没有终点,这就是无界数据流(unbounded stream)。但聚合计算(sum、count、avg)必须知道“算到哪个数为止”,所以 Flink 引入了窗口机制:把无限的数据流按时间或其他维度切成有限片段,每个片段独立做聚合。窗口有明确的边界,通常用半开区间 $[start, end)$ 表示:包含开始时刻,不包含结束时刻,这样相邻窗口之间不会重复也不会遗漏。

窗口机制的核心是两件事:一是“把每一条数据放进哪个窗口”——由窗口分配器决定;二是“什么时候把窗口的计算结果吐出来”——由触发器决定。每条数据进入 Flink 后,会被赋予一个时间戳(事件时间或处理时间),然后根据时间戳划入一个或多个窗口,窗口内维护独立的累加状态。

就像火锅店的班次表把一天切成午市、晚市,Flink 的窗口让流处理这口“大火锅”有了分桌下菜的依据。没有窗口,聚合就是一笔永远算不完的流水账;有了窗口,每一笔账都有了归属的时段。

S=⋃iWi,Wi=[tistart,tiend)\displaystyle S = \bigcup_{i} W_i, \quad W_i = [t_i^{\text{start}}, t_i^{\text{end}})
这组式子表达:整条无界流 S 被切分成若干有界窗口 W_i,每个窗口是一个“左闭右开”的时间区间。
  1. $S$无界数据流:源源不断产生的事件集合,没有终点,像火锅店不停进门的客人。
    S={e1,e2,e3,…}S = \{e_1, e_2, e_3, \ldots\}
  2. $\bigcup_i W_i$窗口并集:所有窗口拼在一起近似覆盖整条数据流,让无界流可以分段处理。
    S≈⋃i=1nWiS \approx \bigcup_{i=1}^{n} W_i
  3. $W_i = [\text{start}, \text{end})$半开区间窗口:窗口包含起点、不包含终点,保证相邻窗口不重不漏。
    Wi=[tistart,tiend)W_i = [t_i^{\text{start}}, t_i^{\text{end}})

滚动窗口:固定班次不重叠

川流阁的午市班次雷打不动:11:00–13:00、13:00–15:00,首尾相接,互不重叠。任何一位客人只要在 12:30 进门,就必然属于午市这一个班次,不会被算进晚市。这种“一刀切”的划分方式,就是滚动窗口(Tumbling Window)。

滚动窗口是所有窗口中最直观的一种:长度固定(如 10 分钟),窗口之间没有重叠,每个数据恰好属于一个窗口。Flink 里的 TumblingWindowAssigner 会按照窗口长度把时间轴切成等长的小段,所有窗口严格对齐到全局时间轴的整数边界,而不是从第一条数据到来时才起算。

滚动窗口适合“每个整点统计一次订单量”“每分钟统计一次在线人数”这类场景。它的优点是简单、低延迟、状态开销小;缺点是边界生硬——如果业务关心“过去 1 小时”的滚动状态,滚动窗口就无法表达。

班次表虽然简单,但胜在清晰可靠。滚动窗口就像火锅店最死板的店长——说好两点交接班,就绝不拖一分钟,任何客人都只属于一个班次,账目永远对得上。

wstart=⌊tsize⌋×size\displaystyle w_{\text{start}} = \left\lfloor \frac{t}{size} \right\rfloor \times size
滚动窗口的起点 = 事件时间整除窗口长度后再乘以窗口长度,例如时间 15、长度 10,则窗口起点为 10,落入 [10,20)。
  1. $t$事件时间:每条数据实际发生的时刻,比如客人进店的时间戳。
    t=事件的原始时间戳t = \text{事件的原始时间戳}
  2. $size$窗口长度:滚动窗口的固定时长,如 10 分钟一个班次。
    size=10 分钟\text{size} = 10 \ \text{分钟}
  3. $\lfloor \cdot \rfloor$向下取整:把除法结果向下取整,保证窗口起点总在整数边界上。
    ⌊1510⌋=1\left\lfloor \frac{15}{10} \right\rfloor = 1
  4. $\times size$还原起点:商再乘回窗口长度,得到窗口起点。
    wstart=1×10=10w_{\text{start}} = 1 \times 10 = 10

滑动窗口:移动的聚光灯

店长不满足于“整点看报表”,他想每 5 分钟就瞄一眼“过去 1 小时”的上座率。这意味着:13:00 看的是 12:00–13:00,13:05 看的是 12:05–13:05,窗口之间重叠了 55 分钟。这就是滑动窗口(Sliding Window)——一束不断前移的聚光灯,照亮的永远是一段“最近的历史”。

滑动窗口由两个参数决定:窗口长度(size)和滑动步长(slide)。每过一个 slide 时间,就启动一个新的窗口;相邻窗口重叠 size − slide 的时间。因此,一个事件会同时属于多个窗口。比如 size=10、slide=2 时,一个事件会落进 5 个窗口,每个窗口都会被这个数据贡献一份聚合值。

滑动窗口适合“每 5 分钟输出一次过去 1 小时的平均值”这类需要高频刷新的场景。代价是计算量成倍增加——窗口越多,同一份数据要参与聚合的次数就越多,就像聚光灯扫过之处,每个角落都被反复照亮。

滑动窗口像一束移动的聚光灯,永远照亮“最近的一段时间”。虽然重叠计算消耗更多资源,但对于那些“既要看趋势、又要看新鲜”的业务,它是最贴心的选择。

n窗口数=sizeslide\displaystyle n_{\text{窗口数}} = \frac{size}{slide}
一个事件归属的窗口个数 = 窗口长度 ÷ 滑动步长。窗口长度 10、步长 2 时,每个事件要被算 5 次。
  1. $size$窗口长度:每次统计覆盖的时间跨度,即聚光灯照射的范围。
    size=10 分钟\text{size} = 10 \ \text{分钟}
  2. $slide$滑动步长:窗口向前平移的时间间隔,即聚光灯移动的节奏。
    slide=2 分钟\text{slide} = 2 \ \text{分钟}
  3. $\frac{size}{slide}$相除得窗口数:一个事件会落进多个窗口,产生重叠统计。
    n=102=5 个窗口n = \frac{10}{2} = 5 \ \text{个窗口}

会话窗口:一桌客人的聚会

有一群朋友约在川流阁聚会,第一个人 12:00 到,最后一个人 14:30 才走。桌上一直有人举筷、加菜、聊天。店长怎么判断“这桌的聚会”结束了?答案是:超过 15 分钟没有新客人坐下、也没有人加菜,就认为这桌散了。这 12:00–14:30 的整段聚会,就是一个会话窗口(Session Window)。

会话窗口的长度不是预先定死的,而是由数据本身的活动节奏决定的。它由一个“会话间隔”(session gap)参数控制:如果相邻两条数据的时间差超过了 gap,就把前面的数据归为一个会话窗口;否则就继续“攒着”。窗口长度完全动态,可能是一分钟,也可能是一小时。

会话窗口特别适合用户行为分析:一次网页访问、一轮 App 操作、一段游戏对局,都可以按“静默超过 30 秒”来切分。Flink 的 SessionWindowAssigner 会自动合并那些间隔小于 gap 的临时窗口,直到静默超时才把整个会话关闭。

会话窗口就像火锅店的“翻台判断”——不是看表,而是看桌面上还有没有人动筷子。这种动态边界让它成为分析用户活跃链路、点击流 session 的利器。

Wend=Wstart+gap,若 tnext−tlast>gap\displaystyle W_{\text{end}} = W_{\text{start}} + \text{gap}, \quad \text{若} \ t_{\text{next}} - t_{\text{last}} > \text{gap}
会话窗口没有固定长度:相邻两条数据的时间差超过 gap 时,当前会话宣告结束,窗口闭合。
  1. $\text{gap}$会话间隔:允许的最大静默期,超过它就算“聚会散了”。
    gap=15 分钟\text{gap} = 15 \ \text{分钟}
  2. $W_{\text{start}}$、$W_{\text{end}}$会话起止:会话窗口以第一个事件时间为起点,以最后一个事件时间加 gap 为终点。
    Wstart=tfirst,Wend=tlast+gapW_{\text{start}} = t_{\text{first}}, \quad W_{\text{end}} = t_{\text{last}} + \text{gap}
  3. $t_{\text{next}} - t_{\text{last}} > \text{gap}$间隔判断:相邻事件时间差超过 gap,判定会话到此为止。
    tnext−tlast>gap⇒窗口闭合t_{\text{next}} - t_{\text{last}} > \text{gap} \Rightarrow \text{窗口闭合}

窗口分配触发:领位员和厨房铃

客人们涌进川流阁,谁坐哪桌、哪桌什么时候开炒,需要两个人配合:领位员决定“这位客人属于哪一桌”;厨房铃铛响了,后厨才喊“开炒!”。在 Flink 里,前者的角色是窗口分配器(WindowAssigner),后者的角色是触发器(Trigger)。

窗口分配器负责把每一条数据分派到正确的窗口。滚动窗口分配器把数据按时间均匀切段,滑动窗口分配器把数据同时放进制多个重叠窗口,会话窗口分配器则按数据间隙动态分组。分配器只决定“数据进哪个框”,不决定“结果什么时候算”。

触发器决定窗口何时输出计算结果。默认的事件时间触发器(EventTimeTrigger)会在水位线越过窗口末尾的瞬间触发计算;你也可以自定义触发器,比如“攒够 100 条就触发一次”(计数触发),或者时间 + 计数的组合触发。触发器内部依赖定时器(Timer)来注册“未来某个时刻唤醒我”的请求,每次触发后窗口会执行聚合函数并输出结果。窗口在触发和等待之间,始终保留着自身的中间状态,直到生命周期彻底结束。

分配器定“框”,触发器定“响”,二者协作才能完成一次统计。就像火锅店没有领位员会乱作一团,没有厨房铃则会烧干锅底——窗口机制的这两根支柱,缺一不可。

watermark≥wend⇒触发窗口计算\displaystyle \text{watermark} \geq w_{\text{end}} \Rightarrow \text{触发窗口计算}
默认情况下,水位线推进到窗口的结束时间时,触发器被唤醒,窗口开始输出聚合结果。
  1. $w_{\text{end}}$窗口结束时间:窗口右边界,即班次的“打烊时刻”。
    wend=wstart+sizew_{\text{end}} = w_{\text{start}} + \text{size}
  2. $\text{watermark}$当前水位线:系统认为“所有早于该时刻的事件都已到达”的置信边界。
    watermark=当前水位线值\text{watermark} = \text{当前水位线值}
  3. $\geq$越过边界:水位线追上了窗口末尾,窗口内的数据不再接收新成员。
    watermark≥wend⇒触发\text{watermark} \ge w_{\text{end}} \Rightarrow \text{触发}

水位线机制:主厨的最后下单钟

川流阁有位严厉的主厨:他宣布“13:50 之后再没见到的 13:50 之前的订单,我一律不认了”。这个“最后下单钟”其实是主厨给后厨下发的一个“信心截止点”——时钟指向 13:50 时,他相信所有 13:50 之前发生的订单都已经送到后厨。这个信心截止点,就是 Flink 里的水位线(Watermark)。

为什么要设水位线?因为网络和系统会让数据乱序到达:A 客人在 13:49 下单,消息却可能比 13:48 下单的 B 客人更晚送到。处理时间(机器本地时钟)无法还原真实发生顺序,所以必须用事件时间(数据自带的原始时间戳)来排序。水位线公式很简单:观察到的最大事件时间减去一个乱序容差 ε。ε 是对“最多会迟到多久”的估计,比如网络链路最慢延迟 5 秒,就把水位线设为最大时间戳减 5 秒。

水位线由数据源周期性发出并单调递增:一旦发出 13:50 的水位线,系统就认为 13:50 之前的乱序数据“基本来齐了”,可以安全关闭相关窗口。它会随并行子任务对传播并取最小值对齐,确保整体判断一致。

水位线就是流处理里的“最后下单钟”。它不像处理时间那样只看表,而是看着“订单实际发生的时间”,再往后让出一点缓冲。有了它,乱序数据不再是灾难——只要在主厨的截止线前赶到,一切都能各归其位。

WM=max⁡e∈S已观察(te)−ε\displaystyle WM = \max_{e \in S_{\text{已观察}}} (t_e) - \varepsilon
水位线 = 已观察到的事件时间最大值 − 乱序容差。它表示“早于 WM 的事件已经全部到达,可以放心结算”。
  1. $\max(t_e)$已见事件时间最大值:所有已到达数据中最大的原始时间戳。
    max⁡(te)=最大事件时间\max(t_e) = \text{最大事件时间}
  2. $\varepsilon$乱序容差:预估最大乱序幅度,即“还可能有迟到数据的时间范围”。
    ε=最大允许乱序时间\varepsilon = \text{最大允许乱序时间}
  3. 相减得水位线把指针往回拨 ε,给迟到的数据留出一条缓冲带。
    WM=max⁡(te)−εWM = \max(t_e) - \varepsilon

迟到数据处理:迟到的客人怎么办

总有人会迟到。14:05 才跑到前台,手里却握着 13:50 的等位号。火锅店的做法是:如果厨房还在做最后一桌(允许延迟期间),这单还能插进去重算;如果锅已经刷了,就把这单转到外卖区(侧输出)单独处理,绝不让它堵住整个出菜通道。

在 Flink 中,一条数据的事件时间 t_e 如果小于当前水位线,就算迟到。Flink 可以给窗口设置“允许延迟”(allowedLateness)δ:迟到数据只要满足 $t_e \geq WM - \delta$,就还能进入原窗口,并用新数据重跑一次聚合,更新已发出的结果——这保证了窗口结果会被“修正”到尽量准确。而如果 $t_e < WM - \delta$,说明它迟得太离谱,窗口状态可能已经清理,Flink 会把它发送到侧输出(side output)——一条独立于主流的下游通道。

侧输出的好处是“不污染主流程、也不丢失数据”:你可以在主输出里继续查正常指标,同时用单独的 OutputTag 接住被放弃的迟到数据,日后做对账、补数、离线修正。允许延迟和侧输出一起构成双保险:前者让“救得回的”数据回到窗口,后者让“救不回的”数据有家可归。

迟到并不可怕,可怕的是没有预案。允许延迟像厨房里的“最后一锅宽容”,侧输出像窗台边的“外卖架”。二者配合,川流阁既能保证高峰期的翻台速度,又能让每一张迟到的订单都不被遗忘。

te<WM−δ⇒侧输出\displaystyle t_e < WM - \delta \Rightarrow \text{侧输出}
当迟到数据的事件时间早于“水位线 − 允许延迟”时,窗口已无法收留它,数据转向侧输出。
  1. $t_e$迟到事件时间:数据自带的原始发生时间,与到达时间无关。
    te=事件原始时间戳t_e = \text{事件原始时间戳}
  2. $WM$当前水位线:系统对已到达数据的置信边界。
    WM=当前水位线WM = \text{当前水位线}
  3. $\delta$允许延迟:窗口额外宽限的时间,迟到的数据还能插队重算。
    δ=allowedLateness 值\delta = \text{allowedLateness 值}
  4. $t_e < WM - \delta$超出宽限:迟到太深,状态已清理,转投侧输出。
    te<WM−δ⇒side outputt_e < WM - \delta \Rightarrow \text{side output}

AI 应用场景

实时交易风控大屏

阶段:实时特征聚合与监控告警

解决的问题:传统按天批处理让风控响应滞后,恶意交易可能在数小时后才被发现。Flink 用滑动窗口每 5 分钟统计一次“最近 1 小时交易总额”,配合水位线容忍支付网关回调的乱序延迟,把异常特征实时喂给风控模型。

具体案例:某支付平台统计每张银行卡 1 小时累计交易笔数和金额,当滑动窗口聚合值超过阈值时立即触发告警并冻结账户;迟到的交易回执通过侧输出进入对账系统,不影响主链路。

用户点击流会话切分

阶段:特征工程(行为序列建模)

解决的问题:用户点击日志是无界流,无法直接判断“一次访问”的起止。用会话窗口按 30 分钟静默切分,动态聚类出一次完整的用户行为链,再从中提取停留时长、跳转深度等特征。

具体案例:推荐系统将流式点击日志接入 Flink,会话窗口聚合出“本次会话浏览的品类序列”,作为 CTR 模型的实时序列特征,显著提升推荐点击率。

IoT 传感器异常检测

阶段:实时异常检测与告警

解决的问题:工厂传感器网络抖动严重,数据乱序到达。用“事件时间 + 水位线”替代处理时间,滚动窗口准确统计“过去 5 分钟平均温度”,允许延迟容忍网络抖动,超阈值立即告警。

具体案例:某智能制造产线为每台设备接入 Flink 作业,滚动窗口统计温湿度均值,水位线设 10 秒容差;异常数据进入侧输出,由离线 AI 模型二次研判是否生成检修工单。

Python 演示

python70 行
"""Flink 窗口机制模拟:滚动窗口 + 水位线 + 迟到数据侧输出"""from collections import defaultdict  class Event:    def __init__(self, ts, value, event_id):        self.ts = ts              # 事件时间戳        self.value = value        # 数值        self.event_id = event_id  # 编号  class TumblingWindowDemo:    """滚动窗口聚合演示"""     def __init__(self, size=6, max_lateness=2):        self.size = size        self.max_lateness = max_lateness        self.max_ts = -1        self.watermark = -1        self.per_window = defaultdict(list)  # 事件按窗口分组        self.emitted = {}                    # 已发出窗口的聚合结果        self.side_output = []                # 过于迟到 -> 侧输出     def assign_window(self, ts):        """滚动窗口起点 = floor(ts / size) * size"""        return (ts // self.size) * self.size     def _aggregate(self, w_start):        values = [e.value for e in self.per_window[w_start]]        self.emitted[w_start] = {            'count': len(values),            'sum': sum(values),        }     def process(self, events):        """吸收一批事件,推进水位线,发出已闭合窗口"""        for ev in events:            self.max_ts = max(self.max_ts, ev.ts)        self.watermark = self.max_ts - self.max_lateness         for ev in events:            w_start = self.assign_window(ev.ts)            if ev.ts < self.watermark - self.max_lateness:                self.side_output.append(ev)   # 太迟了 -> 侧输出            else:                self.per_window[w_start].append(ev)                if w_start in self.emitted:                    self._aggregate(w_start)  # 迟到的数据更新结果         for w_start in list(self.per_window):            if w_start + self.size <= self.watermark and w_start not in self.emitted:                self._aggregate(w_start)     def run(self):        batches = [            [Event(2, 5, 1), Event(5, 3, 2), Event(9, 8, 3)],            [Event(12, 2, 4), Event(11, 4, 5)],            [Event(15, 7, 6)],            [Event(7, 6, 7)],   # 迟到太久的数据        ]        for batch in batches:            self.process(batch)            print('水位线=%d 已发出窗口=%s' % (                self.watermark, sorted(self.emitted.keys())))        print('所有窗口结果: %s' % dict(self.emitted))        print('侧输出数据编号: %s' % [e.event_id for e in self.side_output])  if __name__ == '__main__':    TumblingWindowDemo(size=6, max_lateness=2).run()
目的:用纯 Python 模拟 Flink 滚动窗口的核心流程——窗口分配、水位线推进、已闭合窗口聚合、迟到数据侧输出,帮助理解窗口机制的全链路。
  1. w_start = self.assign_window(ev.ts)用 floor(ts / size) * size 算出每条数据属于哪个滚动窗口。
  2. self.watermark = self.max_ts - self.max_lateness水位线等于已见最大事件时间减去乱序容差,留给迟到数据缓冲带。
  3. if ev.ts < self.watermark - self.max_lateness:迟到数据超出允许延迟范围,转投侧输出,与主流程隔离。
  4. self.per_window[w_start].append(ev)正常事件进入窗口缓冲,等待窗口闭合后参与聚合。
  5. if w_start in self.emitted:窗口已发出但还有迟到数据进来,重新聚合以修正之前的结果。
  6. if w_start + self.size <= self.watermark and w_start not in self.emitted:水位线越过窗口末尾且窗口尚未发出,才触发聚合输出。

常见误区

误区我觉得窗口就是缓存区,攒够一万条数据就开始统计一次,跟时间一点关系都没有。

正解窗口本质是按时间或会话边界对数据流切分。Flink 确实提供按条数触发的 CountWindow,但那只是触发器的一种玩法;滚动、滑动、会话窗口的核心维度都是时间。把“窗口 = 计数缓存”会忽略时间语义,导致无法处理乱序和延迟问题。

为何常见:批处理系统按批次大小处理数据,初学者很容易把窗口和“攒批”混为一谈。

误区最新一条数据是 14:00 到的,那水位线就是 14:00,这有啥好算的?

正解水位线 = 已见最大事件时间 − 乱序容差 ε。如果直接等于最新时间,任何比它早到达的乱序数据都会变成“迟到数据”,窗口结果会被频繁重算甚至出错。预留 ε 的缓冲,是保证乱序场景下窗口结果稳定的关键。

为何常见:直觉上“处理进度 = 最新数据时间”很自然,但忽略了流处理中乱序到达是常态而非异常。

误区既然迟到数据也有用,那把 allowedLateness 设成一天,系统就永远不漏数据了。

正解允许延迟越大,窗口状态保留越久、内存压力越大、结果输出越晚。真实业务里要依据乱序观测值取折中,比如 δ = 2~5 分钟,并把超过 δ 的数据放心交给侧输出兜底。

为何常见:只想着“不漏数据”,没意识到流处理资源是有限的,状态无限保留必然压垮系统。

误区反正都是时间,用机器本地时间代替数据产生时间,不是更简单吗?

正解处理时间只反映数据到达机器的顺序,无法还原真实发生顺序。支付回调可能延迟 10 秒到达,用处理时间会把 14:00 的交易算进 14:01 的窗口。事件时间 + 水位线才是处理乱序数据的正确姿势。

为何常见:本地联调时数据有序,一上生产就乱了,根源就是没理解两种时间语义的本质差异。

误区窗口结果一输出,窗口状态就清空了,之后再来数据也无所谓。

正解如果配置了 allowedLateness,窗口触发后状态仍会保留,直到水位线超过“窗口末尾 + 允许延迟”才彻底清理。期间到达的迟到数据仍会更新窗口结果,所以一个窗口可能被触发两次以上——这在设计下游去重时要特别留意。

为何常见:把“触发”当成“终结”,不熟悉 Flink 窗口的生命周期管理。

面试题

Q1. Flink 窗口机制的核心作用是什么?

单选题 · easy

  • A.把无界数据流切成有界片段做聚合← 正确答案
  • B.把流式数据压缩后存储到磁盘
  • C.对流式数据进行加密并安全传输
  • D.把计算结果复制多份实现高可用

答案:A。窗口机制解决的核心矛盾是“数据无限、计算必须有界”。它把无界数据流按时间或会话切成有界片段,每个片段内才能做 sum、count 等聚合。压缩、加密、复制都不是窗口的本职。

Q2. 滚动窗口与滑动窗口的本质区别是什么?

单选题 · easy

  • A.滚动窗口固定长度且不重叠,滑动窗口可重叠← 正确答案
  • B.滚动窗口按事件条数划分,滑动窗口按时间划分
  • C.滚动窗口计算结果有界,滑动窗口计算结果无界
  • D.滚动窗口不需要水位线驱动,滑动窗口需要依赖

答案:A。滚动窗口首尾相接、互不重叠;滑动窗口由“长度 + 步长”两个参数控制,相邻窗口重叠,一个事件会落入多个窗口。两者都是按时间划分,都依赖水位线。

Q3. 事件时间 t=15,滚动窗口 size=10,该事件属于哪个窗口?

单选题 · easy

  • A.[0,10)
  • B.[10,20)← 正确答案
  • C.[15,25)
  • D.[5,15)

答案:B。滚动窗口起点 = floor(15/10)×10 = 10,终点 = 起点 + size = 20,因此事件属于 [10,20)。窗口是左闭右开区间,15 恰好落在其中。

Q4. 滑动窗口长度 10 分钟、步长 2 分钟,一个事件最多属于几个窗口?

单选题 · mid

  • A.2 个
  • B.5 个← 正确答案
  • C.10 个
  • D.20 个

答案:B。一个事件归属的窗口数 = size / slide = 10 / 2 = 5。每次滑动 2 分钟启动一个新窗口,事件会在 5 个前后相邻的窗口中重复参与聚合。

Q5. 会话窗口在什么条件下判定结束?

单选题 · mid

  • A.窗口长度达到预设的固定时长
  • B.超过会话间隔没有新事件到达← 正确答案
  • C.窗口内数据条数达到阈值上限
  • D.水位线停止推进且系统空闲

答案:B。会话窗口没有固定长度,它靠“静默间隔”来收尾:相邻两条数据的时间差超过会话间隔 gap 时,当前会话关闭。数据条数和水位线停滞都不是判定条件。

Q6. 水位线 WM 的通俗含义是什么?

单选题 · mid

  • A.当前系统每秒处理的平均事件条数
  • B.事件时间早于 WM 的数据认为已全部到达← 正确答案
  • C.系统最近一次输出聚合结果的时间
  • D.数据源产生事件的最大时间戳

答案:B。水位线是系统对“数据完备性”的承诺:当 WM 推进到某个时刻,再早于 WM 的乱序数据都视为不会再来了,可以放心关闭窗口。它既不是吞吐量,也不是输出时间。

Q7. 允许延迟 δ=5,事件时间 T=3,当前水位线 WM=9,该事件如何处理?

单选题 · hard

  • A.正常进入窗口参与聚合
  • B.作为迟到数据被丢弃
  • C.进入侧输出或直接被丢弃← 正确答案
  • D.更新水位线后重新触发窗口

答案:C。T=3 小于 WM−δ=4,说明它迟到得过于离谱,窗口状态可能已清理,系统将其送入侧输出或直接丢弃。若 T 落在 [4,9) 区间,仍可通过 allowedLateness 回到窗口重算。

Q8. 以下哪些是 Flink 内置支持的窗口类型?(多选)

多选题 · mid

  • A.滚动窗口← 正确答案
  • B.滑动窗口← 正确答案
  • C.会话窗口← 正确答案
  • D.环形窗口

答案:ABC。Flink 原生提供滚动窗口(Tumbling)、滑动窗口(Sliding)、会话窗口(Session)三种,它们对应不同的 WindowAssigner。环形窗口不是 Flink 的概念,全局窗口也可视为一种特殊窗口,但不在本题列出的内置类型中。

Q9. 关于水位线和触发器的说法正确的有(多选)

多选题 · hard

  • A.水位线单调递增,不会倒退← 正确答案
  • B.事件时间触发器在水位线超过窗口末尾时触发← 正确答案
  • C.水位线会随数据源并行度自动分叉合并← 正确答案
  • D.触发器只能定时触发,不能按事件数触发

答案:ABC。水位线单调递增,绝不回退;默认 EventTimeTrigger 检查水位线是否越过窗口末尾;并行环境下各分区水位线会取最小对齐(分叉合并)。触发器既可按时间也可按计数触发(如 CountTrigger),故 D 错。

Q10. 关于迟到数据处理,正确的说法有(多选)

多选题 · mid

  • A.允许延迟内的迟到数据仍可进入窗口更新结果← 正确答案
  • B.超过允许延迟的数据可以通过侧输出单独收集← 正确答案
  • C.设置允许延迟会无限增加窗口的状态存储
  • D.侧输出是一条独立于主流的附加数据流← 正确答案

答案:ABD。allowedLateness 内的迟到数据会回到窗口重算并修正结果;超出范围的数据走侧输出单独收集,不阻塞主流程。延迟时间到期后窗口状态会被清理回收,并非无限增加,故 C 错。