【大数据】详解 Flink 中的 WaterMark

详解 Flink 中的 WaterMark

  • 1.基础概念
    • 1.1 流处理
    • 1.2 乱序
    • 1.3 窗口及其生命周期
    • 1.4 Keyed vs Non-Keyed
    • 1.5 Flink 中的时间
  • 2.Watermark
    • 2.1 案例一
    • 2.2 案例二
    • 2.3 如何设置最大乱序时间
    • 2.4 延迟数据重定向
  • 3.在 DDL 中的定义
    • 3.1 事件时间
    • 3.2 处理时间

1.基础概念

1.1 流处理

流处理,最本质的是在处理数据的时候,接受一条处理一条数据。

批处理,则是累积数据到一定程度在处理。这是他们本质的区别。

在设计上 Flink 认为数据是流式的,批处理只是流处理的特例。同时对数据分为有界数据和无界数据。

  • 有界数据对应批处理,API 对应 DateSet
  • 无界数据对应流处理,API 对应 DataStream

1.2 乱序

什么是乱序呢?

可以理解为数据到达的顺序和其实际产生时间的排序不一致。导致这的原因有很多,比如延迟,消息积压,重试等等。

我们知道,流处理从事件产生,到流经 source,再到 operator,中间是有一个过程和时间的。虽然大部分情况下,流到 operator 的数据都是按照事件产生的时间顺序来的,但是也不排除由于网络、背压等原因,导致乱序的产生(out-of-order 或者说 late element)。

✅ 某数据源中的某些数据由于某种原因(如:网络原因,外部存储自身原因)会有 5 秒的延时,也就是在实际时间的第 1 秒产生的数据有可能在第 5 秒中产生的数据之后到来(比如到 Window 处理节点)。例如,有 1 ~ 10 个事件,乱序到达的序列是:2, 3, 4, 5, 1, 6, 3, 8, 9, 10, 7。

1.3 窗口及其生命周期

对于 Flink,如果来一条消息计算一条,这样是可以的,但是这样计算是非常频繁而且消耗资源,如果想做一些统计这是不可能的。所以对于 Spark 和 Flink 都产生了窗口计算。

比如,因为我们想看到过去一分钟或过去半小时的访问数据,这时候我们就需要窗口。

  • Window:Window 是处理无界流的关键,Window 将流拆分为一个个有限大小的 buckets,可以在每一个 buckets 中进行计算。
  • 当 Window 是时间窗口的时候,每个 Window 都会有一个开始时间(start_time)和结束时间(end_time)(左闭右开),这个时间是系统时间。

简而言之,只要属于此窗口的第一个元素到达,就会创建一个窗口,当时间(事件或处理时间)超过其结束时间戳加上用户指定的允许延迟时,窗口将被完全删除。

窗口有如下组件:

  • Window Assigner:用来决定某个元素被分配到哪个或哪些窗口中去。
  • Trigger:触发器。决定了一个窗口何时能够被计算或清除。触发策略可能类似于 “当窗口中的元素数量大于 4” 时,或 “当水位线通过窗口结束时”。
  • Evictor:驱逐器。Evictor 提供了在使用 WindowFunction 之前或者之后从窗口中删除元素的能力。

窗口还拥有函数,比如 ProcessWindowFunctionReduceFunctionAggregateFunctionFoldFunction。该函数将包含要应用于窗口内容的计算,而触发器指定窗口被认为准备好应用该函数的条件。

1.4 Keyed vs Non-Keyed

在定义窗口之前,要指定的第一件事是流是否需要 Keyed,使用 keyBy(...) 将无界流分成逻辑的 keyed stream。如果未调用 keyBy(...),则表示流不是 keyed stream

  • 对于 Keyed 流,可以将传入事件的任何属性用作 key。拥有 keyed stream 将允许窗口计算由多个任务并行执行,因为每个逻辑 Keyed 流可以独立于其余任务进行处理。相同 Key 的所有元素将被发送到同一个任务。
  • 在 Non-Keyed 流的情况下,原始流将不会被分成多个逻辑流,并且所有窗口逻辑将由单个任务执行,即并行性为 1。

