大数据flink框架入门分享(起源与发展、实时与离线计算、场景、处理流程、相关概念、特性普及、入门Demo)

news2024/11/15 9:54:44

文章目录

    • 起源与发展
    • flink在github上的现状
    • 实时计算VS离线计算
      • 实时计算
      • 离线计算
    • 实时计算常用的场景
    • 框架流处理流程
    • flink电商场景下的业务图示例
    • flink中一些重要特性
    • 有界数据和无界数据
    • 时间语义、水位线
      • 事件时间
      • 处理时间
      • 水位线
    • flink窗口
      • 概念
        • 理想中的数据处理
        • 含有延迟数据的数据处理
        • Flink存储桶概念
      • 窗口类型
        • 滚动窗口
        • 滑动窗口
        • 会话窗口
        • 全局窗口
    • flink状态管理
      • 检查点(Checkpoint)
      • 检查点恢复数据过程
    • 下载安装
    • 入门Demo示例
      • pom配置
      • Demo代码
      • 打包到集群
    • 流运行时执行环境
    • 任务槽Slot
    • 扩展Demo
      • 时间窗口Demo
      • Table Api Demo
      • 对迟到数据处理Demo

起源与发展

​         Flink 起源于一个叫作 Stratosphere 的项目,它是由 3 所地处柏林的大学和欧洲其他一些大学在 2010~2014 年共同进行的研究项目,由柏林理工大学的教授沃克尔·马尔科(Volker Markl)领衔开发。2014 年 4 月,Stratosphere 的代码被复制并捐赠给了 Apache 软件基金会,Flink 就是在此基础上被重新设计出来的。

​        在德语中,“flink”一词表示“快速、灵巧”。项目的 logo 是一只彩色的松鼠,当然了,这不仅是因为 Apache 大数据项目对动物的喜好(是否联想到了 Hadoop、Hive?),更是因为松鼠这种小动物完美地体现了“快速、灵巧”的特点。关于 logo 的颜色,还一个有趣的缘由:柏林当地的松鼠非常漂亮,颜色是迷人的红棕色;而 Apache 软件基金会的 logo,刚好也是一根以红棕色为主的渐变色羽毛。于是,Flink 的松鼠 Logo 就设计成了红棕色,而且拥有一个漂亮的渐变色尾巴,尾巴的配色与 Apache 软件基金会的 logo 一致。这只松鼠色彩炫目,既呼应了 Apache 的风格,似乎也预示着 Flink 未来将要大放异彩。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qHDeUZqH-1678069300470)(/Users/dasouche/Library/Application Support/typora-user-images/image-20230222174725804.png)]

在这里插入图片描述

       从命名上,我们也可以看出 Flink 项目对于自身特点的定位,那就是对于大数据处理,要做到快速和灵活。

  • 2014 年 8 月,Flink 第一个版本 0.6 正式发布(至于 0.5 之前的版本,那就是在Stratosphere 名下的了)。与此同时 Fink 的几位核心开发者创办了 Data Artisans 公司,主要做 Fink 的商业应用,帮助企业部署大规模数据处理解决方案。
  • 2014 年 12 月,Flink 项目完成了孵化,一跃成为 Apache 软件基金会的顶级项目。
  • 2015 年 4 月,Flink 发布了里程碑式的重要版本 0.9.0,很多国内外大公司也正是从这时开始关注、并参与到 Flink 社区建设的。
  • 2019 年 1 月,长期对 Flink 投入研发的阿里巴巴,以 9000 万欧元的价格收购了 Data Artisans 公司;之后又将自己的内部版本 Blink 开源,继而与 8 月份发布的 Flink 1.9.0版本进行了合并。自此之后,Flink 被越来越多的人所熟知,成为当前最火的新一代大数据处理框架。
  • 2020年:Flink Forward AsiaFlink Forward Europe 两个 Flink 社区活动成功举办。
  • 2021年:Flink 与 ClickHouse 成为 Apache 流式处理项目的顶级项目。
  • 2022年:Flink 1.15 和 1.16 版本相继发布,引入了许多新特性和改进。

flink在github上的现状

目前已经到了1.16.1的release版本

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EXKXrYa1-1678069300471)(/Users/dasouche/Library/Application Support/typora-user-images/image-20230222174951121.png)]

实时计算VS离线计算

实时计算

  • 数据实时到达
  • 数据到达次序独立,不受应用系统所控制
  • 数据规模大且无法预知容量
  • 原始数据一经处理,除非特意保存,否则不能被再次取出处理,或者再次提取数据代价昂贵

离线计算

  • 数据量大且时间周期长(一天、一星期、一个月、半年、一年)
  • 在大量数据上进行复杂的批量计算操作
  • 数据在计算之前已经固定,不再会发生变化
  • 能够方便的查询批量计算的结果

实时计算常用的场景

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SwnQhcVH-1678069300471)(/Users/dasouche/Library/Application Support/typora-user-images/image-20230223104711412.png)]

  • 实时数据存储:实时数据存储的时候做一些微聚合、过滤某些字段、数据脱敏,组建数据仓库,实时 ETL等
  • 实时数据分析:实时数据接入机器学习框架(TensorFlow)或者一些算法进行数据建模、分析,然后动态的给出商品推荐、广告推荐等
  • 实时监控告警:金融相关涉及交易、实时风控、行车流量预警、服务器监控告警、应用日志告警等
  • 实时数据报表:活动营销时销售额/销售量大屏,TopN 商品等

