流式计算中的Window计算 | 青训营笔记

378 阅读9分钟

流式计算中的Window计算 | 青训营笔记

这是我参与「第四届青训营 」笔记创作活动的的第3天。

一、本堂课重点内容

  1. 流式计算与批式计算的区别;实时和离线数仓的区别;流式计算中window的定义及挑战
  2. 实时计算中watermark的概念,以及如何产生、传递
  3. 介绍三种最基本的window类型及实现原理,结合业务场景提供窗口机制中最核心的优化功能原理

二、详细知识点介绍

1. 流式计算概述

简述流式计算的基本概念,与批式计算相比的难点和挑战

1.1 流式计算和批式计算的对比

  • 实时性越高,数据的价值越高
项目批式计算流式计算
数据存储HDFS、HiveKafka、Pulsar
数据时效性天级别分钟级别
准确性精准精准和时效性取舍
典型计算引擎Hive、Spark、FlinkFlink
计算模型Exactly-OnceAt-Least-Once/Exactly-Once
资源模型定时调度长期持有
主要场景离线天级别数据报表实时数仓、实时营销、实时风控

1.2 批处理

  • T+1离线计算模型:数据计算是天级别,每天只能看到前一天的计算结果

    • 通常使用Hive/Spark,数据和结果是确定的
  • 小时级批计算

    • 批计算每次需要申请调度资源
    • 计算需要时间

1.3 处理时间窗口

  • 实时计算:处理时间窗口
    • 数据实时流动实时计算
    • 窗口结束直接发送结果不需要周期调度任务

1.4 处理时间vs事件时间

  • 处理时间:数据在流式计算系统中真正处理时所在机器的当前时间
  • 事件时间:数据产生的时间,比如客户端、传感器、后段代码等上报数据的时间

1.5 事件时间窗口

  • 实时计算:事件时间窗口
    • 数据实时进入到真实时间发生的窗口中进行计算
    • 可以有效处理数据延迟和乱序
  • 什么时候窗口算结束?
    • 引用watermark来表示当前的真实时间
    • 数据存在乱序时,可以用来在乱序容忍和实时性间做一个平衡
    • 当收到watermark后有比watermark小的数据时认为是延时数据 舍弃

2. Watermark概述

watermark的含义、生成方法、传递机制以及一些典型场景的问题和优化

2.1 Watermark定义

  • 当前系统认为的事件时间所在的真实时间
  • 当前真实的事件时间

2.2 Watermark产生

  • 一般从数据的事件时间来产生
  • 产生策略灵活多样:最常见包括使用当前事件时间的时间减去一个固定的Delay,来表示可以容忍多长时间的乱序

SQL:

CREATE TABLE...
order_time TIMESTAMP(3)
WATERMARK FOR order_time AS order_time - IMTERVAL '5' SECOND

DataStream:

WatermarkStrategy
    .<Tuple2<Long,String>>forBoundedOutOfOrderness(Duration.ofSeconds(20))
    .withTimestampAssigner((event, timestamp)->event.f0);

2.3 Watermark的传递、

上下游task之间有数据传递关系时,上游会将watermark传递给下游;下游收到多个上游发来的watermark后,默认取最小值作为自身的watermark,同时将自己的watermark传递给下游;最后所有的算子都会有自己的watermark。

2.4 Watermark在生产实践中的问题

2.4.1 怎么观察一个任务中Watermark多少,是否正常

通过Flink UI观察算子的watermark和subtask的watermark。

2.4.2 per-partition/per-subtask生成watermark的优缺点

  • per-subtask watermark生成:早期版本机制。典型的问题是如果一个source subtask消费多个partition,那么多个partition之间的数据读取(读取速度不一、系统故障、传输延迟等)可能会加剧乱序程度;
  • per-partition watermark:引入基于每个partition单独的watermark,有效避免乱序度增加;

2.4.3 如果有部分partition/subtask会断流如何处理