1.5 Flink 中的时间

Flink 在流处理程序支持不同的时间概念。分别为是 事件时间Event Time)、处理时间Processing Time)、提取时间Ingestion Time)。

从时间序列角度来说,发生的先后顺序是:事件时间提取时间处理时间

  • Event Time 是事件在现实世界中发生的时间,它通常由事件中的时间戳描述。
  • Ingestion Time 是数据进入 Apache Flink 流处理系统的时间,也就是 Flink 读取数据源时间。
  • Processing Time 是数据流入到具体某个算子 (消息被计算处理) 时候相应的系统时间。也就是 Flink 程序处理该事件时当前系统时间。

2.Watermark

Watermark 是 Apache Flink 为了处理 EventTime 窗口计算提出的一种机制,本质上也是一种时间戳。Watermark 是用于处理乱序事件或延迟数据的,这通常用 Watermark 机制结合 Window 来实现(Watermarks 用来触发 Window 窗口计算)。

2.1 案例一

public class BoundedOutOfOrdernessGenerator implements AssignerWithPeriodicWatermarks<MyEvent> {

    private final long maxOutOfOrderness = 3000; // 3.0 seconds

    private long currentMaxTimestamp;

    @Override
    public long extractTimestamp(MyEvent element, long previousElementTimestamp) {
        long timestamp = element.getCreationTime();
        currentMaxTimestamp = Math.max(timestamp, currentMaxTimestamp);
        return timestamp;
    }

    @Override
    public Watermark getCurrentWatermark() {
        // return the watermark as current highest timestamp minus the out-of-orderness bound
        // 生成 watermark
        return new Watermark(currentMaxTimestamp - maxOutOfOrderness);
    }
}

在这里插入图片描述
上图中是一个 10s 大小的窗口,1000020000 为一个窗口。当 EventTime 为 23000 的数据到来,生成的 WaterMark 的时间戳为 20000,大于等于 window_end_time,会触发窗口计算。

2.2 案例二

public class TimeLagWatermarkGenerator implements AssignerWithPeriodicWatermarks<MyEvent> {

    private final long maxTimeLag = 3000; // 3 seconds

    @Override
    public long extractTimestamp(MyEvent element, long previousElementTimestamp) {
        return element.getCreationTime();
    }

    @Override
    public Watermark getCurrentWatermark() {
        // return the watermark as current time minus the maximum time lag
        return new Watermark(System.currentTimeMillis() - maxTimeLag);
    }
}

在这里插入图片描述
只是简单的用当前系统时间减去最大延迟时间生成 Watermark ,当 WaterMark 为 20000 时,大于等于窗口的结束时间,会触发 1000020000 窗口计算。再当 EventTime 为 19500 的数据到来,它本应该是属于窗口 1000020000 窗口的,但这个窗口已经触发计算了,所以此数据会被丢弃。

2.3 如何设置最大乱序时间

虽说水位线表明着早于它的事件不应该再出现,接收到水位线以前的的消息是不可避免的,这就是所谓的 迟到事件。实际上迟到事件是乱序事件的特例,和一般乱序事件不同的是它们的乱序程度超出了水位线的预计,导致窗口在它们到达之前已经关闭。

迟到事件出现时窗口已经关闭并产出了计算结果,因此处理的方法有 3 种:

  • 重新激活已经关闭的窗口并重新计算以修正结果。将迟到事件收集起来另外处理。将迟到事件视为错误消息并丢弃。Flink 默认的处理方式是直接丢弃,其他两种方式分别使用 Side OutputAllowed Lateness
  • Side Output 机制 可以将迟到事件单独放入一个数据流分支,这会作为 Window 计算结果的副产品,以便用户获取并对其进行特殊处理。
  • Allowed Lateness 机制 允许用户设置一个允许的最大迟到时长。Flink 会在窗口关闭后一直保存窗口的状态直至超过允许迟到时长,这期间迟到的事件不会被丢弃,而是默认会触发窗口重新计算。因为保存窗口状态需要额外内存,并且如果窗口计算使用了 ProcessWindowFunction API 还可能使得每个迟到事件触发一次窗口的全量计算,代价比较大,所以允许迟到时长不宜设得太长,迟到事件也不宜过多,否则应该考虑降低水位线提高的速度或者调整算法。