框架流处理流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YteW3XZe-1678069300472)(/Users/dasouche/Library/Application Support/typora-user-images/image-20230301172756440.png)]

flink电商场景下的业务图示例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wZBOv0os-1678069300472)(/Users/dasouche/Library/Application Support/typora-user-images/image-20230223112526003.png)]

flink中一些重要特性

       Apache Flink 是一个功能强大的流处理引擎,如果你要在 Flink 上进行研发,下面是一些你必须了解的重要特性:

  1. 数据流处理和批处理统一:Flink 支持将批处理和流处理统一到一个编程模型中,这使得 Flink 应用程序可以同时处理无界和有界数据集。
  2. 事件时间处理:Flink 引入了基于事件时间的处理模式,这使得 Flink 应用程序可以处理无序事件数据,并且可以处理延迟事件。
  3. 窗口操作:Flink 可以对无界数据流进行窗口操作,例如滑动窗口、会话窗口、全局窗口等等。
  4. 状态管理:Flink 提供了高效的状态管理,可以轻松地维护和访问应用程序的状态。
  5. 高可用性:Flink 提供了可靠的容错机制,以保证 Flink 应用程序的高可用性和数据完整性。
  6. 数据源集成:Flink 提供了对多种数据源的支持,例如 Mysql HBase、Elasticsearch、Hive、Kafka、Redis 等等。
  7. Flink SQL:Flink SQL 允许用户使用 SQL 语言来进行 Flink 应用程序的编写,这使得 Flink 应用程序的编写变得更加简单和直观。
  8. 深度学习支持:Flink 提供了对深度学习的支持,可以将 TensorFlow、PyTorch 等深度学习框架与 Flink 结合使用,以便在流处理中进行模型训练和推理。
  9. 可扩展性:Flink 可以轻松地水平扩展,以处理大规模数据集,同时还能保持低延迟。

有界数据和无界数据


       Flink 则认为,流处理才是最基本的操作,批处理也可以统一为流处理。在 Flink 的世界观中,万物皆流,实时数据是标准的、没有界限的流,而离线数据则是有界限的流。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jtSzMdLI-1678069300472)(/Users/dasouche/Library/Application Support/typora-user-images/image-20230223152051650.png)]

  1. 无界数据流(Unbounded Data Stream)

​ 所谓无界数据流,就是有头没尾,数据的生成和传递会开始但永远不会结束,如上图所示。我们无法等待所有数据都到达,因为输入是无界的,永无止境,数据没有“都到达”的时候。所以对于无界数据流,必须连续处理,也就是说必须在获取数据后立即处理。在处理无界流时,为了保证结果的正确性,我们必须能够做到按照顺序处理数据。

  1. 有界数据流(Bounded Data Stream)

​ 有界数据流有明确定义的开始和结束,如上图所示,所以我们可以通过获取所有数据来处理有界流。处理有界流就不需要严格保证数据的顺序了,因为总可以对有界数据集进行排序。有界流的处理也就是批处理。

时间语义、水位线

​        在讲解flink的窗口之前,需要先了解一下flink的时间语义中的处理时间、事件时间以及水位线,

事件时间

​         事件时间,是指每个事件在对应的设备上发生的时间,也就是数据生成的时间。

处理时间

​         处理时间的概念非常简单,就是指执行处理操作的机器的系统时间。

​        当然处理时间和时间时间可能在处理事件的时候不一样,因网络波动或者其他因素影响到数据的传输导致在事件处理时不一致,如8点产生的数据在8点01s到达,迟到了一秒,当前系统时间为8:01,而事件时间却是8:00

水位线

​        水位线可以看作一条特殊的数据记录,它是插入到数据流中的一个标记点,主要内容就是一个时间戳,用来指示当前的事件时间。而它插入流中的位置,就应该是在某个数据到来之后;这样就可以从这个数据中提取时间戳,作为当前水位线的时间戳了。

flink窗口

概念

​        Flink 是一种流式计算引擎,主要是来处理无界数据流的,数据源源不断、无穷无尽。想要更加方便高效地处理无界流,一种方式就是将无限数据切割成有限的“数据块”进行处理,这就是所谓的“窗口”(Window)。

​        在 Flink 中, 窗口就是用来处理无界流的核心。我们很容易把窗口想象成一个固定位置的“框”,数据源源不断地流过来,到某个时间点窗口该关闭了,就停止收集数据、触发计算并输出结果。

理想中的数据处理

        如下图所示,到到达间隔时间时,触发这个窗口里所有数据的计算

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VjwkMGdH-1678069300472)(/Users/dasouche/Library/Application Support/typora-user-images/image-20230223164006209.png)]

含有延迟数据的数据处理

​         如下图所示,显示情况中一定会出现因为网络问题数据迟到的问题,下面定义了一个延迟时间为2s的窗口,这样 0~10 秒的窗口会在时间戳为 12 秒的数据到来之后,才真正关闭计算输出结果,这样就可以正常包含迟到的 9 秒数据了。但是这样一来,0~10 秒的窗口不光包含了迟到的 9 秒数据,连 11 秒和 12 秒的数据也包含进去了。我们为了正确处理迟到数据,结果把早到的数据划分到了错误的窗口——最终结果都是错误的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-02PGjh7m-1678069300473)(/Users/dasouche/Library/Application Support/typora-user-images/image-20230223164152420.png)]