上游subtask的watermark不更新,则下游所有watermark不更新;

  • 解决方法:Idle source
    • 当某个subtask断流超过配置的idle超时时间时,将当前subtask置为idle,并下发一个idle的状态给下游。
    • 下游在计算自身watermark的时候,忽略掉当前是idle的subtask。

2.4.4 算子对于时间晚于watermark的数据的处理

  • 迟到数据处理:晚于watermark的数据到来时认为是迟到数据,算子自身来决定如何处理迟到数据
    • window聚合:默认丢弃迟到数据
    • 双流join:outer-join则不能join到任何数据
    • CEP:默认丢弃

3. Window概述

window基本功能和高级优化

3.1 window基本功能

  • window分类

    • tumble window 滚动窗口
    • sliding window 滑动窗口
    • session window 会话窗口
    • 其他window:全局window、count window、累计窗口等
  • window使用 SQL:

SUM(amount) FROM ORDERS
GROUP BY
TUMBLE(order_time, INTERVAL1DAY

DataStream:

DataStream<Tuple2<String, Integer>> dataStream = env
    .socketTextStream("localhost",9999)
    .flatMap(new Splitter())
    .keyBy(value ->value.f0)
    .window(TumblingProcessingTimeWindows.of(Time.seconds(5)))
    .sum(1)

3.1.1 tumble window 滚动窗口

Screen Shot 2022-07-25 at 10.48.32 PM.png

  • 窗口划分:每个key单独划分,每条数据只属于一个窗口
  • 窗口触发:window结束时间到达时一次性触发

3.1.2 HOP Sliding window 滑动窗口

Screen Shot 2022-07-25 at 10.49.16 PM.png

  • 窗口划分:每个key单独划分,每条数据可能属于多个窗口
  • 窗口触发:window结束时间到达时一次性触发

3.1.3 session window 会话窗口

  • 窗口划分:每个key单独划分,每条数据会单独划分一个窗口,如果window之间有交集,则对窗口进行merge;
    • session窗口划分是动态的,可能会与之前的窗口合并
  • 窗口触发:window结束时间到达时一次性触发

3.2 迟到数据处理

  • 迟到:一条数据到来后会用window assigner给它划分一个window,一般时间窗口是一个时间区间,如果划分出来的window end比当前的watermark小,说明这个窗口已经触发计算了,这条数据就是迟到数据。
  • 什么情况下会产生迟到数据:只有事件时间下才会有迟到的数据。
  • 迟到数据的默认处理:丢弃

3.2.1 allow lateness

需要设置一个允许迟到的时间。设置之后窗口正常计算结束后不会马上清理状态,而是会多保留allow lateness这么长的时间。在这段时间内如果还有数据到来则继续之前的状态进行计算。

  • 适用:DataStream、SQL

3.2.2 SideOutput 侧输出流

这种方式对迟到数据打一个tag,然后在dataStream上根据这个tag获取迟到数据流,然后业务层自行选择进行处理。

  • 适用于 DataStream

3.3 增量计算和全量计算

  • 增量计算:每条数据到来直接进行计算
    • window只存储计算结果,不保留每条数据
    • 典型的reduce、aggregate等函数都是增量计算
    • SQL中聚合只有增量计算
  • 全量计算:每条数据到来后会存储到window的state中。
    • window存储所有数据,触发计算时将所有数据拿出一起计算
    • 典型的process函数就是全量计算

3.4 EMIT触发

3.4.1 什么是EMIT

  • 通常window都是在结束时才输出结果。如果窗口比较大则计算结果输出的延迟就比较高,失去了实时计算的含义。
  • EMIT输出指的是在window没有结束时提前把window计算的部分结果输出出来。

3.4.2 怎么实现EMIT

  • DataStream中通过自定义Trigger来实现
    • Trigger的结果可以是
      • CONTINUE
      • FIRE(触发计算但不清理)
      • PURGE
      • FIRE_AND_PURGE
  • SQL通过配置:
    • table.exec.emit.early-fire.enabled=true
    • table.exec.emit.early-fire.delay=(time)

3.5 window offset

windowStart = timeStamp - (timeStamp - offset + windowSize)% windowSize

可以在计算窗口时让窗口有一个偏移。

  • DataStream支持offset,SQL不支持。

3.6 window高级优化

3.6.1 mini-batch优化

  • RETRACT机制下中间结果偏多输出量大,内存存储到外部状态需要序列化和反序列化消耗CPU
  • 多条数据进行一次状态读取、序列化和反序列化、以及写入过程 (攒一批数据然后输出)
  • 上下游算子如果都缓存一批进行输出,数据延迟会大大增加
  • 利用类watermark传递机制,上游添加mini-batch assigner算子,发送开始mini-batch的信号;下游收到信号后开始对缓存数据进行计算,同时向下游继续发送;

3.6.2 local-global 倾斜优化

  • 在shuffle之前,先进行local预聚合,再shuffle到下游全局算子,解决数据倾斜的热点问题

3.6.3 distinct状态复用

COUNT (DISTINCT id) FILTER (WHERE a IN ...)
COUNT (DISTINCT id) FILTER (WHERE b IN ...)
COUNT (DISTINCT id) FILTER (WHERE c IN ...)
COUNT (DISTINCT id) FILTER (WHERE d IN ...)
...
  • DISTINCT通常优化为Group by等形式
  • 利用FILTER前置过滤DISTINCT
  • 复用DISTINCT,把key对应为bit value来确定状态(是否有对应key),从而通过filter复用;
  • 降低DISTINCT连续数据的存储量。

3.6.4 滑动窗口pane复用

  • 滑动窗口每条数据参与多个窗口,数据开销比较大
  • 把滑动窗口划分成更小的pane,从而变成类滚动窗口的形式;每个滑动窗口就是多个pane的聚合,从而数据在每个pane中可以节省访问,降低计算成本。
  • 输出时需要临时做一个默指。

三、生产实例分析

1. 计算实时抖音DAU曲线

思路:做一个EMIT的滚动窗口,count DISTINCT uid

单并发:必须聚合到一个节点

SELECT
    COUNT(DISTINCT uid) as dau
    TUMBLE_START(event_time, INTERVAL '1' DAY) as wstart,
    LOCALTIMESTAMP AS current_ts
FROM user_activity
GROUP BY
TUMBLE(event_time, INTERVAL '1' DAY)

两阶段聚合:把数据打散,完成第一轮聚合后对分桶结果求和即可

SELECT
    SUM(partial_cnt) as dau
    TUMBLE_START(event_time, INTERVAL '1' DAY) as wstart,
    LOCALTIMESTAMP as current_ts
FROM(
    SELECT
        COUNT(DISTINCT uid) as partial_cnt,
        TUMBLE_ROWTIME(event_time, INTERVAL '1' DAY) as event_time
    FROM user_activity
    GROUP BY
    TUMBLE(event_time, INTERVAL '1' DAY),
    MOD(uid,10000) -- 根据uid分为10000个桶
)
GROUP BY TUMBLE(event_time, INTERVAL '1' DAY)

2. 计算大数据任务的资源使用

根据YARN上报的各个container的信息,在任务结束时尽快计算出一个任务占据的总资源,假设前后两个container间隔时间不足10min;

  • 典型通过会话窗口将数据划分到一个window中 然后将结果求和
SELECT application_id,
    SUM(cpu_usage) as CPU_TOTAL
    SUM(memory_usage) as MEMORY_TOTAL
FROM resource_usage
GROUP BY 
    application_id,
    SESSION(event_time, INTERVAL '10' MINUTE)

四、课后个人总结

  • 复杂知识点
    • Watermark机制便于计算迟到信息
    • 三种window的定义、使用场景和优化
  • 本人总结 本节课主要学习了流式计算中watermark的利用,watermark用于标示迟到数据;同时学习了滚动窗口、滑动窗口和会话窗口的定义、实际运用和优化方法。课程学习的内容相对抽象,需要结合实例进行更深入的学习。

五、引用参考

【大数据专场 学习资料二】第四届字节跳动青训营