这里总结机制为:

  • 窗口 Window 的作用是为了周期性的获取数据。
  • WaterMark 的作用是防止数据出现乱序(经常),事件时间内获取不到指定的全部数据,而做的一种保险方法。
  • allowLateNess 是将窗口关闭时间再延迟一段时间。
  • sideOutPut 是最后兜底操作,所有过期延迟数据,指定窗口已经彻底关闭了,就会把数据放到侧输出流。
public class TumblingEventWindowExample {

    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
        env.setParallelism(1);
//        env.getConfig().setAutoWatermarkInterval(100);
        DataStream<String> socketStream = env.socketTextStream("localhost", 9999);
        DataStream<Tuple2<String, Long>> resultStream = socketStream
                .assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor<String>(Time.seconds(3)) {
                    @Override
                    public long extractTimestamp(String element) {
                        long eventTime = Long.parseLong(element.split(" ")[0]);
                        System.out.println(eventTime);
                        return eventTime;
                    }
                })
                .map(new MapFunction<String, Tuple2<String, Long>>() {
                    @Override
                    public Tuple2<String, Long> map(String value) throws Exception {
                        return Tuple2.of(value.split(" ")[1], 1L);
                    }
                })
                .keyBy(0)
                .window(TumblingEventTimeWindows.of(Time.seconds(10)))
                .allowedLateness(Time.seconds(2)) // 允许延迟处理2秒
                .reduce(new ReduceFunction<Tuple2<String, Long>>() {
                    @Override
                    public Tuple2<String, Long> reduce(Tuple2<String, Long> value1, Tuple2<String, Long> value2) throws Exception {
                        return new Tuple2<>(value1.f0, value1.f1 + value2.f1);
                    }
                });
        resultStream.print();
        env.execute();
    }
}

在这里插入图片描述
watermark 为 21000 时,触发了 [10000, 20000) 窗口计算,由于设置了 allowedLateness(Time.seconds(2)),即允许两秒延迟处理,watermark < window_end_time + lateTime 公式得到满足,因此随后 10000 和 12000 进入窗口时,依然能触发窗口计算;随后 watermark 增加到 22000,watermark < window_end_time + lateTime 不再满足,因此 11000 再次进入窗口时,窗口不再进行计算。

2.4 延迟数据重定向

流的返回值必须是 SingleOutputStreamOperator,其是 DataStream 的子类。通过 getSideOutput 方法获取延迟数据。可以将延迟数据重定向到其他流或者进行输出。

public class TumblingEventWindowExample {

    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
        env.setParallelism(1);
        DataStream<String> socketStream = env.socketTextStream("localhost", 9999);
        //保存被丢弃的数据
        OutputTag<Tuple2<String, Long>> outputTag = new OutputTag<Tuple2<String, Long>>("late-data"){};
        //注意,由于getSideOutput方法是SingleOutputStreamOperator子类中的特有方法,所以这里的类型,不能使用它的父类dataStream。
        SingleOutputStreamOperator<Tuple2<String, Long>> resultStream = socketStream
                // Time.seconds(3)有序的情况修改为0
                .assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor<String>(Time.seconds(3)) {
                    @Override
                    public long extractTimestamp(String element) {
                        long eventTime = Long.parseLong(element.split(" ")[0]);
                        System.out.println(eventTime);
                        return eventTime;
                    }
                })
                .map(new MapFunction<String, Tuple2<String, Long>>() {
                    @Override
                    public Tuple2<String, Long> map(String value) throws Exception {
                        return Tuple2.of(value.split(" ")[1], 1L);
                    }
                })
                .keyBy(0)
                .window(TumblingEventTimeWindows.of(Time.seconds(10)))
                .sideOutputLateData(outputTag) // 收集延迟大于2s的数据
                .allowedLateness(Time.seconds(2)) //允许2s延迟
                .reduce(new ReduceFunction<Tuple2<String, Long>>() {
                    @Override
                    public Tuple2<String, Long> reduce(Tuple2<String, Long> value1, Tuple2<String, Long> value2) throws Exception {
                        return new Tuple2<>(value1.f0, value1.f1 + value2.f1);
                    }
                });
        resultStream.print();
        //把迟到的数据暂时打印到控制台,实际中可以保存到其他存储介质中
        DataStream<Tuple2<String, Long>> sideOutput = resultStream.getSideOutput(outputTag);
        sideOutput.print();
        env.execute();
    }
}