Flink存储桶概念

​         在 Flink 中,窗口其实并不是一个“框”,流进来的数据被框住了就只能进这一个窗口。相比之下,我们应该把窗口理解成一个“桶”,如下图所示。在 Flink 中,窗口可以把流切割成有限大小的多个“存储桶”(bucket);每个数据都会分发到对应的桶中,当到达窗口结束时间时,就对每个桶中收集的数据进行计算处理。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UML4iL2n-1678069300473)(/Users/dasouche/Library/Application Support/typora-user-images/image-20230223172846801.png)]

窗口类型

滚动窗口

​         滚动窗口有固定的大小,是一种对数据进行“均匀切片”的划分方式。窗口之间没有重叠,也不会有间隔,是“首尾相接”的状态。如果我们把多个窗口的创建,看作一个窗口的运动,那就好像它在不停地向前“翻滚”一样。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DQLMzdwx-1678069300473)(/Users/dasouche/Library/Application Support/typora-user-images/image-20230223174156294.png)]

滑动窗口

​         与滚动窗口类似,滑动窗口的大小也是固定的。区别在于,窗口之间并不是首尾相接的,而是可以“错开”一定的位置。如果看作一个窗口的运动,那么就像是向前小步“滑动”一样。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wGTATgGX-1678069300473)(/Users/dasouche/Library/Application Support/typora-user-images/image-20230223174207613.png)]

会话窗口

​         会话窗口顾名思义,是基于“会话”(session)来来对数据进行分组的。这里的会话类似Web 应用中 session 的概念,不过并不表示两端的通讯过程,而是借用会话超时失效的机制来描述窗口。简单来说,就是数据来了之后就开启一个会话窗口,如果接下来还有数据陆续到来,那么就一直保持会话;如果一段时间一直没收到数据,那就认为会话超时失效,窗口自动关闭。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cjKb2VQ6-1678069300473)(/Users/dasouche/Library/Application Support/typora-user-images/image-20230223174330822.png)]

全局窗口

​         这种窗口全局有效,会把相同 key 的所有数据都分配到同一个窗口中;说直白一点,就跟没分窗口一样。无界流的数据永无止尽,所以这种窗口也没有结束的时候,默认是不会做触发计算的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f02By1FK-1678069300474)(/Users/dasouche/Library/Application Support/typora-user-images/image-20230223174338871.png)]

flink状态管理

​         状态的存储和管理是由 Flink Runtime 负责的,Flink 提供了多种状态后端来存储和管理状态数据,例如内存状态后端、文件系统状态后端、RocksDB 状态后端等等。Flink 还提供了可插拔的状态后端接口,可以自定义状态后端来满足特定的需求。Flink 还提供了高效的状态快照机制,可以在应用程序故障时对状态进行快速恢复,以保证应用程序的正确性和数据的完整性。

检查点(Checkpoint)

​         有状态流应用中的检查点(checkpoint),其实就是所有任务的状态在某个时间点的一个快照(一份拷贝)。简单来讲,就是一次“存盘”,让我们之前处理数据的进度不要丢掉。在一个流应用程序运行时,Flink 会定期保存检查点,在检查点中会记录每个算子的 id 和状态;如果发生故障,Flink 就会用最近一次成功保存的检查点来恢复应用的状态,重新启动处理流程,就如同“读档”一样。

检查点恢复数据过程

​        这里 Source 任务已经处理完毕,所以偏移量为 5;Map 任务也处理完成了。而 Sum 任务在处理中发生了故障,此时状态并未保存。

1、在算子计算时发生故障

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5XwGF2rI-1678069300474)(/Users/dasouche/Library/Application Support/typora-user-images/image-20230224095032549.png)]

2、应用重启

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mMlP1DIM-1678069300474)(/Users/dasouche/Library/Application Support/typora-user-images/image-20230227112901014.png)]

3、读取检查点,重置状态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zhIAm3lI-1678069300474)(/Users/dasouche/Library/Application Support/typora-user-images/image-20230227112919732.png)]

4、读取检查点,重制偏移量

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3B1JXb1H-1678069300474)(/Users/dasouche/Library/Application Support/typora-user-images/image-20230227112934978.png)]

5、继续处理数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kh5axgh8-1678069300475)(/Users/dasouche/Library/Application Support/typora-user-images/image-20230227112943434.png)]

下载安装

flink下载页:点击跳转

flink官方文档地址:点击跳转

安装后启动bin目录下./start-cluster.sh文件(⚠️:需要配置java环境变量),访问地址:localhost:8081

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mfpBqIho-1678069300475)(/Users/dasouche/Library/Application Support/typora-user-images/image-20230223103612678.png)]

入门Demo示例