3.在 DDL 中的定义

3.1 事件时间

事件时间属性是通过 CREATE TABLE DDL 语句中的 WATERMARK 语句定义的。水印语句在现有事件时间字段上定义 水印生成表达式,将事件时间字段标记为事件时间属性。

Flink SQL 支持在 TIMESTAMPTIMESTAMP_LTZ 列上定义事件时间属性。如果源中的时间戳数据以 年-月-日-时-分-秒 表示,通常是不含时区信息的字符串值,例如 2020-04-15 20:13:40.564,建议将事件-时间属性定义为 TIMESTAMP 列。

CREATE TABLE user_actions (
  user_name STRING,
  data STRING,
  user_action_time TIMESTAMP(3),
  -- Declare the user_action_time column as an event-time attribute
  -- and use a 5-seconds-delayed watermark strategy.
  WATERMARK FOR user_action_time AS user_action_time - INTERVAL '5' SECOND
) WITH (
  ...
);

SELECT TUMBLE_START(user_action_time, INTERVAL '10' MINUTE), COUNT(DISTINCT user_name)
FROM user_actions
GROUP BY TUMBLE(user_action_time, INTERVAL '10' MINUTE);

如果数据源中的时间戳数据以纪元时间表示,通常是一个长值,例如 1618989564564,建议将事件时间属性定义为 TIMESTAMP_LTZ 列。

CREATE TABLE user_actions (
  user_name STRING,
  data STRING,
  ts BIGINT,
  time_ltz AS TO_TIMESTAMP_LTZ(ts, 3),
  -- Declare the time_ltz column as an event-time attribute
  -- and use a 5-seconds-delayed watermark strategy.
  WATERMARK FOR time_ltz AS time_ltz - INTERVAL '5' SECOND
) WITH (
  ...
);

SELECT TUMBLE_START(time_ltz, INTERVAL '10' MINUTE), COUNT(DISTINCT user_name)
FROM user_actions
GROUP BY TUMBLE(time_ltz, INTERVAL '10' MINUTE);

3.2 处理时间

处理时间能让表格程序根据本地机器的时间产生结果。这是最简单的时间概念,但会产生非确定性结果。处理时间不需要提取时间戳或生成水印。

CREATE TABLE DDL 语句中,使用系统 PROCTIME() 函数将处理时间属性定义为计算列。函数返回类型为 TIMESTAMP_LTZ

CREATE TABLE user_actions (
  user_name STRING,
  data STRING,
  -- Declare an additional field as a processing-time attribute.
  user_action_time AS PROCTIME()
) WITH (
  ...
);

SELECT TUMBLE_START(user_action_time, INTERVAL '10' MINUTE), COUNT(DISTINCT user_name)
FROM user_actions
GROUP BY TUMBLE(user_action_time, INTERVAL '10' MINUTE);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/353117.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ubuntu20.04 安装ROS2 记录

主要参考B站古月居的ROS2入门21讲 和 以下链接&#xff08;基本和视频上一致&#xff09; ubuntu20.04安装ROS2 详细教程_ubuntu20.04 ros2-CSDN博客 但是中间有些需要注意的地方&#xff0c; 1&#xff0c;添加源 步骤中提到 sudo curl -sSL https://raw.githubuserconten…