pom配置


  <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <flink.version>1.13.0</flink.version>
    <java.version>1.8</java.version>
    <scala.binary.version>2.12</scala.binary.version>
    <slf4j.version>1.7.30</slf4j.version>
  </properties>
  <dependencies>
    <!-- 引入 Flink 相关依赖-->
    <dependency>
      <groupId>org.apache.flink</groupId>
      <artifactId>flink-java</artifactId>
      <version>${flink.version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.flink</groupId>
      <artifactId>flink-streaming-java_${scala.binary.version}</artifactId>
      <version>${flink.version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.flink</groupId>
      <artifactId>flink-clients_${scala.binary.version}</artifactId>
      <version>${flink.version}</version>
    </dependency>
    <!-- 引入日志管理相关依赖-->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>${slf4j.version}</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>${slf4j.version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-to-slf4j</artifactId>
      <version>2.14.0</version>
    </dependency>


    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.22</version>
      <scope>compile</scope>
    </dependency>

    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.58</version>
    </dependency>

    <dependency>
      <groupId>org.apache.flink</groupId>
      <artifactId>flink-table-api-java-bridge_${scala.binary.version}</artifactId>
      <version>${flink.version}</version>
    </dependency>

    <dependency>
      <groupId>org.apache.flink</groupId>
      <artifactId>flink-table-planner-blink_${scala.binary.version}</artifactId>
      <version>${flink.version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.flink</groupId>
      <artifactId>flink-streaming-scala_${scala.binary.version}</artifactId>
      <version>${flink.version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.flink</groupId>
      <artifactId>flink-table-common</artifactId>
      <version>${flink.version}</version>
    </dependency>

  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-assembly-plugin</artifactId>
        <version>3.0.0</version>
        <configuration>
          <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
          </descriptorRefs>
        </configuration>
        <executions>
          <execution>
            <id>make-assembly</id>
            <phase>package</phase>
            <goals>
              <goal>single</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>net.alchim31.maven</groupId>
        <artifactId>scala-maven-plugin</artifactId>
        <version>3.2.2</version>
        <executions>
          <execution>
            <id>scala-compile-first</id>
            <phase>process-resources</phase>
            <goals>
              <goal>add-source</goal>
              <goal>compile</goal>
            </goals>
          </execution>
          <execution>
            <id>scala-test-compile</id>
            <phase>process-test-resources</phase>
            <goals>
              <goal>testCompile</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
    </plugins>
  </build>

Demo代码

import com.alibaba.fastjson.JSON;
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.java.functions.KeySelector;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.util.Collector;

public class FlinkExampleWordCount {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment executionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment();
        executionEnvironment.setParallelism(4);

      	//使用nc -lk 9999,进行数据流输入,如hello word hello flink
        DataStream<String> dataStream = executionEnvironment.socketTextStream("localhost", 9999);
        dataStream
                .flatMap(new FlatMapFunction<String, Tuple2<String, Integer>>() {
                             @Override
                             public void flatMap(String data, Collector<Tuple2<String, Integer>> collector) throws Exception {
                                 String[] split = data.split("\\s");
                                 System.out.println(JSON.toJSON(split));
                                 for (String word : split) {
                                     collector.collect(Tuple2.of(word, 1));
                                 }

                             }
                         }
                )
                .keyBy(new KeySelector<Tuple2<String, Integer>, String>() {
                    @Override
                    public String getKey(Tuple2<String, Integer> stringIntegerTuple2) throws Exception {
                        return stringIntegerTuple2.f0;
                    }
                })
                .sum(1)
                .print();


        executionEnvironment.execute();
    }
}

打包到集群

使用maven对项目package打包,将打好的jar包上传到集群中,可以通过界面上传或者拷贝到机器上直接用指令进行运行

界面上传

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eriiG7bt-1678069300475)(/Users/dasouche/Library/Application Support/typora-user-images/image-20230302104450978.png)]

       点击Submit之后可以取相应位置看输出日志

日志查看
在这里插入图片描述

流运行时执行环境

       每个 Flink 应用程序都需要一个执行环境env。流媒体应用程序需要使用StreamExecutionEnvironment.应用程序中进行的 DataStream API 调用构建了一个附加到 StreamExecutionEnvironment. 当env.execute()被调用时,这个图被打包并发送到 JobManagerJobManager 将作业并行化并将它的切片分配给任务管理器以供执行。您的作业的每个并行切片都将在 任务槽中执行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WUywNxn2-1678069300475)(/Users/dasouche/Library/Application Support/typora-user-images/image-20230302101620325.png)]

任务槽Slot

       每个worker(TaskManager)都是一个JVM进程,可以在不同的线程中执行一个或多个子任务。为了控制 TaskManager 接受多少任务,它有所谓的任务槽(至少一个)。每个 TaskManager 有一个插槽意味着每个任务组都在单独的 JVM 中运行(例如,可以在单独的容器中启动)。拥有多个槽意味着更多的子任务共享同一个 JVM。同一个 JVM 中的任务共享 TCP 连接(通过多路复用)和心跳消息。它们还可以共享数据集和数据结构,从而减少每个任务的开销。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uzRFc6og-1678069300476)(/Users/dasouche/Library/Application Support/typora-user-images/image-20230302103243631.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tTap7FNw-1678069300476)(/Users/dasouche/Library/Application Support/typora-user-images/image-20230302103250679.png)]

扩展Demo

时间窗口Demo

import lombok.*;
import org.apache.flink.api.common.eventtime.SerializableTimestampAssigner;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.api.common.functions.AggregateFunction;
import org.apache.flink.api.common.state.ListState;
import org.apache.flink.api.common.state.ListStateDescriptor;
import org.apache.flink.api.common.typeinfo.Types;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.KeyedProcessFunction;
import org.apache.flink.streaming.api.functions.windowing.ProcessWindowFunction;
import org.apache.flink.streaming.api.windowing.assigners.SlidingEventTimeWindows;
import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
import org.apache.flink.util.Collector;
import org.apache.flink.streaming.api.functions.source.SourceFunction;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Comparator;
import java.util.Random;

import java.sql.Timestamp;

public class FlinkWindowTopNDemo {
    public static void main(String[] args) throws Exception {

        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        env.getConfig().setAutoWatermarkInterval(100);

        SingleOutputStreamOperator<UserEvent> eventStream = env.addSource(new CustomSouce())
                .assignTimestampsAndWatermarks(WatermarkStrategy.<UserEvent>forMonotonousTimestamps()
                        .withTimestampAssigner(new SerializableTimestampAssigner<UserEvent>() {
                            @Override
                            public long extractTimestamp(UserEvent element, long
                                    recordTimestamp) {
                                return element.getTimestemp();
                            }
                        }));

        //按照访问url分区,求出每个 url 的访问量
        SingleOutputStreamOperator<UserViewCount> urlCountStream = eventStream
                .keyBy(data -> data.getUrl())
                //滚动窗口:5s间隔
                .window(TumblingEventTimeWindows.of(Time.seconds(5)))
                //自定义聚合函数
                .aggregate(new NewUrlViewCountAgg(), new NewUrlViewCountResult());

        // 对结果中同一个窗口的统计数据,进行排序处理
        SingleOutputStreamOperator<String> result = urlCountStream
                .keyBy(data -> data.windowEnd)
                .process(new TopN(3));
        result.print("result");


        env.execute();
    }

    //自定义增量聚合
    public static class NewUrlViewCountAgg implements AggregateFunction<UserEvent, Long, Long> {
        @Override
        public Long createAccumulator() {
            return 0L;
        }

        @Override
        public Long add(UserEvent value, Long accumulator) {
            return accumulator + 1L;
        }

        @Override
        public Long getResult(Long accumulator) {
            return accumulator;
        }

        @Override
        public Long merge(Long a, Long b) {
            return null;
        }
    }

    //自定义全窗口函数,只需要包装窗口信息
    public static class NewUrlViewCountResult extends ProcessWindowFunction<Long, UserViewCount, String, TimeWindow> {

        @Override
        public void process(String url, ProcessWindowFunction<Long, UserViewCount, String, TimeWindow>.Context context, Iterable<Long> elements, Collector<UserViewCount> out) throws Exception {
            // 结合窗口信息,包装输出内容
            long currentWindowStartTimeStemp = context.window().getStart();
            long currentWindowEndTimeStemp = context.window().getEnd();
            out.collect(new UserViewCount(url, elements.iterator().next(), currentWindowStartTimeStemp, currentWindowEndTimeStemp));
        }
    }

    // 自定义处理函数,排序取 top n
    public static class TopN extends KeyedProcessFunction<Long, UserViewCount, String> {
        // 将 n 作为属性
        private Integer n;
        // 定义一个列表状态
        private ListState<UserViewCount> urlViewCountListState;

        public TopN(Integer n) {
            this.n = n;
        }

        public TopN() {
            super();
        }

        @Override
        public void open(Configuration parameters) throws Exception {
            // 从环境中获取列表状态句柄
            urlViewCountListState = getRuntimeContext().getListState(
                    new ListStateDescriptor<UserViewCount>("url-view-count-list",
                            Types.POJO(UserViewCount.class)));
        }

        @Override
        public void processElement(UserViewCount value, KeyedProcessFunction<Long, UserViewCount, String>.Context ctx, Collector<String> out) throws Exception {
            // 将 count 数据添加到列表状态中,保存起来
            urlViewCountListState.add(value);

            // 注册 window end + 1ms 后的定时器,等待所有数据到齐开始排序
            ctx.timerService().registerEventTimeTimer(ctx.getCurrentKey() + 1);
        }

        @Override
        public void onTimer(long timestamp, KeyedProcessFunction<Long, UserViewCount, String>.OnTimerContext ctx, Collector<String> out) throws Exception {
            // 将数据从列表状态变量中取出,放入 ArrayList,方便排序
            ArrayList<UserViewCount> urlViewCountArrayList = new ArrayList<>();
            for (UserViewCount urlViewCount : urlViewCountListState.get()) {
                urlViewCountArrayList.add(urlViewCount);
            }

            // 清空状态,释放资源
            urlViewCountListState.clear();

            // 排序
            urlViewCountArrayList.sort(new Comparator<UserViewCount>() {
                @Override
                public int compare(UserViewCount o1, UserViewCount o2) {
                    return o2.count.intValue() - o1.count.intValue();
                }
            });


            // 提取前n名(由函数入口提供),构建输出结果
            StringBuilder result = new StringBuilder();
            result.append("========================================\n");
            result.append("窗口结束时间:" + new Timestamp(timestamp - 1) + "\n");
            for (int i = 0; i < this.n; i++) {
                UserViewCount userViewCount = urlViewCountArrayList.get(i);
                if (null == userViewCount) {
                    break;
                }
                result.append("处理数据所在窗口:" + new Timestamp(userViewCount.getWindowStart()) + "~" + new Timestamp(userViewCount.getWindowEnd()));
                UserViewCount needUserView = userViewCount;
                String info = "   No." + (i + 1) + " "
                        + "url:" + userViewCount.url + " "
                        + "浏览量:" + userViewCount.count + "\n";
                result.append(info);
            }
            result.append("========================================\n");
            out.collect(result.toString());

        }

    }