基于QC-LDPC编码的循环移位网络的FPGA实现

一、桶式移位寄存器(barrel shifter) 八位桶式移位寄存器的VHDL实现如下&#xff0c;由于每一层结构相似&#xff0c;于是采用生成语句for_generate实现&#xff0c;使用该代码实现的RTL级分析和理论的结构一致&#xff0c;仿真结果也符合预期。 entity barrel_shift isGENE…

【RT-DETR有效改进】反向残差块网络EMO | 一种轻量级的CNN架构(轻量化网络,参数量下降约700W)

前言 大家好&#xff0c;这里是RT-DETR有效涨点专栏。 本专栏的内容为根据ultralytics版本的RT-DETR进行改进&#xff0c;内容持续更新&#xff0c;每周更新文章数量3-10篇。 专栏以ResNet18、ResNet50为基础修改版本&#xff0c;同时修改内容也支持ResNet32、ResNet101和PP…

文件上传之大文件分块上传进度控制处理

在分块上传内容结束以后的事件监听&#xff0c;我们会实现 unlinkSync 删除临时文件操作&#xff0c;那么试想一下&#xff0c;在这个事件监听中&#xff0c;我们是否可以通过totalChunks以及currentChunk获取当前上传的进度情况呢&#xff1f; 后端 upload上传接口&#xff…

python222网站实战(SpringBoot+SpringSecurity+MybatisPlus+thymeleaf+layui)-友情链接管理实现

锋哥原创的SpringbootLayui python222网站实战&#xff1a; python222网站实战课程视频教程&#xff08;SpringBootPython爬虫实战&#xff09; ( 火爆连载更新中... )_哔哩哔哩_bilibilipython222网站实战课程视频教程&#xff08;SpringBootPython爬虫实战&#xff09; ( 火…

在 React 组件中使用 JSON 数据文件,怎么去读取请求数据呢?

要在 React 组件中使用 JSON 数据&#xff0c;有多种方法。 常用的有以下几种方法&#xff1a; 1、直接将 JSON 数据作为一个变量或常量引入组件中。 import jsonData from ./data.json;function MyComponent() {return (<div><h1>{jsonData.title}</h1>&…

openssl3.2 - 测试程序的学习 - 准备openssl测试专用工程的模板

文章目录 openssl3.2 - 测试程序的学习 - 准备openssl测试专用工程的模板概述笔记工程中需要的openssl的库实现END openssl3.2 - 测试程序的学习 - 准备openssl测试专用工程的模板 概述 openssl3.2 - 测试程序的学习 整了几个test.c, 每开一个新的测试工程, 总要改这改那的. …

Maven入门及其使用

目录 一、Maven入门 1.1 初识Maven 1.2 Maven的作用 1.2.1 依赖管理 1.2.2 统一项目结构 1.2.3 项目构建 1.3 Maven坐标 1.4 Maven仓库 1.4.1 Maven仓库概述 二、Maven的下载与安装 2.1 安装步骤 2.1.1 解压安装&#xff08;建议解压到没有中文、特殊字符的路径下。&#xff09…

【JavaScript】ECMA6Script es6

文章目录 一、 es6的介绍二、 es6的变量和模板字符串2.1 let 与 var2.2 const 与 var2.3 模板字符串 三、 es6的解构表达式四、 es6的箭头函数4.1 声明和特点4.2 实践和应用场景4.3 rest和spread 五、es6的对象创建和拷贝5.1 对象创建的语法糖5.2 对象的深拷贝和浅拷贝 六、es6…

STM32 PWM驱动设计

单片机学习&#xff01; 目录 文章目录 前言 一、PWM驱动配置步骤 二、代码示例及注意事项 2.1 RCC开启时钟 2.2 配置时基单元 2.3 配置输出比较单元 2.4 配置GPIO 2.5 运行控制 三、PWM周期和占空比计算 总结 前言 PWM本质是利用面积等效原理来改变波形的有效值。 一、PWM驱动…

【electron】打包问题处理