    @Data
    @NoArgsConstructor
    @ToString
    @AllArgsConstructor
    public static class UserEvent {
        private String userName;
        private String url;
        private Long timestemp;
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public static class UserViewCount {
        public String url;
        public Long count;
        public Long windowStart;
        public Long windowEnd;

        @Override
        public String toString() {
            return "UserViewCount{" +
                    "url='" + url + '\'' +
                    ", count=" + count +
                    ", windowStart=" + new Timestamp(windowStart) +
                    ", windowEnd=" + new Timestamp(windowEnd) +
                    '}';
        }
    }

    public static class CustomSouce implements SourceFunction<UserEvent> {
        // 声明一个布尔变量,作为控制数据生成的标识位
        private Boolean running = true;

        @Override
        public void run(SourceContext<UserEvent> ctx) throws Exception {
            Random random = new Random(); // 在指定的数据集中随机选取数据
            String[] users = {"Mary", "Alice", "Bob", "Cary"};
            String[] urls = {"./home", "./cart", "./fav", "./prod?id=1",
                    "./prod?id=2"};
            while (running) {
                ctx.collect(new UserEvent(
                        users[random.nextInt(users.length)],
                        urls[random.nextInt(urls.length)],
                        Calendar .getInstance().getTimeInMillis()
                ));
                // 隔 200 ms生成一个点击事件,方便观测
                Thread.sleep(200);
            }
        }
        @Override
        public void cancel() {
            running = false;
        }

    }

}

运行结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-28CUYu1X-1678069300476)(/Users/dasouche/Library/Application Support/typora-user-images/image-20230302160207101.png)]

Table Api Demo

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.table.api.Table;
import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;

public class TableApiDemo {

    public static void main(String[] args) throws Exception {
        // 获取流执行环境
        StreamExecutionEnvironment env =
                StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        // 读取数据源
        SingleOutputStreamOperator<UserEvent> eventStream = env
                .fromElements(
                        new UserEvent("Alice", "./home", 1000L),
                        new UserEvent("Bob", "./cart", 1000L),
                        new UserEvent("Alice", "./prod?id=1", 5 * 1000L),
                        new UserEvent("Cary", "./home", 60 * 1000L),
                        new UserEvent("Bob", "./prod?id=3", 90 * 1000L),
                        new UserEvent("Alice", "./prod?id=7", 105 * 1000L)
                );
        // 获取表环境
        StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);
        // 将数据流转换成表
        Table eventTable = tableEnv.fromDataStream(eventStream);
        // 用执行 SQL 的方式提取数据
        Table visitTable = tableEnv.sqlQuery("select url, userName from " + eventTable);
        // 将表转换成数据流,打印输出
        tableEnv.toDataStream(visitTable).print();
        // 执行程序
        env.execute();
    }

    @Data
    @NoArgsConstructor
    @ToString
    @AllArgsConstructor
    public static class UserEvent {
        private String userName;
        private String url;
        private Long timestemp;
    }

}

运行结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ekmka372-1678069300476)(/Users/dasouche/Library/Application Support/typora-user-images/image-20230302155008831.png)]

对迟到数据处理Demo

import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.flink.api.common.eventtime.SerializableTimestampAssigner;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.api.common.functions.AggregateFunction;
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.api.java.tuple.Tuple3;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor;
import org.apache.flink.streaming.api.functions.windowing.ProcessWindowFunction;
import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
import org.apache.flink.util.Collector;
import org.apache.flink.util.OutputTag;
import org.example.entity.Event;

import java.sql.Timestamp;
import java.time.Duration;

public class LateDataProcessExampleDemo {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env =
                StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        // 读取 socket 文本流
        /** 样例数据
         * Alice ./home 1000
         * Alice ./home 2000
         * Alice ./home 10000
         * Alice ./home 9000
         * Alice ./cart 12000
         * Alice ./prod?id=100 15000
         * Alice ./home 9000
         * Alice ./home 8000
         * Alice ./prod?id=200 70000
         * Alice ./home 8000
         * Alice ./prod?id=300 72000
         * Alice ./home 8000
         */

        SingleOutputStreamOperator<Event> stream =
                env.socketTextStream("localhost", 9999)
                        .map(new MapFunction<String, Event>() {
                            @Override
                            public Event map(String value) throws Exception {
                                String[] fields = value.split("\\s");
                                return new Event(fields[0].trim(), fields[1].trim(),
                                        Long.valueOf(fields[2].trim()));
                            }
                        })
                        // 方式一:设置 watermark 延迟时间,2 秒钟
                        .assignTimestampsAndWatermarks(WatermarkStrategy.<Event>forBoundedOutOfOrderness(Duration.ofSeconds(2))
                                .withTimestampAssigner(new SerializableTimestampAssigner<Event>() {
                                    @Override
                                    public long extractTimestamp(Event element, long
                                            recordTimestamp) {
                                        return element.getTimestamp();
                                    }
                                }));
        // 定义侧输出流标签
        OutputTag<Event> outputTag = new OutputTag<Event>("late") {};
        SingleOutputStreamOperator<UrlViewCount> result = stream
                .keyBy(data -> data.getUrl())
                //设置10s的时间窗口
                .window(TumblingEventTimeWindows.of(Time.seconds(10)))
                // 方式二:允许窗口处理迟到数据,设置 1 分钟的等待时间
                .allowedLateness(Time.minutes(1))
                // 方式三:将最后的迟到数据输出到侧输出流
                .sideOutputLateData(outputTag)
                .aggregate(new UrlViewCountAgg(), new UrlViewCountResult());
        result.print("result");


        /**
         * 输出数据
         * origin data input> Event(user=Alice, url=./prod?id=300, timestamp=72000)
         * origin data input> Event(user=Alice, url=./home, timestamp=8000)
         * late data result> Event(user=Alice, url=./home, timestamp=8000)
         * 
         * 我们设置窗口等待的时间为 1 分钟,所以当时间推进到 10000 + 60 * 1000 = 70000 时,窗
         * 口就会真正被销毁。此前的所有迟到数据可以直接更新窗口的计算结果,而之后的迟到数据已
         * 经无法整合进窗口,就只能用侧输出流来捕获了。需要注意的是,这里的“时间”依然是由水
         * 位线来指示的,所以时间戳为 70000 的数据到来,并不会触发窗口的销毁;当时间戳为 72000
         * 的数据到来,水位线推进到了 72000 – 2 * 1000 = 70000,此时窗口真正销毁关闭,之后再来的
         * 迟到数据就会输出到侧输出流了:
         */
        result.getSideOutput(outputTag).print("late data result");

        // 为方便观察,可以将原始数据也输出
        stream.print("origin data input");
        env.execute();
    }

    public static class UrlViewCountAgg implements AggregateFunction<Event, Long,
            Long> {
        @Override
        public Long createAccumulator() {
            return 0L;
        }

        @Override
        public Long add(Event value, Long accumulator) {
            return accumulator + 1;
        }

        @Override
        public Long getResult(Long accumulator) {
            return accumulator;
        }

        @Override
        public Long merge(Long a, Long b) {
            return null;
        }
    }

    public static class UrlViewCountResult extends ProcessWindowFunction<Long, UrlViewCount, String, TimeWindow> {
        @Override
        public void process(String url, Context context, Iterable<Long> elements,
                            Collector<UrlViewCount> out) throws Exception {
            // 结合窗口信息,包装输出内容
            Long start = context.window().getStart();
            Long end = context.window().getEnd();
            out.collect(new UrlViewCount(url, elements.iterator().next(), start,
                    end));
        }
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public static class UrlViewCount {
        public String url;
        public Long count;
        public Long windowStart;
        public Long windowEnd;

        @Override
        public String toString() {
            return "UrlViewCount{" +
                    "url='" + url + '\'' +
                    ", count=" + count +
                    ", windowStart=" + new Timestamp(windowStart) +
                    ", windowEnd=" + new Timestamp(windowEnd) +
                    '}';
        }
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于BP神经网络的手部动作分类识别,BP神经网络详细原理

目标 背影 BP神经网络的原理 BP神经网络的定义 BP神经网络的基本结构 BP神经网络的神经元 BP神经网络的激活函数&#xff0c; BP神经网络的传递函数 数据 神经网络参数 基于BP神经网络手部动作识别的MATLAB代码 效果图 结果分析 展望 背影 随着人工智能的发展&#xff0c;智…

深入剖析 MVC 模式与三层架构

文章目录1. 前言2. MVC模式3. 三层架构4. MVC和三层架构5. 总结5.1 IDEA 小技巧1. 前言 前面我们探讨了 JSP 的使用&#xff0c;随着计算机技术的不断更新迭代&#xff0c;JSP 的技术由于存在很多的缺点&#xff0c;已经逐渐退出了历史的舞台&#xff0c;所以在学习时&#xf…

加密功能实现

文章目录1. 前言2. 密码加密1. 前言 本文 主要实现 对密码进行加密 &#xff0c;因为 使用 md5 容易被穷举 (彩虹表) 而破解 &#xff0c;使用 spring security 框架又太大了 (杀鸡用牛刀) 。   所以本文 就自己实现一个密码加密 . 2. 密码加密 这里我们通过 加盐是方式 来 对…

pytorch安装的超级详细教程(没有之一)

一、发展历程 &#xff08;简单介绍&#xff09; (15年)caffe --> (16年)tensorflow1.x --> (17年)keras --> (18年)Tensorflow2.x --> (19年)pytorch。 面向gihub开源项目编程。 向下支持比较好&#xff0c;各个版本之间支持比较好&#xff0c;兼容性强。 版本…

Android事件拦截(3)——系统拦截和应用拦截

本文主要分析触摸事件和按键事件在不同阶段被拦截的流程&#xff0c;总结在不同阶段不同方法中返回值的含义。 按键的拦截 &#xff08;1&#xff09;interceptKeyBeforeQueueing interceptKeyBeforeQueueing方法的意义就是在事件入队列前拦截按键事件&#xff0c;也就是如果…

想找工作,这一篇15w字数+的文章帮你解决

文章目录前言一 专业技能1. 熟悉GoLang语言1.1 Slice1.2 Map1.3 Channel1.4 Goroutine1.5 GMP调度1.6 垃圾回收机制1.7 其他知识点2. 掌握Web框架Gin和微服务框架Micro2.1 Gin框架2.2 Micro框架2.3 Viper2.4 Swagger2.5 Zap2.6 JWT3. 熟悉使用 MySQL 数据库3.1 索引3.2 事务3.3…

每日学术速递3.6

Subjects: cs.CV 1.Multi-Source Soft Pseudo-Label Learning with Domain Similarity-based Weighting for Semantic Segmentation 标题&#xff1a;用于语义分割的基于域相似性加权的多源软伪标签学习 作者&#xff1a;Shigemichi Matsuzaki, Hiroaki Masuzawa, Jun Miura …

2022掉队的“蔚小理”,按下了兔年加速键

配图来自Canva可画 进入2023年&#xff0c;各大车企又展开了新一轮的“竞速”。尽管1月份汽车整体销量出现了“阴跌”&#xff0c;但从各路车企发布的销量目标来看&#xff0c;车企对于2023依旧保持着较高的信心和预期。在一众车企中&#xff0c;以“蔚小理”为代表的新势力们…

基于quartz实现定时任务管理系统

基于quartz实现定时任务管理系统 背景 说起定时任务框架&#xff0c;首先想到的是Quartz。这是定时任务的老牌框架了&#xff0c;它的优缺点都很明显。借助PowerJob 的readme文档的内容简单带过一下这部分。 除了上面提到&#xff0c;还有elastic-job-lite、quartzui也是相当…

【C++】仿函数 -- priority_queue

文章目录一、priority_queue 的介绍和使用1、priority_queue 的介绍2、priority_queue 的使用3、priority_queue 相关 OJ 题二、仿函数1、什么是仿函数2、仿函数的作用三、priority_queue 的模拟实现一、priority_queue 的介绍和使用 1、priority_queue 的介绍 priority_queu…

vue3 transition动画

Vue 提供了 transition 的封装组件&#xff0c;通过它可以给任何元素和组件添加进入/离开过渡动画 一、vue怎么实现单组件/元素的过渡动画 Vue 在插入、更新或者移除 DOM 时&#xff0c;提供多种不同方式的应用过渡效果。------vue官网 vue的transition组件通过观察元素的DOM状…

全网最全整理,自动化测试10种场景处理(超详细)解决方案都在这......

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 自动化工作流程 自动…

三种让DIV标签中的P标签水平和垂直都居中方法

效果如下图 红色1块是span&#xff0c;属于行内元素。 绿色2块和蓝色4块是p&#xff0c;属于块级元素。 黄色3块h3&#xff0c;属于块 都是块级元素方法是可以通用的 这里两个类别元素一起来展示主要是为了区别 1行内块元素水平居中垂直居中 行内元素和行内块元素水平居中…

docker项目自动化部署脚本(认真排版、工作积累)

要解决什么问题&#xff1f; 把日益复杂化、工程化的开发环境&#xff0c;以及生产环境&#xff0c;变得简单&#xff0c;自动化部署。 达到什么效果&#xff1f; 环境处处一致&#xff0c;并且自动化部署&#xff0c;提升生产力&#xff0c;又快又好。 当您更换电脑、更换…

Vue2.0开发之——购物车案例-Goods组件封装-修改商品的勾选状态(49)

一 概述 如何修改商品的勾选状态自定义state-change事件修改对应商品的勾选状态 二 如何修改商品的勾选状态 2.1 App.vue中data每个Item中goods_state的变化伴随商品勾选状态变化 2.2 Goods.vue中复选框的值是props属性 <inputtype"checkbox"class"custom…

LeeCode:回文子串个数(动态规划)

文章目录一、题目二、算法思路三、代码实现四、复杂度分析一、题目 给你一个字符串 s &#xff0c;请你统计并返回这个字符串中 回文子串 的数目。具有不同开始位置或结束位置的子串&#xff0c;即使是由相同的字符组成&#xff0c;也会被视作不同的子串。 回文字符串 是正着读…

二值图像骨架线提取

二值图像骨架线提取HilditchThin算法Rosenfeld算法OpenCV_Contrib中的算法示例其他细化算法查表法HilditchThin的另一种算法参考二值图像骨架线提取算法&#xff1a;HilditchThin算法、Rosenfeld算法、OpenCV_Contrib中的算法 HilditchThin算法 1、使用的8邻域标记为&#xff…

Java+ElasticSearch+Pytorch实现以图搜图

以图搜图&#xff0c;涉及两大功能&#xff1a;1、提取图像特征向量。2、相似向量检索。第一个功能我通过编写pytorch模型并在java端借助djl调用实现&#xff0c;第二个功能通过elasticsearch7.6.2的dense_vector、cosineSimilarity实现。一、准备模型创建demo.py&#xff0c;输…

cuda2D FDTD——share

https://www.coder.work/article/30133 shared memory只能在block内共享&#xff0c;之间无法互相通信 对于2D TM波动方程计算&#xff0c;我们可以使用以下策略来处理共享内存的边界&#xff1a; 将全局内存中的数据复制到共享内存中时&#xff0c;除了将每个线程需要的数据…

Python爬虫实践:优志愿 院校列表

https://www.youzy.cn/tzy/search/colleges/collegeList获取目标网址等信息打开开发人员工具&#xff08;F12&#xff09;&#xff0c;拿到调用接口的地址&#xff0c;以及接口请求参数等信息&#xff0c;如下curl https://uwf7de983aad7a717eb.youzy.cn/youzy.dms.basiclib.ap…