目录 项目无法在win7执行场景尝试处理 项目无法在win7执行 场景 使用electron25.0.1、electron-builder24.2.1&#xff0c;打出来的项目在win7系统上跑不起来&#xff0c;报错无法定位程序输入点DiscardVirtualMemoty于动态链接库KERNEL32.dll上。 尝试处理 通过百度发现ele…

编曲学习:和声音程 调式体系 唱名法 调式调性

34届和声音程 调式体系 唱名法 调式调性https://app8epdhy0u9502.pc.xiaoe-tech.com/live_pc/l_65af994be4b064a8cb1c3a5f?course_idcourse_2XLKtQnQx9GrQHac7OPmHD9tqbv 34届独立音乐人编曲训练营https://app8epdhy0u9502.pc.xiaoe-tech.com/p/t_pc/course_pc_detail/camp_p…

Tomcat 简介安装

目录 1、概念介绍 Tomcat 组件 HTTP 请求过程 Tomcat 目录结构 Tomcat 命令 配置文件说明 2、安装环境 安装JDK 安装Tomcat 创建管理用户 3、搭建虚拟主机 1、概念介绍 端口&#xff1a;8080 Tomcat 服务器是一个免费的开放源代码的 Web 应用服务器, 按照 Sun 提供…

DevOps系列文章之 GitLabCI汇总

GitlabCI环境搭建 前提 先安装 docker Docker容器化安装 docker pull gitlab/gitlab-ee:12.4.0-ee.0 创建挂载目录 mkdir -p /srv/gitlab mkdir -p /srv/gitlab/config # 映射到 Glitlab 容器中的配置目录 mkdir -p /srv/gitlab/logs # 映射到 Glitlab 容器中的日志目录 m…

ArXiv| Graph-Toolformer: 基于ChatGPT增强提示以赋予大语言模型图数据推理能力

ArXiv| Graph-Toolformer: 基于ChatGPT增强提示以赋予大语言模型图数据推理能力. 来自加利福利亚大学戴维斯分校计算机科学系的IFM实验室发表在arXiv上的文章:“Graph-ToolFormer: To Empower LLMs with Graph Reasoning Ability via Prompt Augmented by ChatGPT”。 文章的…

P1045 [NOIP2003 普及组] 麦森数题解

题目 形如的素数称为麦森数&#xff0c;这时P一定也是个素数。但反过来不一定&#xff0c;即如果P是个素数&#xff0c;不一定也是素数。到1998年底&#xff0c;人们已找到了37个麦森数。最大的一个是P3021377&#xff0c;它有909526位。麦森数有许多重要应用&#xff0c;它与…

pcl之滤波器(三)

pcl滤波器 pcl一共是有十二个主要模块&#xff0c;详细了解可以查看官网。https://pcl.readthedocs.io/projects/tutorials/en/latest/#basic-usage 今天学习一下pcl的滤波器模块。 滤波器模块&#xff0c;官网一共是提供了6个例程&#xff0c;今天看第五个、第六个。 从一…

P2246 SAC#1 - Hello World(升级版)

网址如下&#xff1a; P2246 SAC#1 - Hello World&#xff08;升级版&#xff09; - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 刚开始是用递归做的&#xff0c;虽然用了哈希表优化&#xff0c;但是超时&#xff0c;只得了50 后面想到了一个新的算法&#xff0c;时间复杂度…

Java笔记 --- 三、方法引用

三、方法引用 概述 分类 引用静态方法 引用成员方法 本类中注意&#xff0c;静态方法中没有this&#xff0c;需要创建本类的对象 引用构造方法 其他的调用方式 使用类名引用成员方法 引用数组的构造方法

【遥感专题系列】影像信息提取之——基于专家知识的决策树分类

可以将多源数据用于影像分类当中&#xff0c;这就是专家知识的决策树分类器&#xff0c;本专题以ENVI中Decision Tree为例来叙述这一分类器。 本专题包括以下内容&#xff1a; 专家知识分类器概述知识&#xff08;规则&#xff09;定义ENVI中Decision Tree的使用 概述 基于知…
最新文章