├── .gitignore ├── README.md ├── dependency-reduced-pom.xml ├── note ├── 2019-10-10-flink_learn_introduction.md ├── 2019-10-13-flink_learn_hello_world.md ├── 2019-10-24-flink_learn_datasource.md ├── 2019-11-12-flink_learn_transformation.md ├── 2019-11-21-flink_learn_datasink.md ├── 2019-11-27-flink_learn_time.md ├── 2019-12-01-flink_learn_window.md └── pics │ ├── Flink_study_diagram.png │ ├── datasink │ ├── flink_demo_flow_chart.png │ ├── sinkfuncaiton_diagram.png │ ├── sinkfunction_customsink.png │ ├── sinkfunction_data_flow.png │ ├── sinkfunction_datasource.png │ ├── sinkfunction_printsinkfunction_diagram.png │ ├── sinkfunction_source&job.png │ └── sinkfunction_verify.png │ ├── datasource │ ├── StreamExecutionEnvironment_DataSource.png │ ├── flink_data_source_connector.png │ ├── flink_datasource_kafka.png │ ├── flink_datasource_kafka_result.png │ ├── flink_datasource_mind.png │ └── flink_rich_source_function.png │ ├── helloworld │ ├── flink_dashboard.png │ ├── flink_debug_method.png │ ├── flink_demo_overview.png │ ├── flink_hello_world_time_winodw.png │ ├── flink_helloworld_process.png │ └── flink_run_demo.png │ ├── introduction │ ├── alibaba_flink_preview.jpeg │ ├── api-stack.png │ ├── bounded-unbounded.png │ ├── event_ingestion_processing_time.svg │ ├── flink_application_semantic.png │ ├── flink_architecture.jpeg │ ├── flink_distract_architecture.png │ ├── flink_jobManager_worker.jpg │ ├── flink_state_checkpoint_introducation.png │ ├── flink_storm_throughput_contrast.png │ ├── flink_streaming_dataflow.svg │ ├── flink_strom_delayed_contrast.png │ ├── levels_of_abstraction.svg │ └── system_architecture.png │ ├── time │ ├── flink_time_introduction.png │ ├── parallel_streams_watermarks.svg │ ├── stream_watermark_in_order.svg │ └── stream_watermark_out_of_order.svg │ ├── transformation │ ├── flink_transformation_demo_methods.png │ ├── flink_transformation_min.png │ ├── flink_transformation_minBy&min_diff.png │ ├── flink_transformation_minBy.png │ └── flink_transformation_official_desc.png │ └── window │ ├── evictor_impls.png │ ├── flink_window_mechanism.png │ ├── flink_window_method.png │ ├── flink_window_sensor_intro.png │ ├── flink_window_xmind.png │ ├── jark_window_intro.png │ ├── trigger_impls.png │ └── window_assigner_impls.png ├── pom.xml └── src └── main ├── java └── cn │ └── sevenyuan │ ├── BatchJob.java │ ├── StreamingJob.java │ ├── datasource │ ├── DataSourceFromCollection.java │ ├── DataSourceFromFile.java │ ├── DataSourceFromSocket.java │ └── custom │ │ ├── DataSourceFromKafka.java │ │ ├── DataSourceFromRedis.java │ │ └── MyRedisDataSourceFunction.java │ ├── domain │ ├── Student.java │ └── WordWithCount.java │ ├── hotest │ └── itemcount │ │ ├── CountAgg.java │ │ ├── HotItems.java │ │ ├── ItemViewCount.java │ │ ├── TopNHotItems.java │ │ ├── UserBehavior.java │ │ └── WindowResultFunction.java │ ├── sink │ └── SinkToMySQL.java │ ├── transformation │ ├── MyExtendTimestampExtractor.java │ ├── MyTimestampExtractor.java │ ├── OperatorTransformation.java │ ├── StudentTimeAndWindowTransformation.java │ └── studentwindow │ │ ├── CountStudentAgg.java │ │ ├── StudentViewCount.java │ │ └── WindowStudentResultFunction.java │ ├── util │ ├── KafkaUtils.java │ ├── MyDruidUtils.java │ └── RedisUtils.java │ ├── watermark │ ├── EventTimeData.txt │ ├── UnOrderEventTimeWindowDemo.java │ └── WordCountPeriodicWatermarks.java │ └── wordcount │ ├── SocketTextStreamWordCount.java │ └── SocketWindowWordCount.java └── resources ├── UserBehavior.csv ├── datasource ├── student.txt └── student_min&minBy.txt └── log4j.properties /.gitignore: -------------------------------------------------------------------------------- 1 | # Project exclude paths 2 | /target/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Welcome to flink-learning-note 👋

2 |

3 | 4 | 5 | 6 | JueJin: Vip-Augus 7 | 8 |

9 | 10 | > Flink 流式计算框架学习笔记 11 | 12 | **一直对流式计算引擎感兴趣,于是一边学习,一边总结知识点,加深理解** 13 | 14 | **同时由于个人技术有限,出现理解错误或者有疑问的地方,可在掘金文章下留言,期待朋友们的反馈~** 15 | 16 | 17 | ### 🏠 [工程地址](https://github.com/Vip-Augus/flink-learning-note) 18 | 19 | ## 下载源码 20 | 21 | ```sh 22 | git clone https://github.com/Vip-Augus/flink-learning-note 23 | ``` 24 | 25 | ## 代码执行 26 | 27 | ![](note/pics/helloworld/flink_debug_method.png) 28 | 29 | ## 传送门: 30 | 31 | - [Flink 基础学习(一)初识和基础概念](note/2019-10-10-flink_learn_introduction.md) 32 | 33 | - [Flink 基础学习(二)搭建一个 "Hello World" 程序](note/2019-10-13-flink_learn_hello_world.md) 34 | 35 | - [Flink 基础学习(三)数据源 DataSource](note/2019-10-24-flink_learn_datasource.md) 36 | 37 | - [Flink 基础学习(四)数据转换 Transformation](note/2019-11-12-flink_learn_transformation.md) 38 | 39 | - [Flink 基础学习(五)数据存储 DataSink](note/2019-11-21-flink_learn_datasink.md) 40 | 41 | - [Flink 基础学习(六)时间 Time 和 Watermark](note/2019-11-27-flink_learn_time.md) 42 | 43 | - [Flink 基础学习(七)窗口 Window](note/2019-12-01-flink_learn_window.md) 44 | 45 | 46 | 47 | ## 关于我 48 | 49 | 👤 **JingQ** 50 | 51 | * Github: [@Vip-Augus](https://github.com/Vip-Augus) 52 | * JueJin: [@Vip-Augus](https://juejin.im/user/58782b471b69e6005823ab38) 53 | * Github page : [JingQ](http://vip-augus.github.io) 54 | 55 | ## :orange_book: 参考资料 56 | 57 | - [`Flink` 官网](https://ci.apache.org/projects/flink/flink-docs-release-1.9/) 58 | - [`Vererica` 国内牛人的分享](https://ververica.cn) 59 | - [`Github 项目`](https://github.com/apache/flink) 60 | - [`zhisheng`](http://www.54tianzhisheng.cn/tags/Flink/) 61 | - [`wuchong`](http://wuchong.me/categories/Flink/) 62 | 63 | 64 | ## 知识点 65 | 66 | ![](note/pics/Flink_study_diagram.png) 67 | 68 | ## 点个小星星✨ 69 | 70 | **如果觉得这个项目不错,请点个小星星,蟹蟹你的鼓励~** 71 | 72 | *** 73 | _This README was generated with ❤️ by [readme-md-generator](https://github.com/kefranabg/readme-md-generator)_ 74 | -------------------------------------------------------------------------------- /dependency-reduced-pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | cn.sevenyuan 5 | flink-quick-start 6 | Flink Quickstart Job 7 | 1.0-SNAPSHOT 8 | http://www.myorganization.org 9 | 10 | 11 | 12 | 13 | org.eclipse.m2e 14 | lifecycle-mapping 15 | 1.0.0 16 | 17 | 18 | 19 | 20 | 21 | org.apache.maven.plugins 22 | maven-shade-plugin 23 | [3.0.0,) 24 | 25 | shade 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | org.apache.maven.plugins 35 | maven-compiler-plugin 36 | [3.1,) 37 | 38 | testCompile 39 | compile 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | maven-compiler-plugin 55 | 3.1 56 | 57 | ${java.version} 58 | ${java.version} 59 | 60 | 61 | 62 | maven-shade-plugin 63 | 3.0.0 64 | 65 | 66 | package 67 | 68 | shade 69 | 70 | 71 | 72 | 73 | org.apache.flink:force-shading 74 | com.google.code.findbugs:jsr305 75 | org.slf4j:* 76 | log4j:* 77 | 78 | 79 | 80 | 81 | *:* 82 | 83 | META-INF/*.SF 84 | META-INF/*.DSA 85 | META-INF/*.RSA 86 | 87 | 88 | 89 | 90 | 91 | cn.sevenyuan.SocketWindowWordCount 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | add-dependencies-for-IDEA 103 | 104 | 105 | org.apache.flink 106 | flink-java 107 | ${flink.version} 108 | compile 109 | 110 | 111 | org.apache.flink 112 | flink-streaming-java_${scala.binary.version} 113 | ${flink.version} 114 | compile 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | false 123 | 124 | 125 | apache.snapshots 126 | Apache Development Snapshot Repository 127 | https://repository.apache.org/content/repositories/snapshots/ 128 | 129 | 130 | 131 | 132 | org.apache.flink 133 | flink-java 134 | 1.9.0 135 | provided 136 | 137 | 138 | flink-core 139 | org.apache.flink 140 | 141 | 142 | flink-shaded-asm-6 143 | org.apache.flink 144 | 145 | 146 | commons-lang3 147 | org.apache.commons 148 | 149 | 150 | commons-math3 151 | org.apache.commons 152 | 153 | 154 | jsr305 155 | com.google.code.findbugs 156 | 157 | 158 | force-shading 159 | org.apache.flink 160 | 161 | 162 | 163 | 164 | org.apache.flink 165 | flink-streaming-java_2.11 166 | 1.9.0 167 | provided 168 | 169 | 170 | flink-runtime_2.11 171 | org.apache.flink 172 | 173 | 174 | flink-clients_2.11 175 | org.apache.flink 176 | 177 | 178 | flink-shaded-guava 179 | org.apache.flink 180 | 181 | 182 | flink-core 183 | org.apache.flink 184 | 185 | 186 | commons-math3 187 | org.apache.commons 188 | 189 | 190 | jsr305 191 | com.google.code.findbugs 192 | 193 | 194 | force-shading 195 | org.apache.flink 196 | 197 | 198 | 199 | 200 | org.slf4j 201 | slf4j-log4j12 202 | 1.7.7 203 | runtime 204 | 205 | 206 | log4j 207 | log4j 208 | 1.2.17 209 | runtime 210 | 211 | 212 | 213 | 1.8 214 | 2.11 215 | 1.9.0 216 | ${java.version} 217 | UTF-8 218 | ${java.version} 219 | 220 | 221 | 222 | -------------------------------------------------------------------------------- /note/2019-10-10-flink_learn_introduction.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [为什么要学 Flink](#%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E5%AD%A6-flink) 6 | - [官网介绍](#%E5%AE%98%E7%BD%91%E4%BB%8B%E7%BB%8D) 7 | - [基础语义](#%E5%9F%BA%E7%A1%80%E8%AF%AD%E4%B9%89) 8 | - [1、流 Stream](#1%E6%B5%81-stream) 9 | - [2、状态 State](#2%E7%8A%B6%E6%80%81-state) 10 | - [3、时间 Time](#3%E6%97%B6%E9%97%B4-time) 11 | - [4、接口 API](#4%E6%8E%A5%E5%8F%A3-api) 12 | - [架构介绍](#%E6%9E%B6%E6%9E%84%E4%BB%8B%E7%BB%8D) 13 | - [1、有界和无界数据流](#1%E6%9C%89%E7%95%8C%E5%92%8C%E6%97%A0%E7%95%8C%E6%95%B0%E6%8D%AE%E6%B5%81) 14 | - [2、部署灵活](#2%E9%83%A8%E7%BD%B2%E7%81%B5%E6%B4%BB) 15 | - [3、极高的可伸缩性](#3%E6%9E%81%E9%AB%98%E7%9A%84%E5%8F%AF%E4%BC%B8%E7%BC%A9%E6%80%A7) 16 | - [4、极致的流式处理性能](#4%E6%9E%81%E8%87%B4%E7%9A%84%E6%B5%81%E5%BC%8F%E5%A4%84%E7%90%86%E6%80%A7%E8%83%BD) 17 | - [特性和优点](#%E7%89%B9%E6%80%A7%E5%92%8C%E4%BC%98%E7%82%B9) 18 | - [处理流程](#%E5%A4%84%E7%90%86%E6%B5%81%E7%A8%8B) 19 | - [1、数据源 Source](#1%E6%95%B0%E6%8D%AE%E6%BA%90-source) 20 | - [2、转换 Transaction](#2%E8%BD%AC%E6%8D%A2-transaction) 21 | - [3、存储 Sink](#3%E5%AD%98%E5%82%A8-sink) 22 | - [性能比较](#%E6%80%A7%E8%83%BD%E6%AF%94%E8%BE%83) 23 | - [管理方式 JobManager、TaskWorker](#%E7%AE%A1%E7%90%86%E6%96%B9%E5%BC%8F-jobmanagertaskworker) 24 | - [高可用 HA、状态恢复](#%E9%AB%98%E5%8F%AF%E7%94%A8-ha%E7%8A%B6%E6%80%81%E6%81%A2%E5%A4%8D) 25 | - [社区生态](#%E7%A4%BE%E5%8C%BA%E7%94%9F%E6%80%81) 26 | - [基础知识点](#%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E7%82%B9) 27 | - [总结:未来的计算方式](#%E6%80%BB%E7%BB%93%E6%9C%AA%E6%9D%A5%E7%9A%84%E8%AE%A1%E7%AE%97%E6%96%B9%E5%BC%8F) 28 | - [参考资料](#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99) 29 | 30 | 31 | 32 | 33 | 34 | # 为什么要学 Flink 35 | 36 | 在 18 年时,就听说过 `Flink` 流式计算引擎,是阿里调研选型选择的新一代大数据框计算架,当时就记住了这个新框架。 37 | 38 | 由于工作中,常写的还是业务开发,没有系统的去学习它,恰好在今年,**我们的数据增长越来越快,架构师提出可以根据数据进行加工,通过数据分析得到更多指标性的计算结果,提供更多有价值的业务给用户。** 39 | 40 | 于是规划了基于以下的系统架构: 41 | 42 | ![](./pics/introduction/system_architecture.png) 43 | 44 | **可以看到,业务数据库与数据分析进行了剥离,避免了对核心业务的影响,基于数据分析的结果存储到线下备份库,查询大量的分析结果也不会影响核心业务。** 45 | 46 | 同时,在数据处理上,选择了 `Flink` 这个分布式处理引擎。随着深入的调研和了解,从它的描述、性能、接口编程和容错恢复上进行了学习,觉得它十分适合我们的场景,所以接下来我分享一下调研的结果~ 47 | 48 | 49 | 50 | 51 | 52 | # 官网介绍 53 | 54 | > [Apache Flink 是什么?](https://flink.apache.org/zh/flink-architecture.html) 55 | > Apache Flink 是一个框架和分布式处理引擎,用于在无边界和有边界数据流上进行有状态的计算。Flink 能在所有常见集群环境中运行,并能以内存速度和任意规模进行计算。 56 | 57 | 58 | 官网也有中文版的文档,但是翻译的并不是很全面,经常跳转到英文博文,**这里推荐一个国内网站 [https://ververica.cn/](https://ververica.cn/),上面的翻译和技术分享都十分的棒**,一开始就看英文文档还是比较吃力的,所以可以先从国内牛人的分享中学习起来~ 59 | 60 | --- 61 | ## 基础语义 62 | 63 | 基础语义很重要,高层语法都是基础基础语义加工的,所以需要对它们有个了解,这里推荐的是 [`ververica`](https://ververica.cn/developers/flink-basic-tutorial-1-basic-concept/) 中的介绍: 64 | 65 | 66 | ### 1、流 Stream 67 | 68 | ![](./pics/introduction/bounded-unbounded.png) 69 | 70 | 从上图中看出,分为 **有界(bounded)** 和 **无界(unbounded)** 数据流。二者的区别在于无限数据流的数据会随着时间的推演而持续增加,计算持续进行且不存在结束的状态,相对的有限数据流大小固定,计算最终会完成并处于结束的状态。 71 | 72 | 同样还有 **实时** 和 **历史记录** 属性。流中的数据一生成就得到实时处理;另一种可能时效性要求不高,只需要在凌晨统计前一天完整的数据,可以先将数据流持久化到存储系统中,然后再进行批处理。 73 | 74 | 75 | ### 2、状态 State 76 | 77 | 状态时计算过程中的数据信息,在容错恢复和 `Checkpoint` 中有重要的作用,流计算在本质上是增量处理,因此需要不断查询保持状态;另外,为了保证 `Excatly-once` 语义,还需要将数据写入到状态中,用来保证在故障发生时,通过保存在状态中的数据,进行恢复,保证一致性;还有持久化存储,能够保证在整个分布式系统运行失败或者挂掉的情况下做到 `Exactly-once`,这是状态的另一个价值。 78 | 79 | 80 | ### 3、时间 Time 81 | 82 | ![](./pics/introduction/event_ingestion_processing_time.svg) 83 | 84 | 分为**事件时间(Event Time)**、**摄入时间(Ingestion Time)**、**处理时间(Processing Time)**,`Flink` 的无限数据流是一个持续的过程,时间是我们判断业务状态是否滞后,数据处理是否及时的重要依据。 85 | 86 | `事件时间`:事件发生的时间,一般数据源携带的一个字段,指明事件发生的时间。 87 | 88 | `处理时间`:事件被处理的时间,也就是由机器的系统时间决定。 89 | 90 | `摄入时间`:时间进入 `Flink` 的时间,在数据源处,事件将会以当源的操作时间作为时间戳。 91 | 92 | **三个时间出现的位置在上图的底部,具体的之后会专门讲述~** 93 | 94 | 95 | ### 4、接口 API 96 | 97 | ![](./pics/introduction/api-stack.png) 98 | 99 | 从上往下有三层,分别是 `SQL/Table API`、`DataStream API` 和 `ProcessFunction`,`API` 的表达能力及业务抽象能力都非常强大,但越接近 `SQL` 层,表达能力会逐步减弱,抽象能力会增强(由于是基础了解,所以没有对于 `SQL API` 层深入学习,感兴趣的同学可以深入了解)。 100 | 101 | 反之, `ProessFunction` 层 `API` 的表达能力非常强,可以进行多种灵活方便的操作,但抽象能力也相对越小。 102 | 103 | **结合上面说的,平时用到最多的是中间 `DataStream` 这层的 `API`,后面的内容学习基本也是围绕着它~** 104 | 105 | --- 106 | ## 架构介绍 107 | 108 | 来源 [https://ververica.cn/developers/flink-basic-tutorial-1-basic-concept/](https://ververica.cn/developers/flink-basic-tutorial-1-basic-concept/) 109 | 110 | ![](./pics/introduction/flink_architecture.jpeg) 111 | 112 | ### 1、有界和无界数据流 113 | 114 | `Flink` 具有统一的框架处理有界和无界两种数据流的能力(流处理是无界的,批处理是有界的,给无界的流处理加上窗口 `Window` 就相当于有界的批处理,由于 `API` 是一致的,所以算子写完后可以进行复用) 115 | 116 | 117 | ### 2、部署灵活 118 | 119 | `Flink` 底层支持多种资源调度器,包括 `Yarn`、`Kubernetes` 等。`Flink` 自身带的 `Standalone` 的调度器,在部署上也十分灵活。(`Standalone` 也是我们本地开发常用的模式) 120 | 121 | --- 122 | ### 3、极高的可伸缩性 123 | 124 | 可伸缩性对于分布式系统十分重要,资源不够可以动态添加节点,分摊压力,资源充足,撤下服务器,减少资源浪费。介绍中说到:阿里巴巴双 11 大屏采用 `Flink` 处理海量数据,使用过程中测得峰值可达 17 亿/秒。 125 | 126 | 127 | ### 4、极致的流式处理性能 128 | 129 | `Flink` 相对于 `Storm` 最大的特地就是将状态语义完全抽象到框架后只能怪,支持本地状态读取,避免了大量网络 `IO`,可以极大提升状态存储的性能。 130 | 131 | --- 132 | # 特性和优点 133 | 134 | 上面是对于 `Flink` 的定义以及架构上的介绍,下面来看下关于它更具体一点的信息,**在官网中,分为了【架构】、【应用】和【运维】三个方面来介绍**。 135 | 136 | 这里不会太深入分析,主要简单介绍它的特性和优点,有个大致的了解,由浅入深,在之后的文章中慢慢加深学习~ 137 | 138 | 139 | ## 处理流程 140 | 141 | `Flink` 程序的基本构建块是流和转换。 (请注意,`Flink` 的 `DataSet API `中使用的 `DataSet `也是内部流)从概念上讲,流是数据记录流(可能永无止境),而转换是将一个或多个流作为一个操作的操作。一个输入,可以产生一个(例如 `map`)或多个输出流(例如 `flatMap`)。 142 | 143 | ![](./pics/introduction/flink_streaming_dataflow.svg) 144 | 145 | 上图是数据处理流程,可以看到有几个核心组件: 146 | 147 | ### 1、数据源 Source 148 | 自带的 `api` 中,可以读取数据如下:集合数据(fromCollection)、文件数据(readFile)、网络套接字(socket)以及更多扩展来源(addSource),更多扩展中通过自定义来实现 `RichSourceFuncation`,实现读取更多来源的数据。 149 | 150 | 像图中获取的数据源是 `Kafka`,与其它中间件整合中,也封装了很多方便的方法,调用它们可以更方便获取数据源的数据。 151 | 152 | 153 | ### 2、转换 Transaction 154 | 155 | 进行数据的转化,对应于文档中的算子 `Operator`。常见的数据操作有以下:`map`、`flatMap`、`filter`、`keyBy`、`reduce`、`fold`(在 1.9 中看到被标注为 `deprecated`)、`aggregate`、`window`等常用操作。 156 | 157 | 同时从上图也能看出,转换的操作可以不止一次,多个算子可以形成 `chain` 链式调用,然后发挥作用。 158 | 159 | 160 | ### 3、存储 Sink 161 | 162 | 进行数据的存储或发送,对应于文档中的 `connector`(既可以连接数据源,也能发送到某个地方存储起来)。 163 | 164 | 常用的存储 `sink` 有 `Kafka`、`Apache Cassandra`、`Elasticsearch`、`RabbitMQ`、`Hadoop` 等。与前面一样,可以通过扩展 `RichSinkFunction` 进行自定义存储的逻辑。 165 | 166 | 167 | ## 性能比较 168 | 169 | 例如 `Hadoop`、`Storm` 或 `Spark`,与这些优秀的前辈们进行比较,对比性能的高低,如果选择使用 `Flink`,必须得比以前的开发方便和性能好。 170 | 171 | 由于之前没有使用过这些大数据框架,所以测评数据可以参考了这两篇: 172 | 173 | - [Flink实时计算性能分析 https://ververica.cn/developers/shishijisuan/](https://ververica.cn/developers/shishijisuan/) 174 | - [流计算框架 Flink 与 Storm 的性能对比 https://ververica.cn/developers/stream-computing-framework/](https://ververica.cn/developers/stream-computing-framework/) 175 | 176 | 下面简单列出它俩的**吞吐量和作业延迟的比较** 177 | 178 | ![](./pics/introduction/flink_storm_throughput_contrast.png) 179 | 180 | 上图的数据源是 `Kafka Source`,蓝色是 `Storm`,橙色是 `Flink`,在一个分区 `partition` 情况下,`Flink` 吞吐约为 `Storm` 的 3.2 倍;而在 8 个分区情况下,性能提高到 4.6 倍。 181 | 182 | 183 | ![](./pics/introduction/flink_strom_delayed_contrast.png) 184 | 185 | 上图采用的的 `outTime-eventTime` 作为延迟,可以看出,`Flink` 的延迟还是比 `Storm` 的要低。 186 | 187 | 188 | ## 管理方式 JobManager、TaskWorker 189 | 190 | ![](./pics/introduction/flink_jobManager_worker.jpg) 191 | 192 | 上面是官方示意图,阐述了 `Flink` 提交作业的流程,应用程序 `Flink Program`、`JobManage` 和 `TaskManager` 之间的关系。 193 | 194 | ![](./pics/introduction/flink_distract_architecture.png) 195 | 196 | 上面是我对它的理解,个人觉得 `zhisheng` 大佬写的更加详细,可以参考这篇文章:[http://www.54tianzhisheng.cn/2018/10/13/flink-introduction/](http://www.54tianzhisheng.cn/2018/10/13/flink-introduction/) 197 | 198 | 199 | ## 高可用 HA、状态恢复 200 | 201 | `High Availablity` 是个老生常谈的话题了,服务难免会遇到无法预测的意外,如何在出现异常情况下并尽快恢复,继续处理之前的数据,保证一致性,这是个考量服务稳定性的标准。 202 | 203 | `Flink` 提供了丰富的状态访问(例如有 `List`、`Map`、`Aggregate` 等数据类型),以及高效的容错机制,存储状态 `State`,然后通过存储了状态的 `Checkpoint` 和 `Savepoint` 来帮助应用进行快速恢复。 204 | 205 | ![](./pics/introduction/flink_state_checkpoint_introducation.png) 206 | 207 | 详细请参考这两篇: 208 | 209 | - [Apache Flink 零基础入门(七):状态管理及容错机制 https://ververica.cn/developers/state-management/](https://ververica.cn/developers/state-management/) 210 | - [Apache Flink状态管理和容错机制介绍 https://ververica.cn/developers/introduction-to-state-management-and-fault-tolerance/](https://ververica.cn/developers/introduction-to-state-management-and-fault-tolerance/) 211 | 212 | 213 | ## 社区生态 214 | 215 | **真的是十分敬仰发明优秀框架的团队,也十分敬佩每一个为技术做贡献的参与者,所以每次找到相关的资料都跟发现宝藏一样。** 216 | 217 | 下面罗列一下目前找到的资料: 218 | 219 | - `Flink` 官网:[https://ci.apache.org/projects/flink/flink-docs-release-1.9/](https://ci.apache.org/projects/flink/flink-docs-release-1.9/) 220 | - `Vererica` 国内牛人的分享:[https://ververica.cn](https://ververica.cn) 221 | - `Github 项目` 可以关注一下提的问题和阿里分支 `Blink`:[https://github.com/apache/flink](https://github.com/apache/flink) 222 | - `zhisheng` 个人学习的流程是跟着他的文章走了一遍,然后遇到不懂的继续深入学习和了解:[http://www.54tianzhisheng.cn/tags/Flink/](http://www.54tianzhisheng.cn/tags/Flink/) 223 | - `wuchong` 这位大佬是从 16 年就开始研究 `Flink`,写的文章很有深度,想要详细了解 `Flink`,一定要看它的文章!:[http://wuchong.me/categories/Flink/](http://wuchong.me/categories/Flink/) 224 | 225 | **从上面的资料可以看出,`Flink` 的社区慢慢从小众走向大众,越来越多人参与。** 226 | 227 | --- 228 | ## 基础知识点 229 | 230 | 运行环境如下: 231 | 232 | > OS : Mac 233 | > 234 | > Flink Version : 1.9 235 | > 236 | > IDE : IDEA 237 | > 238 | > Java Version : 1.8 239 | 240 | ![](./pics/Flink_study_diagram.png) 241 | 242 | 上图是我在学习过程中整理的一些知识点,之后将会根据罗列的知识点慢慢进行梳理和记录~ 243 | 244 | 245 | # 总结:未来的计算方式 246 | 从调研的结果中能看出,无论从性能、接口编程和容错上,`Flink` 都是一个不错的计算引擎。 `github` 拥有 1w 多个 `star`,这么多人支持以及阿里巴巴的大力推广,还有在 2019.09 参加的云栖大会,演讲嘉宾对 `Flink` 的展望 247 | 248 | ![](./pics/introduction/alibaba_flink_preview.jpeg) 249 | 250 | - **`Apache Flink` 已经是非常优秀和成熟的流计算引擎** 251 | - **`Apache Flink` 已经成为优秀的批处理引擎的挑战者** 252 | - **继续挖掘 `Apache Flink` 在 `OLAP` 数据分析领域的潜力,使其成为优秀的数据分析引擎** 253 | 254 | 直觉相信,`Flink` 的发展前景不错,希望接下来与大家分享和更好的去学习它~ 255 | 256 | --- 257 | # 参考资料 258 | 259 | 1. [Flink 从 0 到 1 学习 —— Apache Flink 介绍](http://www.54tianzhisheng.cn/2018/10/13/flink-introduction/) 260 | 2. [Apache Flink 是什么?](https://flink.apache.org/zh/flink-architecture.html) 261 | 3. [Apache Flink 零基础入门(一&二):基础概念解析](https://ververica.cn/developers/flink-basic-tutorial-1-basic-concept/) 262 | 4. [为什么说流处理即未来?](https://ververica.cn/developers/stream-processing-is-the-future/) 263 | 5. [Apache Flink 零基础入门(七):状态管理及容错机制](https://ververica.cn/developers/state-management/) 264 | 6. [Apache Flink状态管理和容错机制介绍](https://ververica.cn/developers/introduction-to-state-management-and-fault-tolerance/) 265 | -------------------------------------------------------------------------------- /note/2019-10-13-flink_learn_hello_world.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [1 环境准备](#1-%E7%8E%AF%E5%A2%83%E5%87%86%E5%A4%87) 6 | - [1.1 安装 Flink](#11-%E5%AE%89%E8%A3%85-flink) 7 | - [1.2 创建项目](#12-%E5%88%9B%E5%BB%BA%E9%A1%B9%E7%9B%AE) 8 | - [2 开始项目](#2-%E5%BC%80%E5%A7%8B%E9%A1%B9%E7%9B%AE) 9 | - [2.1 项目代码](#21-%E9%A1%B9%E7%9B%AE%E4%BB%A3%E7%A0%81) 10 | - [2.2 开启 tcp 长链接](#22-%E5%BC%80%E5%90%AF-tcp-%E9%95%BF%E9%93%BE%E6%8E%A5) 11 | - [2.3 启动 Flink 程序](#23-%E5%90%AF%E5%8A%A8-flink-%E7%A8%8B%E5%BA%8F) 12 | - [2.3.1 本地调试](#231-%E6%9C%AC%E5%9C%B0%E8%B0%83%E8%AF%95) 13 | - [2.3.2 提交到 JobManager](#232-%E6%8F%90%E4%BA%A4%E5%88%B0-jobmanager) 14 | - [2.4 输入数据 & 验证结果](#24-%E8%BE%93%E5%85%A5%E6%95%B0%E6%8D%AE--%E9%AA%8C%E8%AF%81%E7%BB%93%E6%9E%9C) 15 | - [3 扩展阅读](#3-%E6%89%A9%E5%B1%95%E9%98%85%E8%AF%BB) 16 | - [4 总结](#4-%E6%80%BB%E7%BB%93) 17 | - [5 项目地址](#5-%E9%A1%B9%E7%9B%AE%E5%9C%B0%E5%9D%80) 18 | - [参考资料](#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99) 19 | 20 | 21 | 22 | 23 | 24 | 在学习技术时,总会有一个简单程序 `Demo` 带着我们入门,所以参考着官网例子,带大家快速熟悉 `Flink` 的 `Hello World`~ 25 | 26 | 说明一下,项目运行的环境如下: 27 | 28 | > OS : Mac 29 | > 30 | > Flink Version : 1.9 31 | > 32 | > IDE : IDEA 33 | > 34 | > Java Version : 1.8 35 | 36 | 下面来讲下关于环境准备,如果是 `Windows` 的用户,请参照每个步骤,找到适应自己的安装 `or` 启动方法。 37 | 38 | 39 | 40 | 41 | # 1 环境准备 42 | 43 | **首先我们默认已经安装了 `Jdk 1.8` 和编码工具 `IDEA`,下面来讲如何安装 `Flink` 和建立脚手架。下面展示的项目代码已经放入了 [Github](https://github.com/Vip-Augus/flink-learning-note),可以下载进行本地运行** 44 | 45 | 46 | ## 1.1 安装 Flink 47 | 48 | ```sh 49 | $ brew install apache-flink 50 | ``` 51 | 52 | 检查安装是否成功以及版本号 53 | 54 | ```sh 55 | $ flink --version 56 | Version: 1.9.0, Commit ID: 9c32ed9 57 | ``` 58 | 59 | 接着以单机集群模式启动 `Flink` 60 | 61 | ```sh 62 | $ sh /usr/local/Cellar/apache-flink/1.9.0/libexec/bin/start-cluster.sh 63 | Starting cluster. 64 | Starting standalonesession daemon on host yejingqideMBP-c510. 65 | Starting taskexecutor daemon on host yejingqideMBP-c510. 66 | ``` 67 | 68 | 然后访问 `localhost:8081` 监控界面(1.9 版本更新了 UI): 69 | 70 | ![](./pics/helloworld/flink_dashboard.png) 71 | 72 | 73 | ## 1.2 创建项目 74 | 75 | 这里推荐的是使用 `maven` 进行构建,在命令行中输入如下内容(# 号后面是说明,请不要输入): 76 | 77 | ```sh 78 | $ mvn archetype:generate \ 79 | -DarchetypeGroupId=org.apache.flink \ # flink 的 group.id 80 | -DarchetypeArtifactId=flink-quickstart-java \ # flink 的 artifact.id 81 | -DarchetypeVersion=1.9.0 \ # flink 的 version,以上三个请不要修改,按照默认即可 82 | -DgroupId=wiki-edits \ # 项目的 group.id 83 | -DartifactId=wiki-edits \ # 项目的 artifact.id 84 | -Dversion=0.1 \ # 项目的 version.id 85 | -Dpackage=wikiedits \ # 项目的基础包名 86 | -DinteractiveMode=false # 是否需要和用户交互以获得输入,由于上面已经自己写了项目的参数,所以禁用了。反之请删掉 上面项目的配置,将交互模式设为 true 87 | ``` 88 | 89 | 如果按照官方的例子填写,那么你将得到如下的项目结构: 90 | 91 | ```sh 92 | $ tree wiki-edits 93 | wiki-edits/ 94 | ├── pom.xml 95 | └── src 96 | └── main 97 | ├── java 98 | │ └── wikiedits 99 | │ ├── BatchJob.java 100 | │ └── StreamingJob.java 101 | └── resources 102 | └── log4j.properties 103 | ``` 104 | 105 | 如果是自己自定义的,包结构会不一致,但是通过脚手架创立的,`pom` 文件中预置的依赖都将一致,引入了 `Flink` 基础开发相关的 `API`,然后通过 `IDEA` 打开该项目目录,就可以开始我们的 `Hello world`。 106 | 107 | 108 | # 2 开始项目 109 | 110 | 首先交代一下待会的流程,编写程序代码,启动 `netcat` 命令来监听 9000 端口,启动或提交 `Flink` 程序,最后监听日志输出信息。 111 | 112 | 113 | ## 2.1 项目代码 114 | 115 | `Demo` 的代码作用是监听 `netcat` 输入的字符,然后进行聚合操作,最后输出字符统计 116 | 117 | ```java 118 | public class SocketTextStreamWordCount { 119 | 120 | public static void main(String[] args) throws Exception { 121 | String hostName = "127.0.0.1"; 122 | int port = 9000; 123 | // 设置运行环境 124 | StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); 125 | // 获取数据源 126 | DataStreamSource stream = env.socketTextStream(hostName, port); 127 | // 计数 128 | SingleOutputStreamOperator> sum = stream 129 | .flatMap((new LineSplitter())) 130 | .keyBy(0) 131 | .sum(1); 132 | // 输出 133 | sum.print(); 134 | // 提交任务 135 | env.execute("Java Word from SocketTextStream Example"); 136 | } 137 | 138 | public static final class LineSplitter implements FlatMapFunction> { 139 | 140 | @Override 141 | public void flatMap(String s, Collector> collector) throws Exception { 142 | String[] tokens = s.toLowerCase().split("\\W+"); 143 | for (String token : tokens) { 144 | if (token.length() > 0) { 145 | collector.collect(new Tuple2(token, 1)); 146 | } 147 | } 148 | } 149 | } 150 | } 151 | ``` 152 | 153 | ![](./pics/helloworld/flink_helloworld_process.png) 154 | 155 | 简单说明一下,上面出现了 `SocketTextStream` 套接字字符 **数据源(Source)**,接着是 **算子(Operator)**: `FlatMap`(一个输入源,可以输出零个或多个结果)、`KeyBy`(按照某字段或者 tuple 元组中某个下标进行分类) 和 `sum`(跟翻译一样,就是进行聚合汇总) ,最后输出 156 | 157 | 158 | ## 2.2 开启 tcp 长链接 159 | 160 | 为了模拟流数据,我们造的场景是不断往 9000 端口输入字符,`Flink` 程序添加的数据源是 `SocketTextStream` (套接字字符流)。 161 | 162 | 在你的终端中输入以下命令 163 | 164 | ```sh 165 | $ nc -l 9000 166 | ``` 167 | 168 | 有关 `netcat` 命令的用法,请看参考资料第二条,这里的作用就是打开 `TCP` 长链接,监听 9000 端口 169 | 170 | 171 | ## 2.3 启动 Flink 程序 172 | 173 | 刚才第一个步骤中,已经编辑好了程序代码,第二个步骤也已经启动了一个 `TCP` 客户端,启动 `Flink` 程序有两种方法: 174 | 175 | ### 2.3.1 本地调试 176 | 177 | 使用 `IDEA` 的好处很多,代码补全,语法检查和快捷键之类的。我经常使用的调试方法就是添加一个 `psvm` 的 `main` 方法,在里面写执行代码,最后点击绿色的启动按钮~ 178 | 179 | ![](./pics/helloworld/flink_debug_method.png) 180 | 181 | 如果不需要调试,想直接看结果,选择第一个 `Run`,但有时不确定代码执行过程和出错的具体原因,可以通过第二个选项 `Debug` 进行调试。 182 | 183 | 这是本地开发经常使用的方法,进行结果的验证。 184 | 185 | 186 | ### 2.3.2 提交到 JobManager 187 | 188 | 前面我们启动的是单机集群版,启动了一个 `JobManager` 和 `TaskWorker`,打开的 `localhost:8081` 就是 `JobManager` 的监控面板,所以我们要通过下面的方式,将 `Flink` 程序提交到 `JobManager`。 189 | 190 | 这里教一个简单的方法,我们通过 `mvn clean package` 进行打包后,可以在 `IDEA` 集成的终端标签栏下提交我们的程序: 191 | 192 | ![](./pics/helloworld/flink_run_demo.png) 193 | 194 | 由于每个人的绝对路径都不一样,所以我们通过 `IDEA` 的终端,它会自动定位到项目的路径,然后执行时填写相对路径的 `jar` 包名字即可 195 | 196 | ```sh 197 | $ flink run -c cn.sevenyuan.wordcount.SocketTextStreamWordCount target/flink-quick-start-1.0-SNAPSHOT.jar 198 | ``` 199 | 200 | `-c` 参数是指定运行的主程序入口,接着我们去查看监控面板,可以发现任务状态已经处于监控中: 201 | 202 | ![](./pics/helloworld/flink_demo_overview.png) 203 | 204 | 顶部信息讲的是运行程序名字、时间、时间线、配置参数等信息,底下 `Name` 一栏,说明该程序逻辑步骤(读取数据源,进行映射处理,使用 keyBy 和聚合运算,最后输出到【打印 sink】) 205 | 206 | 207 | ## 2.4 输入数据 & 验证结果 208 | 209 | 前面验证了程序正常启动,接下来我们来验证输入和输出 210 | 211 | 先来监听输出,进入 `Flink` 的日志目录,接着通过 `tail` 命令监听任务执行者 `TaskWorkder`(默认会启动一个任务执行者,所以编码为 0) 的日志输出 212 | 213 | ```sh 214 | $ usr/local/Cellar/apache-flink/1.9.0/libexec/log 215 | $ tail -400f flink*-taskexecutor-0*.out 216 | 217 | ``` 218 | 219 | 接着,在 `nc -l 9000` 对应的终端窗口中输入如下数据: 220 | 221 | ```sh 222 | $ nc -l 9000 223 | hello world 224 | test world 225 | test hello 226 | hello my world 227 | ``` 228 | 229 | 最后就能够看到以下输出结果: 230 | 231 | ```sh 232 | (hello,1) 233 | (world,1) 234 | (test,1) 235 | (world,2) 236 | (test,2) 237 | (hello,2) 238 | (hello,3) 239 | (my,1) 240 | (world,3) 241 | ``` 242 | 243 | 每行字符以空格进行分割,然后分别进行汇总统计,得到的输出结果一致。 244 | 245 | 246 | # 3 扩展阅读 247 | 248 | 如果你在官网阅览,应该也曾看到过 `TimeWindow` 时间窗口的例子,下面是 `Demo` 代码 249 | 250 | ```java 251 | public class SocketWindowWordCount { 252 | 253 | public static void main(String[] args) throws Exception { 254 | 255 | // the port to connect to 256 | String hostName = "127.0.0.1"; 257 | int port = 9000; 258 | 259 | // get the execution environment 260 | final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); 261 | 262 | // get input data by connecting to the socket 263 | DataStream text = env.socketTextStream("localhost", port, "\n"); 264 | 265 | // parse the data, group it, window it, and aggregate the counts 266 | DataStream windowCounts = text 267 | .flatMap(new FlatMapFunction() { 268 | @Override 269 | public void flatMap(String value, Collector out) { 270 | for (String word : value.split("\\s")) { 271 | out.collect(new WordWithCount(word, 1L)); 272 | } 273 | } 274 | }) 275 | .keyBy("word") 276 | .timeWindow(Time.seconds(5), Time.seconds(1)) 277 | .reduce(new ReduceFunction() { 278 | @Override 279 | public WordWithCount reduce(WordWithCount a, WordWithCount b) { 280 | return new WordWithCount(a.getWord(), a.getCount() + b.getCount()); 281 | } 282 | }); 283 | 284 | // print the results with a single thread, rather than in parallel 285 | windowCounts.print().setParallelism(1); 286 | 287 | env.execute("Socket Window WordCount"); 288 | } 289 | } 290 | ``` 291 | 292 | 这里的程序代码核心点在于,**比之前的多了一个算子 `timeWindow`,并且有两个参数,分别是时间窗口大小以及滑动窗口大小(`Time size, Time slide`)**,下面是简单的输入和输出示意图: 293 | 294 | 295 | ![](./pics/helloworld/flink_hello_world_time_winodw.png) 296 | 297 | 由于滑动窗口大小是 1s,窗口是有重合的部分,然后每秒统计自己所在窗口的数据(5s 内传输过来的数据),可以看到第 6s 时,已经舍弃掉第 0s 输入的字符串数据。 298 | 299 | 小伙伴们也可以修改一下时间窗口大小和滑动窗口大小,然后输入自定义的数据,进行不同参数的设置,看下输出效果如何,是否有达到自己的预期。 300 | 301 | 这里先初步接触一下 **时间(Time)和窗口(Window)概念**,之后慢慢接触逐步加深理解吧。 302 | 303 | --- 304 | # 4 总结 305 | 306 | **本文基于 `Mac` 系统、 `Apache Flink 1.9` 版本进行了项目搭建和 `Demo` 编写,介绍了 `Suorce -> Transformation -> Sink` 的流程。简单的实现了一个字符计数器,往套接字数据源 `SocketTextStream`,源源不断的输入,然后进行统计出现的次数,如有疑惑或不对之处请与我讨论~** 307 | 308 | --- 309 | # 5 项目地址 310 | 311 | [https://github.com/Vip-Augus/flink-learning-note](https://github.com/Vip-Augus/flink-learning-note) 312 | 313 | ```sh 314 | git clone https://github.com/Vip-Augus/flink-learning-note 315 | ``` 316 | 317 | --- 318 | # 参考资料 319 | 320 | 1. [DataStream API Tutorial](https://ci.apache.org/projects/flink/flink-docs-release-1.9/getting-started/tutorials/datastream_api.html) 321 | 2. [netcat 命令详解](https://segmentfault.com/a/1190000016626298) 322 | 3. [Flink 从 0 到 1 学习 —— Mac 上搭建 Flink 1.6.0 环境并构建运行简单程序入门](http://www.54tianzhisheng.cn/2018/09/18/flink-install/) -------------------------------------------------------------------------------- /note/2019-10-24-flink_learn_datasource.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [为何要使用 Flink](#%E4%B8%BA%E4%BD%95%E8%A6%81%E4%BD%BF%E7%94%A8-flink) 6 | - [1、前言](#1%E5%89%8D%E8%A8%80) 7 | - [2、DataSource 介绍](#2datasource-%E4%BB%8B%E7%BB%8D) 8 | - [3、集合](#3%E9%9B%86%E5%90%88) 9 | - [4、文件 File](#4%E6%96%87%E4%BB%B6-file) 10 | - [4.1、实现 IMPLEMENTATION:](#41%E5%AE%9E%E7%8E%B0-implementation) 11 | - [4.2、重要笔记 IMPORTANT NOTES:](#42%E9%87%8D%E8%A6%81%E7%AC%94%E8%AE%B0-important-notes) 12 | - [5、套接字 Socket](#5%E5%A5%97%E6%8E%A5%E5%AD%97-socket) 13 | - [6、自定义 DataSource](#6%E8%87%AA%E5%AE%9A%E4%B9%89-datasource) 14 | - [6.1、RichSourceFunction](#61richsourcefunction) 15 | - [6.2、验证自定义数据源结果](#62%E9%AA%8C%E8%AF%81%E8%87%AA%E5%AE%9A%E4%B9%89%E6%95%B0%E6%8D%AE%E6%BA%90%E7%BB%93%E6%9E%9C) 16 | - [7、更多自定义数据源(如 Kafka)](#7%E6%9B%B4%E5%A4%9A%E8%87%AA%E5%AE%9A%E4%B9%89%E6%95%B0%E6%8D%AE%E6%BA%90%E5%A6%82-kafka) 17 | - [7.1、测试场景示意](#71%E6%B5%8B%E8%AF%95%E5%9C%BA%E6%99%AF%E7%A4%BA%E6%84%8F) 18 | - [7.2、前置环境安装和启动](#72%E5%89%8D%E7%BD%AE%E7%8E%AF%E5%A2%83%E5%AE%89%E8%A3%85%E5%92%8C%E5%90%AF%E5%8A%A8) 19 | - [7.3、模拟应用系统](#73%E6%A8%A1%E6%8B%9F%E5%BA%94%E7%94%A8%E7%B3%BB%E7%BB%9F) 20 | - [7.4、启动 Flink 程序](#74%E5%90%AF%E5%8A%A8-flink-%E7%A8%8B%E5%BA%8F) 21 | - [8、总结](#8%E6%80%BB%E7%BB%93) 22 | - [项目地址](#%E9%A1%B9%E7%9B%AE%E5%9C%B0%E5%9D%80) 23 | - [参考资料](#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99) 24 | 25 | 26 | 27 | # 为何要使用 Flink 28 | 29 | 因为本篇文章中,有个 `Kafka` 数据源的 `Demo`,在一开始解答小伙伴有可能的困惑: 30 | 31 | **Question:既然监听 `Kafka` 消息,为何不建立一个简单的消息消费者,使用简单的代码就能进行消息的消费?** 32 | 33 | **Answer:在普通的消费者逻辑中,只能做到对传送过来的一条消息进行单条处理。而在 `Flink` 这个优秀的流计算框架中,能够使用窗口进行多样化处理。提供了窗口处理函数,可以对一段时间(例如 5s 内)或者一批(计数大小,例如 5 个一批)的数据进行计数或者 `reduce` 整合处理** 34 | 35 | **还有 `Flink` 拥有状态管理,能够保存 `checkpoint`,如果程序出现错误,也能够之前的检查点恢复,继续程序的处理,于是拥有这些好处的优秀框架,希望小伙伴也加入进来,一起学习~** 36 | 37 | 38 | 39 | # 1、前言 40 | 41 | 接下来的几篇文章,都会围绕着下面这张图,整体上来说,就是 `DataStreamAPI` 编程的练习: 42 | 43 | ![](./pics/introduction/flink_streaming_dataflow.svg) 44 | 45 | **分别是 `Source`、`Transformation` 和 `Sink` 进行逐一学习。** 46 | 47 | --- 48 | # 2、DataSource 介绍 49 | 50 | 直译:数据来源 51 | 52 | 计算引擎,不管是批出来还是流处理,最重要的是数据来源,根据源源不断的数据进行处理,加工成更有价值的数据。 53 | 54 | **`Flink` 官方包中提供了如下基于集合、文件、套接字等 `API`,然后第三方例如 `Kafka`、`RabbitMq` 等也提供了方便的集成库。** 55 | 56 | 57 | 由于我们测试时,使用的是 `StreamExecutionEnvironment.getExecutionEnvironment()` 来获取流执行环境类进行操作,所以我们来看下这个类的返回类型是 `DataStreamSource` 的方法: 58 | 59 | ![](./pics/datasource/StreamExecutionEnvironment_DataSource.png) 60 | 61 | --- 62 | # 3、集合 63 | 64 | 集合数据源主要有三种:`collection`、`element` 和 `generateSequence`。 65 | 66 | - **fromCollection(Collection)**:接受的参数对象必须是同一类型的集合 67 | - **fromCollection(Iterator`<`OUT`>` data, Class`<`OUT`>` type)**:第一个参数是迭代器,第二个参数是指定返回的类型 68 | - **fromElements(Class`<`OUT`>` type, OUT... data)**:第一个参数是指定返回的类型,后面的是不定数量入参,可以输入多个 `OUT` 类型的对象 69 | - **fromParallelCollection(SplittableIterator`<`OUT`>` iterator, TypeInformation`<`OUT`>` typeInfo, String operatorName)**:**从一个可分离的迭代器中创建并行数据源**。这个方法是 `parallel` 并行数据源的底层调用方法,`typeInfo` 是具体的类型信息,最后一个参数就是操作名字。这个并行数据源并没有测试过,等到之后回来补坑吧。 70 | - **generateSequence(long, long)**:创建一个包含数字序列的新数据流。例如传进去是 1l 和 10l,那么数据源就是 [1-10] 71 | 72 | 测试代码如下: 73 | 74 | > DataSourceFromCollection.java 75 | 76 | ```java 77 | private static DataStreamSource collection1(StreamExecutionEnvironment env) { 78 | List studentList = Lists.newArrayList( 79 | new Student(1, "name1", 23, "address1"), 80 | new Student(2, "name2", 23, "address2"), 81 | new Student(3, "name3", 23, "address3") 82 | ); 83 | return env.fromCollection(studentList); 84 | } 85 | 86 | 87 | private static DataStreamSource collection2(StreamExecutionEnvironment env) { 88 | return env.generateSequence(1, 20); 89 | } 90 | ``` 91 | 92 | --- 93 | # 4、文件 File 94 | 95 | **从官方例子中,罗列了以下三个读取文件的方法,第一个返回的文本类型的数据源,第二个数据源是只读取一次文件,第三个方法参数比较多,文档中关于 `watchType` 观察类型介绍比较多,这里翻译自文档 [Flink DataStream API Programming Guide](https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/datastream_api.html)** 96 | 97 | - **readTextFile(filePath)**:从 `filePath` 读取文本数据源,文本类型是 `TextInputFormat` 以及字符串类型是 `UTF-8`,返回的是文本类型的数据源 98 | - **readFile(fileInputFormat, path)**: 根据指定的文件输入格式读取文件,只读取一次,不随着文本修改重新读取 99 | - **readFile(fileInputFormat, path, watchType, interval, pathFilter, typeInfo)**:这是前两个内部调用的方法。它根据给定的 `fileInputFormat` 读取路径中的文件。 **根据提供的 `watchType` 对数据源做不同的操作,`FileProcessingMode.PROCESS_CONTINUOUSLY` 模式下,会定期(每间隔 ms)监视新数据的路径,`FileProcessingMode.PROCESS_ONCE` 模式下,会一次处理当前路径中的数据并退出。**使用 `pathFilter`,用户可以进一步从处理文件中排除文件。 100 | 101 | --- 102 | ## 4.1、实现 IMPLEMENTATION: 103 | 104 | **在后台,Flink将文件读取过程分为两个子任务,即目录监视和数据读取。 这些子任务中的每一个都是由单独的实体实现的。** 105 | 106 | 监视由单个非并行(并行度= 1)任务实现,而读取由并行运行的多个任务执行。 107 | 108 | 后者的并行性等于作业并行性。 单个监视任务的作用是扫描目录(根据 `watchType` 定期或仅扫描一次),查找要处理的文件,将它们划分为多个拆分,然后将这些拆分分配给下游读取器 (reader)。 109 | 110 | `readers` 是实际读取到数据的角色。每一个分片 `split` 只能由一个 `reader` 读取,但是一个 `reader` 可以读取多个分片 `split`。 111 | 112 | --- 113 | ## 4.2、重要笔记 IMPORTANT NOTES: 114 | 115 | 1. **如果 `watchType` 设置为 `FileProcessingMode.PROCESS_CONTINUOUSLY`,则在修改文件时,将完全重新处理其内容。** 这可能会破坏“完全一次”的语义,因为在文件末尾附加数据将导致重新处理其所有内容。 116 | 2. **如果 `watchType` 设置为 `FileProcessingMode.PROCESS_ONCE`,则源将扫描路径一次并退出,而无需等待读取器完成文件内容的读取。** 当然,读者将继续阅读,直到读取了所有文件内容。 关闭源将导致在该点之后没有更多检查点。 这可能导致节点故障后恢复速度变慢,因为作业将从上一个检查点恢复读取。 117 | 118 | 根据上诉两种情况,个人觉得如果用文件数据作为数据源进行测试,那么使用第二种观察模式 `FileProcessingMode.PROCESS_ONCE`,只扫描一次,避免修改文件后影响之前的计算结果。 119 | 120 | > DataSourceFromFile.java 121 | 122 | ```java 123 | 124 | // 简单的文字文件输入流 125 | DataStreamSource textFileSource = env.readTextFile(filePath); 126 | 127 | // 指定格式和监听类型 128 | Path pa = new Path(filePath); 129 | TextInputFormat inputFormat = new TextInputFormat(pa); 130 | DataStreamSource complexFileSource = 131 | env.readFile(inputFormat, filePath, 132 | FileProcessingMode.PROCESS_CONTINUOUSLY, 133 | 100L, 134 | TypeExtractor.getInputFormatTypes(inputFormat)); 135 | ``` 136 | 137 | --- 138 | # 5、套接字 Socket 139 | 140 | - **socketTextStream**: 从套接字 `socket` 读取。 元素可以由自定义分隔符 `delimiter` 进行分隔。 141 | 142 | > DataSourceFromSocket.java 143 | 144 | ```java 145 | // 监听端口号 146 | DataStreamSource source = env.socketTextStream("localhost", 9000); 147 | 148 | // 定义分隔符 149 | DataStreamSource source = env.socketTextStream("localhost", 9000, "\\W+"); 150 | ``` 151 | 152 | 更具体例子可以参考上一篇 `Hello World` 例子 153 | 154 | --- 155 | # 6、自定义 DataSource 156 | 157 | 从前面介绍中看到,`Flink` 提供了一个 `addSource(SourceFunction)` 的方法,其中 `SourceFunction` 是实现自定义数据源的关键接口,而我们常用来扩展的是它的抽象子类 `RichSourceFunction` 158 | 159 | --- 160 | ## 6.1、RichSourceFunction 161 | 162 | 进行自定义扩展数据源前,来看下这个类的继承体系: 163 | 164 | ![](./pics/datasource/flink_rich_source_function.png) 165 | 166 | 下面是我测试的一个场景: 167 | 168 | 1. 启动 `Redis`,手动不断设置某个 `key` 的值,模拟应用不断对它的修改 169 | 2. `Flink` 读取 `Redis` 数据源,进行数据加工 170 | 3. 存储加工后的数据(例如放入数据库或者简单打印出来) 171 | 172 | 于是乎,创建了一个自定义的 `Redis` 数据源,重写上面图中提到的方法 173 | 174 | > MyRedisDataSourceFunction.java 175 | 176 | ```java 177 | public class MyRedisDataSourceFunction extends RichSourceFunction { 178 | 179 | @Override 180 | public void open(Configuration parameters) throws Exception { 181 | super.open(parameters); 182 | // noop 183 | } 184 | 185 | @Override 186 | public void run(SourceContext ctx) throws Exception { 187 | while (true) { 188 | String maxNumber = RedisUtils.get("maxNumber", String.class); 189 | ctx.collect(StringUtils.isBlank(maxNumber) ? "0" : maxNumber); 190 | // 隔 1 s 执行程序 191 | Thread.sleep(1000); 192 | } 193 | } 194 | 195 | @Override 196 | public void cancel() { 197 | // noop 198 | } 199 | 200 | @Override 201 | public void close() throws Exception { 202 | super.close(); 203 | RedisUtils.close(); 204 | } 205 | } 206 | ``` 207 | 208 | 从上面代码可以看出,我在 `run` 方法中,通过 `while` 循环,不断从 `Redis` 中获取数据,关于缓存的相关操作,封装到了 `RedisUtils`,感兴趣的可以下载项目来看看。 209 | 210 | 由于偷懒,`open` 、`cancel` 是没有做操作,在关闭方法中,也只是简单释放了 `jedis` 连接。 211 | 212 | --- 213 | ## 6.2、验证自定义数据源结果 214 | 215 | > DataSourceFromRedis.java 216 | 217 | ```java 218 | public class DataSourceFromRedis { 219 | 220 | public static void main(String[] args) throws Exception { 221 | StreamExecutionEnvironment env = 222 | StreamExecutionEnvironment.getExecutionEnvironment(); 223 | DataStreamSource customSource = 224 | env.addSource(new MyRedisDataSourceFunction()); 225 | SingleOutputStreamOperator operator = customSource 226 | .map((MapFunction) value -> "当前最大值为 : " + value); 227 | operator.print(); 228 | env.execute("test custom redis datasource function"); 229 | } 230 | } 231 | ``` 232 | 233 | 上面代码,主要核心在于 `env.addSource(new MyRedisDataSourceFunction())`,从我们自定义的 `Redis` 数据源中获取数据,编写好代码后,进行打包并通过 `flink run` 执行。 234 | 235 | 为了方便,我直接在本地 `IDEA` 中,点击了绿色执行按钮,进行本地调试,接着来修改数据源和查看输出结果。 236 | 237 | 一、修改 `Redis` 中的数据 238 | ```sh 239 | $ redis-cli -h localhost -p 6379 240 | > set maxNumber 100 241 | > set maxNumber 200 242 | > set maxNumber 300 243 | > set maxNumber 400 244 | ``` 245 | 246 | 二、查看控制台输出结果 247 | 248 | ```sh 249 | 3> 当前最大值为 : 100 250 | 4> 当前最大值为 : 100 251 | 6> 当前最大值为 : 200 252 | 7> 当前最大值为 : 200 253 | 1> 当前最大值为 : 200 254 | 2> 当前最大值为 : 300 255 | .... 256 | ``` 257 | 258 | **可以看到数据源的修改,我们的程序能够正常接收到并进行处理**。当然这个 `Demo` 只是用来演示,用来演示我们可以基于变动的数据源进行更多复杂的操作,从而来达到数据处理想要的目的。 259 | 260 | --- 261 | # 7、更多自定义数据源(如 Kafka) 262 | 263 | 例如在收集日志时,`Kafka` 消息中间件用得比较多,可以通过官方集成的方法 `new FlinkKafkaConsumer` 进行添加 `Kafka` 数据源 264 | 265 | 266 | 测试类位置在: 267 | 268 | > cn.sevenyuan.datasource.custom.DataSourceFromKafka 269 | 270 | ```java 271 | DataStreamSource dataStreamSource = env.addSource( 272 | new FlinkKafkaConsumer( 273 | KafkaUtils.TOPIC_STUDENT, 274 | new SimpleStringSchema(), 275 | props 276 | )).setParallelism(1); 277 | ``` 278 | 279 | ## 7.1、测试场景示意 280 | 281 | ![](./pics/datasource/flink_datasource_kafka.png) 282 | 283 | **测试场景如上图,模拟一个 `A` 应用系统,不断的往 `Kafka` 发送消息,接着我们的 `Flink` 监听到 `Kafka` 的数据变动,搜集在一个时间窗口内(例如 10s)的数据,对窗口内的数据进行转换操作,最后进行存储(简单演示,使用的是 `Print` 打印)** 284 | 285 | 286 | ## 7.2、前置环境安装和启动 287 | 288 | 如果在本地测试 `Kafka` 数据源,需要做这三步前置操作: 289 | 290 | **1. 安装 `ZooKeeper`**,启动命令: 291 | `zkServer start` 292 | 293 | **2. 安装 `Kafka`**,启动命令: 294 | `kafka-server-start /usr/local/etc/kafka/server.properties` 295 | 296 | **3. 安装 `Flink`**,启动单机集群版的命令 297 | `/usr/local/Cellar/apache-flink/1.9.0/libexec/bin/start-cluster.sh` 298 | 299 | ## 7.3、模拟应用系统 300 | 301 | 在终端中,通过 `Kafka` 命令创建名字为 `student` 的 `Topic`: 302 | 303 | ```sh 304 | $kafka-topics --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic student 305 | ``` 306 | 307 | 启动以下代码的 `main` 方法,通过 `while` 循环,每隔 3s 往 `kafka` 发送一条消息: 308 | 309 | > KafkaUtils.java 310 | 311 | ```java 312 | public class KafkaUtils { 313 | 314 | public static final String BROKER_LIST = "localhost:9092"; 315 | public static final String TOPIC_STUDENT = "student"; 316 | public static final String KEY_SERIALIZER = "org.apache.kafka.common.serialization.StringSerializer"; 317 | public static final String VALUE_SERIALIZER = "org.apache.kafka.common.serialization.StringSerializer"; 318 | public static void writeToKafka() throws Exception { 319 | Properties props = new Properties(); 320 | props.put("bootstrap.servers", BROKER_LIST); 321 | props.put("key.serializer", KEY_SERIALIZER); 322 | props.put("value.serializer", VALUE_SERIALIZER); 323 | KafkaProducer producer = new KafkaProducer<>(props); 324 | // 制造传递的对象 325 | int randomInt = RandomUtils.nextInt(1, 100); 326 | Student stu = new Student(randomInt, "name" + randomInt, randomInt, "=-="); 327 | stu.setCheckInTime(new Date()); 328 | // 发送数据 329 | ProducerRecord record = new ProducerRecord<>(TOPIC_STUDENT, null, null, JSON.toJSONString(stu)); 330 | producer.send(record); 331 | System.out.println("kafka 已发送消息 : " + JSON.toJSONString(stu)); 332 | producer.flush(); 333 | } 334 | public static void main(String[] args) throws Exception { 335 | while (true) { 336 | Thread.sleep(3000); 337 | writeToKafka(); 338 | } 339 | } 340 | } 341 | ``` 342 | 343 | 在该工具类中,设定了很多静态变量,例如主题名字、`key` 序列化类、`value` 的序列化类,之后可以在其它类中进行复用。 344 | 345 | 点击 `main` 方法后,可以在控制台终端看到每隔三秒(`checkInTime` 间隔时间),我们的消息成功的发送出去了。 346 | 347 | ```sh 348 | kafka 已发送消息 : {"address":"=-=","age":49,"checkInTime":1571845900050,"id":49,"name":"name49"} 349 | kafka 已发送消息 : {"address":"=-=","age":92,"checkInTime":1571845903371,"id":92,"name":"name92"} 350 | kafka 已发送消息 : {"address":"=-=","age":72,"checkInTime":1571845906391,"id":72,"name":"name72"} 351 | kafka 已发送消息 : {"address":"=-=","age":19,"checkInTime":1571845909413,"id":19,"name":"name19"} 352 | kafka 已发送消息 : {"address":"=-=","age":34,"checkInTime":1571845912435,"id":34,"name":"name34"} 353 | ``` 354 | 355 | ## 7.4、启动 Flink 程序 356 | 357 | > DataSourceFromKafka.java 358 | 359 | ```java 360 | public static void main(String[] args) throws Exception { 361 | StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); 362 | // 省略 kafka 的参数配置,具体请看代码 363 | Properties props = new Properties(); 364 | DataStreamSource dataStreamSource = env.addSource(new FlinkKafkaConsumer( 365 | KafkaUtils.TOPIC_STUDENT, 366 | new SimpleStringSchema(), 367 | props 368 | )).setParallelism(1); 369 | 370 | // 数据转换 & 打印 371 | // 从 kafka 读数据,然后进行 map 映射转换 372 | DataStream dataStream = dataStreamSource.map(value -> JSONObject.parseObject(value, Student.class)); 373 | // 不需要 keyBy 分类,所以使用 windowAll,每 10s 统计接收到的数据,批量插入到数据库中 374 | dataStream 375 | .timeWindowAll(Time.seconds(10)) 376 | .apply(new AllWindowFunction, TimeWindow>() { 377 | @Override 378 | public void apply(TimeWindow window, Iterable values, Collector> out) throws Exception { 379 | List students = Lists.newArrayList(values); 380 | if (students.size() > 0) { 381 | System.out.println("最近 10 秒汇集到 " + students.size() + " 条数据"); 382 | out.collect(students); 383 | } 384 | } 385 | }) 386 | .print(); 387 | env.execute("test custom kafka datasource"); 388 | } 389 | ``` 390 | 391 | **上述代码主要有三个步骤,获取 `Kafka` 数据源 —> 数据转换(通过 `map` 映射操作,时间窗口搜集数据) —> 最后的数据存储(简单的 `print`)。** 392 | 393 | 点击执行代码后,我们就能在控制台中看到如下输出结果: 394 | 395 | ![](./pics/datasource/flink_datasource_kafka_result.png) 396 | 397 | 可以看到,按照发送消息的速度,我们能够在 10s 内搜集到 3-4 条数据,从输出结果能够验证 `Flink` 程序的正确性。 398 | 399 | 安装操作请参考网上资源,更多详细添加 `Kafka` 数据源的操作可以看**项目中的测试类 `DataSourceFromKafka`和 `zhisheng` 写的 [Flink 从 0 到 1 学习 —— 如何自定义 Data Source ](http://www.54tianzhisheng.cn/2018/10/30/flink-create-source/)** 400 | 401 | 402 | 403 | --- 404 | # 8、总结 405 | 406 | 407 | 本章总结大致可以用下面这张思维导图概括: 408 | 409 | ![](./pics/datasource/flink_datasource_mind.png) 410 | 411 | - 集合、文件:读取本地数据,比较适合在本地测试时使用 412 | - 套接字:监听主机地址和端口号,获取数据,比较少用 413 | - 自定义数据源:一般常用的数据源,例如 `Kafka`、`Hive` 和 `RabbitMQ`,官方都有集成的依赖,通过 `POM` 进行引用即可使用,还有想要自己扩展的话,通过继承 `RichSourceFunction`,重写里面的方法,就能够获取自定义的数据 414 | 415 | **本文主要写了 `Flink` 提供的数据源使用,介绍了集合、文件、套接字和自定义数据源的例子。当然请根据自己的用途,选择使用合适的数据源,如有疑惑或不对之处请与我讨论~** 416 | 417 | --- 418 | # 项目地址 419 | 420 | [https://github.com/Vip-Augus/flink-learning-note](https://github.com/Vip-Augus/flink-learning-note) 421 | 422 | ```sh 423 | git clone https://github.com/Vip-Augus/flink-learning-note 424 | ``` 425 | 426 | --- 427 | # 参考资料 428 | 429 | 1. [Flink 从 0 到 1 学习 —— Data Source 介绍](http://www.54tianzhisheng.cn/2018/10/28/flink-sources/) 430 | 2. [流式连接器](https://ci.apache.org/projects/flink/flink-docs-release-1.9/zh/dev/connectors/) 431 | 3. [Flink DataStream API Programming Guide](https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/datastream_api.html) 432 | 4. [Flink 从 0 到 1 学习 —— 如何自定义 Data Source ](http://www.54tianzhisheng.cn/2018/10/30/flink-create-source/) 433 | 5. [Flink实践-读取kafka](https://chloy.com/2017/06/09/Flink%E5%AE%9E%E8%B7%B5-%E8%AF%BB%E5%8F%96kafka/) -------------------------------------------------------------------------------- /note/2019-11-12-flink_learn_transformation.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [前言](#%E5%89%8D%E8%A8%80) 6 | - [转换 Transformation](#%E8%BD%AC%E6%8D%A2-transformation) 7 | - [一、Map,DataStream ---> DataStream](#%E4%B8%80mapdatastream-----datastream) 8 | - [二、FlatMap,DataStream ---> DataStream](#%E4%BA%8Cflatmapdatastream-----datastream) 9 | - [三、Filter,DataStream ---> DataStream](#%E4%B8%89filterdatastream-----datastream) 10 | - [四、KeyBy,DataStream ---> KeyedStream](#%E5%9B%9Bkeybydatastream-----keyedstream) 11 | - [五、 Reduce,KeyedStream ---> DataStream](#%E4%BA%94-reducekeyedstream-----datastream) 12 | - [六、Fold,KeyedStream ---> DataStream](#%E5%85%ADfoldkeyedstream-----datastream) 13 | - [七、Aggregations,KeyedStream ---> DataStream](#%E4%B8%83aggregationskeyedstream-----datastream) 14 | - [1. 测试文件](#1-%E6%B5%8B%E8%AF%95%E6%96%87%E4%BB%B6) 15 | - [2. 测试代码](#2-%E6%B5%8B%E8%AF%95%E4%BB%A3%E7%A0%81) 16 | - [3. 查看结果](#3-%E6%9F%A5%E7%9C%8B%E7%BB%93%E6%9E%9C) 17 | - [八、Window,KeyedStream ---> WindowedStream](#%E5%85%ABwindowkeyedstream-----windowedstream) 18 | - [九、WindowAll,DataStream ---> AllWindowedStream](#%E4%B9%9Dwindowalldatastream-----allwindowedstream) 19 | - [十、Window Reduce、Apply、Aggregations,WindowedStream ---> DataStream](#%E5%8D%81window-reduceapplyaggregationswindowedstream-----datastream) 20 | - [十一、Union,DataStream * ---> DataStream](#%E5%8D%81%E4%B8%80uniondatastream------datastream) 21 | - [十二、Window Join, DataStream,DataStream ---> DataStream](#%E5%8D%81%E4%BA%8Cwindow-join-datastreamdatastream-----datastream) 22 | - [十三、Window coGroup,DataStream,DataStream ---> DataStream](#%E5%8D%81%E4%B8%89window-cogroupdatastreamdatastream-----datastream) 23 | - [十四、Connect,DataStream,DataStream ---> ConnectedStreams](#%E5%8D%81%E5%9B%9Bconnectdatastreamdatastream-----connectedstreams) 24 | - [十五、Split,DataStream ---> SplitStream](#%E5%8D%81%E4%BA%94splitdatastream-----splitstream) 25 | - [十六、Select,SplitStream ---> DataStream](#%E5%8D%81%E5%85%ADselectsplitstream-----datastream) 26 | - [十七、Project,DataStream ---> DataStream](#%E5%8D%81%E4%B8%83projectdatastream-----datastream) 27 | - [总结](#%E6%80%BB%E7%BB%93) 28 | - [项目地址](#%E9%A1%B9%E7%9B%AE%E5%9C%B0%E5%9D%80) 29 | - [参考资料](#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99) 30 | 31 | 32 | 33 | 34 | # 前言 35 | 36 | 前面写了如何使用 `Flink` 读取常用的数据源,也简单介绍了如何进行自定义扩展数据源,本篇介绍它的下一步:数据转换 `Transformation`,其中数据处理用到的函数,叫做算子 `Operator`,下面是算子的官方介绍。 37 | 38 | > 算子将一个或多个 `DataStream` 转换为新的 `DataStream`。程序可以将多种转换组合成复杂的数据流拓扑。 39 | 40 | 41 | 42 | 在学习过程中,官网是个不错的入门介绍,格式如下: 43 | 44 | ![](./pics/transformation/flink_transformation_official_desc.png) 45 | 46 | 一共有两列,左边介绍了函数名称,转换前的流类型,以及转换后的流类型,右边进行了方法描述,介绍该算子的概念和作用,然后有个代码段,告诉了如何使用它。 47 | 48 | 但官网的中文介绍不多,有些例子 `demo` 也有点不完善,所以接下来我会将自己理解笔记贴下来,跟大家一起来学习。 49 | 50 | 友情提示,贴出来的代码段可能不完整,可以下载 [github](https://github.com/Vip-Augus/flink-learning-note) 上的代码进行参考。 51 | 52 | # 转换 Transformation 53 | 54 | ## 一、Map,DataStream ---> DataStream 55 | 56 | **映射转换**。输入是一个 `DataStream`,输出也是一个 `DataStream`,属于一对一操作,例如输入是 [1, 3, 5],然后每个数乘以 2,可以通过下面形式实现: 57 | 58 | ```java 59 | dataStream.map((MapFuction) ) value -> value * 2;); 60 | ``` 61 | 62 | 将会输出 [2, 6, 10],也有可能不是如上顺序,默认按照程序处理数据的时间进行输出。 63 | 64 | **后面如果大家发现执行顺序与我展示的不一致,请不用担心,属于正常现象,由于顺序性问题涉及到时间和窗口属性的作用,所以请各位先按照默认情况运行,理解算子的概念和使用,之后再去了解上诉概念。** 65 | 66 | ## 二、FlatMap,DataStream ---> DataStream 67 | 68 | **"平坦"映射(不知道该如何翻译囧)。**不同于上面的单个操作,这是一对多操作,取一个元素并产生零个,一个或多个元素。例如拆分一行字符串,然后输出多个单词: 69 | 70 | ```java 71 | SingleOutputStreamOperator operator = source.flatMap(new FlatMapFunction() { 72 | @Override 73 | public void flatMap(String value, Collector out) throws Exception { 74 | String[] tokens = parseString2Tokens(value); 75 | if (tokens == null) { 76 | return; 77 | } 78 | for (String token : tokens) { 79 | out.collect(token); 80 | } 81 | } 82 | }); 83 | ``` 84 | 85 | 输出多个元素的关键是 `Collector` 这个参数,通过它可以收集到更多元素。 86 | 87 | ## 三、Filter,DataStream ---> DataStream 88 | 89 | **过滤操作**。通过一个 `boolean function` 对元素进行过滤,保留为 `true` 的元素,从而达到过滤的目的。例如下面过滤操作,保留 `id` 是偶数的元素: 90 | 91 | ```java 92 | SingleOutputStreamOperator operator = source.filter((FilterFunction) value -> { 93 | Student stu = parseTokens2Object(parseString2Tokens(value)); 94 | return stu != null && stu.getId() % 2 == 0; 95 | }); 96 | ``` 97 | 98 | ## 四、KeyBy,DataStream ---> KeyedStream 99 | 100 | **按键 `key` 进行分类**。`KeyBy` 通过 `Hash partitioning` 方法将一个 `stream` 变成一组不想交的分区 `partitions`,每个 `partitions` 包含的元素具有相同的 `key`。例如输入的是一组字符串,根据第一个字段的整数值进行分类: 101 | 102 | ```java 103 | KeyedStream keyedStream = source.keyBy((KeySelector) value -> { 104 | String[] tokens = parseString2Tokens(value); 105 | return tokens == null ? 0 : Integer.valueOf(tokens[0]); 106 | }); 107 | ``` 108 | 109 | **注意,`KeyBy` 操作后,流类型从 `DataStream` 变成了 `keyedStream`,是一组 `partitions`。** 110 | 111 | 上面介绍的是通用类型的 `keySelector` 分类方法,也可以通过下述两种方法进行分区: 112 | 113 | ```java 114 | keyedStream.keyBy(0); // key 是 tuple 中的第一个元素 115 | keyedStream.keyBy("id"); // key 是 pojo 中的 id 字段,实现了 getter、setter 116 | ``` 117 | 118 | ## 五、 Reduce,KeyedStream ---> DataStream 119 | 120 | **对按 `key` 分类的数据流进行"滚动"压缩**。可以将当前元素与前一个元素进行整合处理,并返回一个新值。 121 | 122 | 例如下面例子,通过 `id` 字段进行分类,然后使用 `reduce` 进行压缩处理,每次将学生的名字字段进行拼接,年龄进行相加,返回一个新的对象: 123 | 124 | ```java 125 | SingleOutputStreamOperator operator = source 126 | .map((MapFunction) value -> parseTokens2Object(parseString2Tokens(value))) 127 | .keyBy((KeySelector) value -> value == null ? 0 : value.getId()) 128 | .reduce((ReduceFunction) (value1, value2) -> { 129 | Student student = new Student(); 130 | student.setId(value1.getId() + value2.getId()); 131 | student.setName(value1.getName() + " || " + value2.getName()); 132 | student.setAge(value1.getAge() + value2.getAge()); 133 | return student; 134 | }); 135 | ``` 136 | 137 | 输出结果如下: 138 | 139 | ```sh 140 | 16> Student(id=2, name=name2, age=22) 141 | 11> Student(id=1, name=name1, age=21) 142 | 15> Student(id=3, name=name13, age=28) 143 | 16> Student(id=5, name=name10, age=25) 144 | 16> Student(id=10, name=name10 || name15, age=55) 145 | 16> Student(id=4, name=name2 || name7, age=44) 146 | 16> Student(id=6, name=name2 || name7 || name12, age=71) 147 | ... 148 | 16> Student(id=15, name=name10 || name15 || name5, age=80) 149 | 1> Student(id=12, name=name4 || name9 || name14, age=77) 150 | 11> Student(id=3, name=name1 || name6 || name11, age=68) 151 | ``` 152 | 153 | 从结果可以看到,`id` 相同的都分到同一个分区(测试中可以简单通过前面的线程 `id` 确认,属于同一分区处理),然后传入新对象,按照 `reduce` 的操作进行了处理,返回了拼装之后的对象。 154 | 155 | ## 六、Fold,KeyedStream ---> DataStream 156 | 157 | **滚动折叠**。合并当前元素和上一个被折叠的值,输入值可以与返回值类型不一样。 158 | 159 | 例如数据流是一组数字 [1, 5, 7],想要输出一个拼接后的字符串,可以通过下面进行处理: 160 | 161 | ```java 162 | // 标准格式 163 | keyedStream.fold(${initialValue}, (s1, s2) -> s1 + " || " + s2); 164 | 165 | SingleOutputStreamOperator operator = source 166 | .map((MapFunction) value -> parseTokens2Object(parseString2Tokens(value))) 167 | .keyBy("id") 168 | .fold("strat", new FoldFunction() { 169 | @Override 170 | public String fold(String accumulator, Student value) throws Exception { 171 | return accumulator + " || " + value; 172 | } 173 | }); 174 | ``` 175 | 176 | 上述例子的 `${initialValue}` 初始值是 `start`,然后在方法中通过 ` || ` 分隔符进行拼接,最后输出结果是: 177 | 178 | ```sh 179 | 16> strat || Student(id=2, name=name2, age=22) 180 | 11> strat || Student(id=1, name=name1, age=21) 181 | 15> strat || Student(id=3, name=name13, age=28) 182 | 16> strat || Student(id=2, name=name2, age=22) || Student(id=2, name=name12, age=27) 183 | 1> strat || Student(id=4, name=name9, age=24) 184 | 11> strat || Student(id=1, name=name1, age=21) || Student(id=1, name=name6, age=21) 185 | 1> strat || Student(id=4, name=name9, age=24) || Student(id=4, name=name14, age=29) 186 | 16> strat || Student(id=2, name=name2, age=22) || Student(id=2, name=name12, age=27) || Student(id=2, name=name7, age=22) 187 | ... 188 | ``` 189 | 190 | 从输出结果可以看到,初始值和每次处理的对象进行了拼接,最后返回的是折叠后的对象,不过该方法被标注为 `@Deprecated`,不建议继续使用。 191 | 192 | ## 七、Aggregations,KeyedStream ---> DataStream 193 | 194 | **在按 `key` 分类的数据流上滚动聚合。**要注意的是 `min` 和 `minBy` 之间的区别是 `min` 返回**指定字段**的最小值,而 `minBy` 返回在此字段中具有最小值的**元素,也可以理解成整个对象**(`max` 和 `maxBy` 的机制相同) 195 | 196 | 常用的 `sum` 合计函数就不多说了,下面具体展示的 `min` 和 `minBy` 函数,取当前流中的最小值: 197 | 198 | 测试代码的逻辑是:从一个文本流中读取数据,转换成对象,接着根据 `id` 进行 `keyBy` 分类,接着从每组分区 `partitions` 中取出年龄 `age` 字段最小的,**我们来分别测试 `min` 和 `minBy` 的不同之处**: 199 | 200 | ### 1. 测试文件 201 | 202 | ```sh 203 | # id, name, age, address (代替 index 下标) 204 | ... 205 | 1 name10 21 j 206 | 1 name9 20 k 207 | 1 name8 21 l 208 | 1 name7 21 m 209 | ... 210 | 1 name2 21 r 211 | 1 name1 10 s 212 | ``` 213 | 214 | 为了便于从小样本中观察的具体的区别,`id` 字段都设成 1,它们将会分组到同一个 `partitions`,然后 `age` 字段有三种值(21、20 和 10),每次处理一个元素时,都会输出一个最小值 215 | 216 | ### 2. 测试代码 217 | 218 | ```java 219 | SingleOutputStreamOperator operator = source 220 | .map((MapFunction) value -> { 221 | parseTokens2Object(parseString2Tokens(value)); } 222 | ) 223 | .keyBy("id") 224 | // 这里表示选择使用 minBy 或者 min 函数 225 | .minBy("age")/min("age"); 226 | ``` 227 | 228 | ### 3. 查看结果 229 | 230 | ![](./pics/transformation/flink_transformation_minBy&min_diff.png) 231 | 232 | 左图是 `minBy`,右图是 `min` 输出结果,从结果可以看出,`minBy` 返回多个值的意思是:**返回元素的完整对象**,而 `min` 返回的是字段的最小值,其它字段将不会修改。于是左边能看到不同的 `age` 对应的下标 `address` 是不同的,而右边不同的 `age`,对应的还是第一次输出的 `address` 结果。 233 | 234 | **综上所述,如果使用聚合函数 `minBy` 和 `min`(以及 `max`),注意自己需要返回的类型,是否需要一个完整的对象,如果是的话,请选择使用 `minBy`。** 235 | 236 | ## 八、Window,KeyedStream ---> WindowedStream 237 | 238 | **窗口**。该函数允许在**已分区**的 `KeyedStream` 上定义窗口。例如最近 5 秒内到达的数据: 239 | 240 | ```java 241 | dataStream.keyBy(0).window(TumblingEventTimeWindows.of(Time.seconds(5))); 242 | ``` 243 | 244 | ## 九、WindowAll,DataStream ---> AllWindowedStream 245 | 246 | **在 `DataStream` 上定义窗口**。与上面不同的是,此次定义的范围是**所有流**,`windows` 会根据某些特征(例如,最近 5 秒内到达的数据)对所有流事件进行划分窗口。 247 | 248 | ```java 249 | SingleOutputStreamOperator operator = source 250 | .map((MapFunction) value -> parseTokens2Object(parseString2Tokens(value))) 251 | .assignTimestampsAndWatermarks(new MyTimestampExtractor()) 252 | .windowAll(TumblingEventTimeWindows.of(Time.milliseconds(1))) 253 | .apply(new AllWindowFunction(){...}; 254 | ``` 255 | 256 | 注意:在许多情况下,这是非并行转换,所有记录都将被收集在 `WindowAll` 运算符的一项任务中。 257 | 258 | ## 十、Window Reduce、Apply、Aggregations,WindowedStream ---> DataStream 259 | 260 | 窗口函数跟前面提到的常用函数一样,也有**属于窗口的数据流功能**。 261 | 262 | 例如应用 `apply` 进行自定义逻辑加工,还有 `reduce` 进行“压缩”,返回合并后的值、`sum` 等统计函数。 263 | 264 | **唯一不同的是,前面常规函数对应的整个流中的数据,而 `window` 针对是根据特征(例如 5s 时间内)分开的窗口流的数据进行操作。** 265 | 266 | ```java 267 | windowedStream.apply(new WindowFunction<...>{...}); 268 | windowedStream.sum(0; 269 | windowedStream.min("key"); 270 | windowedStream.maxBy(0); 271 | ``` 272 | 273 | ## 十一、Union,DataStream * ---> DataStream 274 | 275 | **联合**。Union 将两个或多个数据流合并,生成一个新的数据流,包含了本身以及参数中数据流的所有数据。 276 | 277 | ```java 278 | dataStream.union(otherStream1, otherStream2); 279 | ``` 280 | 281 | 如果是一个数据流联合它自己,相当于得到两份同样的数据。 282 | 283 | ## 十二、Window Join, DataStream,DataStream ---> DataStream 284 | 285 | **窗口连接**。通过 **`key`** 将同一个 `window` 的两个数据流 `join` 起来。 286 | 287 | ```java 288 | dataStream.join(otherStram) 289 | .where() 290 | .equalsTo() 291 | .window(TumblingEventTimeWindows.of(Time.seconds(3))) 292 | .apply(new JoinFunction<...>{...}); 293 | ``` 294 | 295 | 上面代码的作用是,在 3s 时间内连接两个窗口,连接条件是 `where` (第一个流)和 `equalsTo` (第二个流)相同的返回值。 296 | 297 | ## 十三、Window coGroup,DataStream,DataStream ---> DataStream 298 | 299 | **窗口连接**。跟上面的作用一样,通过 **`key`** 将同一个 `window` 的两个数据流连接起来。 300 | 301 | ```java 302 | dataStream.coGroup(otherStram) 303 | .where() 304 | .equalsTo() 305 | .window(TumblingEventTimeWindows.of(Time.seconds(3))) 306 | .apply(new CoGroupFunction<...>{...}); 307 | ``` 308 | 309 | 可以看到,除了 `join` 和 `coGroup` 函数不同外,还有一个 `apply` 处理使用的参数类型也不一样。 310 | 311 | ```java 312 | interface JoinFunction { 313 | OUT join(IN1 first, IN2 second); 314 | } 315 | 316 | interface CoGroupFunction { 317 | void coGroup(Iterable first, Iterable second, Collector out); 318 | } 319 | ``` 320 | 321 | 上面展示的是两个接口,在 `join` 算子中使用的是 `JoinFunction`,对应的 `join` 方法是两个窗口流的单个入参;在 `coGroup` 算子中,使用的是 `CoGroupFunction`,对应的 `coGroup` 方法是两个窗口流的迭代器,可以进行更多数据处理。 322 | 323 | 在参考资料四种详细介绍了两个的区别,如有连接操作,为了更多扩展性,推荐使用的是 `CoGroup` 算子。 324 | 325 | ## 十四、Connect,DataStream,DataStream ---> ConnectedStreams 326 | 327 | **数据流连接**。“连接”两个保存其类型的数据流,该连接允许两个流之间的共享状态。 328 | 329 | 例如下面连接了两个数据流,将会转换成 `ConnectedStram` 类型,接着使用 `CoFlatMapFunction` 分别对合并流中两种类型数据流进行处理(也可以自定义一个 `MyCoFlatMapFunction` 类,继承自 `RichCoFlatMapFunction`),在里面实现自己的逻辑 330 | 331 | ```java 332 | ConnectedStreams connectedStreams = source.connect(studentDataStreamSource); 333 | connectedStreams.flatMap(new CoFlatMapFunction() { 334 | @Override 335 | public void flatMap1(String value, Collector out) throws Exception { 336 | // 状态 1 337 | out.collect("Add prefix : " + value); 338 | } 339 | @Override 340 | public void flatMap2(Student value, Collector out) throws Exception { 341 | // 状态 2 342 | if (value.getId() % 2 != 0) { 343 | out.collect(value); 344 | } 345 | } 346 | }); 347 | ``` 348 | 349 | 前面的连接 `Join`/`CoGroup` 数据流类型都必须是一致,而使用 `Connecte` 算子,允许两个数据流的类型不同,根据 `flatMap1`(处理的是字符串) 和 `flatMap2` (处理的是对象)中的 `collecot` 收集,进行输出: 350 | 351 | ```sh 352 | 7> Add prefix : 1 name1 21 a 353 | 7> Add prefix : 2 name2 22 b 354 | 6> Add prefix : 5 name15 30 o 355 | ... 356 | 6> Student(id=1, name=otherName1, age=1, address=, checkInTime=null, successTimeStamp=0) 357 | 8> Student(id=3, name=otherName3, age=3, address=, checkInTime=null, successTimeStamp=0) 358 | ``` 359 | 360 | ## 十五、Split,DataStream ---> SplitStream 361 | 362 | **切分**。从 `DataStream` 转换成 `SplitStream`,类似于字符串中的 `split` 切分方法,`Split` 算子作用就是根据设定的规则,将原来的流切分成多个流。例如根据 `id` 的奇偶数分成两个流: 363 | 364 | ```java 365 | SplitStream splitStream = source.split(new OutputSelector() { 366 | @Override 367 | public Iterable select(String value) { 368 | String[] tokens = parseString2Tokens(value); 369 | int num = Integer.valueOf(tokens[0]); 370 | List output = new ArrayList<>(); 371 | if (num % 2 == 0) { 372 | output.add("even"); 373 | } else { 374 | output.add("odd"); 375 | } 376 | return output; 377 | } 378 | }); 379 | ``` 380 | 381 | ## 十六、Select,SplitStream ---> DataStream 382 | 383 | **选择**。从拆分流中选择一个或多个流。接着上面的例子,如果需要从 `SplitStream` 中分别获取奇偶数的数据流,可以使用该算子: 384 | 385 | ```java 386 | DataStream even = splitStream.select("even"); 387 | DataStream odd = splitStream.select("odd"); 388 | DataStream all = splitStream.select("even","odd"); 389 | ``` 390 | 391 | 在 `Flink` 1.9 版本中,切分流 `SplitStream` 也被标注为不建议使用 `@Deprecated`,出现了更好的选择 [Side Outputs](https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/stream/side_output.html),感兴趣的朋友可以深入看看~ 392 | 393 | ## 十七、Project,DataStream ---> DataStream 394 | 395 | **选择部分字段**。注意,只对元组 Tuple 类型的输入流有效,输出的也是选择下标的新元组数据流。例如下面,选择是下标 1 和 3 的属性: 396 | 397 | ```java 398 | DataStreamSource> customSource = env.fromCollection( 399 | Lists.newArrayList( 400 | Tuple4.of("one", 1, 1L, BigDecimal.ONE), 401 | Tuple4.of("two", 2, 2L, BigDecimal.ZERO), 402 | Tuple4.of("three", 3, 3L, BigDecimal.TEN), 403 | Tuple4.of("four", 4, 4L, BigDecimal.TEN) 404 | ) 405 | ); 406 | // 分离下标 1,3 到新到数据流 407 | DataStream> tuple2DataStreamSource = customSource.project(1, 3); 408 | ``` 409 | 410 | 输出的结果如下: 411 | 412 | ```sh 413 | [1, 1] 414 | [2, 0] 415 | [3, 10] 416 | [4, 10] 417 | ``` 418 | 419 | # 总结 420 | 421 | 本篇主要介绍了不同算子的概念和作用,包括 **`map`/`flatMap`/`filter`/`keyBy`/`reduce`/`fold`/`aggregations`/`window`/`windowAll`/`union`/`join`/`coGroup`/`connect`/`split`/`select`/`project`**, 也指出它们转变前和转变后的数据流类型,其中重点介绍了 `min` 和 `minBy` 之间的区别,希望能够帮助大家在学习时更好理解每个算子的用法,在实际使用时,选择合适的。 422 | 423 | 测试算子功能和代码结构如下图: 424 | 425 | ![](./pics/transformation/flink_transformation_demo_methods.png) 426 | 427 | **将注释去掉就可以进行测试,调整不同的输入源和参数条件,来看看是否符合你的预期吧,如有困惑或不对之处,请与我交流~** 428 | 429 | # 项目地址 430 | 431 | [https://github.com/Vip-Augus/flink-learning-note](https://github.com/Vip-Augus/flink-learning-note) 432 | 433 | ```sh 434 | git clone https://github.com/Vip-Augus/flink-learning-note 435 | ``` 436 | 437 | --- 438 | # 参考资料 439 | 1. [Operators](https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/stream/operators/index.html#operators) 440 | 2. [Flink 从 0 到 1 学习 —— Flink Data transformation(转换)](http://www.54tianzhisheng.cn/2018/11/04/Flink-Data-transformation/) 441 | 3. [Flink编程<一> 概念, Setup](https://www.jianshu.com/p/aa7b30e76a7f) 442 | 4. [flink实战--双流join之Join和coGroup的区别和应用](https://blog.csdn.net/aA518189/article/details/84032660) 443 | 5. [Side Outputs](https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/stream/side_output.html) -------------------------------------------------------------------------------- /note/2019-11-21-flink_learn_datasink.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [1 前言](#1-%E5%89%8D%E8%A8%80) 6 | - [2 为什么要用 DataSink](#2-%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E7%94%A8-datasink) 7 | - [3 DataSink 是什么](#3-datasink-%E6%98%AF%E4%BB%80%E4%B9%88) 8 | - [4 官方支持的连接器 Connector](#4-%E5%AE%98%E6%96%B9%E6%94%AF%E6%8C%81%E7%9A%84%E8%BF%9E%E6%8E%A5%E5%99%A8-connector) 9 | - [5 PrintSinkFunction](#5-printsinkfunction) 10 | - [6 自定义 SinkFunction(存储到 MySQL)](#6-%E8%87%AA%E5%AE%9A%E4%B9%89-sinkfunction%E5%AD%98%E5%82%A8%E5%88%B0-mysql) 11 | - [6.1 Demo 流程图](#61-demo-%E6%B5%81%E7%A8%8B%E5%9B%BE) 12 | - [6.2 Demo 输入输出示意](#62-demo-%E8%BE%93%E5%85%A5%E8%BE%93%E5%87%BA%E7%A4%BA%E6%84%8F) 13 | - [6.3 SinkToMySQL](#63-sinktomysql) 14 | - [6.4 项目结构和验证](#64-%E9%A1%B9%E7%9B%AE%E7%BB%93%E6%9E%84%E5%92%8C%E9%AA%8C%E8%AF%81) 15 | - [7 单次操作和聚合操作](#7-%E5%8D%95%E6%AC%A1%E6%93%8D%E4%BD%9C%E5%92%8C%E8%81%9A%E5%90%88%E6%93%8D%E4%BD%9C) 16 | - [8 总结](#8-%E6%80%BB%E7%BB%93) 17 | - [9 项目地址](#9-%E9%A1%B9%E7%9B%AE%E5%9C%B0%E5%9D%80) 18 | - [10 参考资料](#10-%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99) 19 | 20 | 21 | 22 | 23 | 24 | # 1 前言 25 | 先来回顾一下 `Flink` 基础的三兄弟: 26 | 27 | - 数据来源 `DataSource` 28 | - 数据转换 `Transaformation` 29 | - 数据存储 `DataSink` 30 | 31 | 前面两篇笔记已经写了数据来源和转换如何使用,那么这篇当然就到了数据存储,接下来将会从以下角度介绍一下(喜闻乐见的 `What` / `Why` / `How`)~: 32 | 33 | - 1 为什么要用 `Sink` 34 | - 2 `DataSink` 是什么 35 | - 3 如何使用(进阶使用,滑动时间窗口例子) 36 | 37 | 38 | 39 | # 2 为什么要用 DataSink 40 | 41 | 在处理数据的最后一步,一般要进行验证和之后统计,如果没有将计算结果存储下来,后面的操作也很难展开,**所以结果的存储或者更新就显得很必要**。 42 | 43 | 44 | 45 | # 3 DataSink 是什么 46 | 47 | `Flink` 基础操作与一个处理数据 `Http` 接口的生命周期很像,**接受数据 -> 处理数据 -> 存储数据**,而 `Sink` 在翻译有表示【下沉】的意思,也就是我们经常对处理数据后做的一件事:**存储**。 48 | 49 | 下面来看下 `RickSinkFunction` 类的继承体系图: 50 | 51 | ![](./pics/datasink/sinkfuncaiton_diagram.png) 52 | 53 | **个人觉得跟数据源 `RichSourceFunction` 很像,都继承了 `AbstractRichFunction` 抽象类,实现了 `RichFunction` 中的 `open` 和 `close` 等基础方法。两者的区别在于,数据源 `DataSource` 另外实现的是 `SourceFunction` 接口,而我们本篇文章的主角 `DataSink` 实现的就是 `SinkFunction` 接口。** 54 | 55 | # 4 官方支持的连接器 Connector 56 | 57 | 在流式计算框架 `Flink` 中,可以通过 `Sink` 进行存储操作。官方给出更推荐的说法是**连接器 `Connector`, 第三方中间件作为连接器,既可以当成数据源,也能当成目的地,取决于上面提到实现的接口(`SourceFunction`/`SinkFunction`)** 58 | 59 | **以下是官方支持的连接器,感兴趣的可以点击参考资料三去详细了解~** 60 | 61 | - Apache Kafka (source/sink) 62 | - Apache Cassandra (sink) 63 | - Amazon Kinesis Streams (source/sink) 64 | - Elasticsearch (sink) 65 | - Hadoop FileSystem (sink) 66 | - RabbitMQ (source/sink) 67 | - Apache NiFi (source/sink) 68 | - Twitter Streaming API (source) 69 | - Google PubSub (source/sink) 70 | 71 | 其中结尾的 `source` 表示数据源,`sink` 表示数据的发送地,例如常见的消息中间件 `Apache Kafka`,**它既可以作为数据源,也能成为数据的发送目的地**。 72 | 73 | # 5 PrintSinkFunction 74 | 75 | 在我们平时编码中,常用的验证结果的方式是**将结果输出到控制台**,例如 `IDEA` 的快捷键 `SOUT`,可以很快的将结果输出到底部控制台中。 76 | 77 | 在 `Flink` 的世界中,流式计算因为要一直接收数据进行处理,常用的操作对象是 `DataStream`,它是一个流对象,有特定的打印 `Print` 方法,就是我接下来要介绍的 `PrintSinkFunction`。 78 | 79 | ![](./pics/datasink/sinkfunction_printsinkfunction_diagram.png) 80 | 81 | 从结构图看出,`PrintSinkFunction` 继承自 `RichSinkFunction`,重写了其中两个关键方法 `open` 和 `invoke`,在这两个方法中,实现了输出功能。 82 | 83 | > PrintSinkFunction.java 84 | 85 | ```java 86 | public class PrintSinkFunction extends RichSinkFunction { 87 | 88 | private final PrintSinkOutputWriter writer; 89 | 90 | ... 91 | 92 | @Override 93 | public void open(Configuration parameters) throws Exception { 94 | super.open(parameters); 95 | StreamingRuntimeContext context = (StreamingRuntimeContext) getRuntimeContext(); 96 | writer.open(context.getIndexOfThisSubtask(), context.getNumberOfParallelSubtasks()); 97 | } 98 | 99 | @Override 100 | public void invoke(IN record) { 101 | writer.write(record); 102 | } 103 | 104 | ... 105 | } 106 | ``` 107 | 108 | 上面是它的核心代码,省略了 `writer` 变量的初始化。 109 | 110 | **在 `open` 方法中,获取了运行上下文对象,从中取出当前运行的任务下标以及并发任务数量,传递到了 `writer` 变量中**(所以 `demo` 中经常能看到 【1> xxxx】,前面的数字是一个前缀,实际值是当前任务下标 + 1)。 111 | 112 | **在 `invoke` 方法中,做的工作就比较简单了,就是将流处理传入记录 `record` 进行输出打印(详细输出过程可跟踪进 `PrintSinkOutputWriter` 查看)** 113 | 114 | 官方库中的 `PrintSinkFunction` 在日常开发中常使用,通过控制台输出结果进行验证数据是否跟自己预期的一致。所以先以常用的类进行介绍,可以更快的对 `SinkFunction` 的整体结构有个更清晰的了解。 115 | 116 | # 6 自定义 SinkFunction(存储到 MySQL) 117 | 118 | 除了官方支持的 `Connector` 外,还提供了途径,让我们扩展存储方式,通过 `addSink()` 方法,添加自定义的 `SinkFunction`。 119 | 120 | 前面都是单独介绍数据源 `DataSource` 和转换 `Transformation`,这次终于将三者串了起来,下面来看下完整的流程,通过例子来说下自定义的存储方法如何实现。 121 | 122 | 123 | ## 6.1 Demo 流程图 124 | 125 | 126 | ![](./pics/datasink/flink_demo_flow_chart.png) 127 | 128 | 上面是 `Demo` 的流程图,一共包含三个模块,其中有两个模块(数据源和转换),在前面的文章 [Flink 基础学习(三)数据源 DataSource]() 已经有详细例子,请参考下图两个核心类的代码编写。 129 | 130 | ![](./pics/datasink/sinkfunction_source&job.png) 131 | 132 | **在主程序中,使用了 `map` 算子进行一对一映射,从单条字符消息转换成应用中的实体对象,接着使用 `timeWindowAll` 算子进行数据的时间窗口聚合,时间窗口大小是 10s,在这个时间段中接收的数据都会在一个窗口中。** 133 | 134 | ## 6.2 Demo 输入输出示意 135 | 136 | ![](./pics/datasink/sinkfunction_data_flow.png) 137 | 138 | **上面是程序的输入和输出示意图**,在 `Input` 中,以秒为单位,`TimeWindow` 以 10s 为间隔,将输入的数据放在一个窗口中(在一个窗口中的数据,可以进行聚合 `reduce` 操作,然后进行输出),**最后 `Sink` 到常用的存储地,这里以 `MySQL` 进行数据的落库作为示例~** 139 | 140 | 上面每个窗口搜集的数据如下: 141 | 142 | - 0-10s: [A, C, D, B] 143 | - 10-20s: [G, A] 144 | - 20-30s: [Q, O, Z] 145 | 146 | **最后每次传入 `Sink` 时,是一个数据列表 `List` 型的入参。从上面的示意图来联想我们 `kafka` 消息,搜集 10s 内的消息,然后放入同一个时间窗口中,接着一次性存入到数据库中。** 147 | 148 | ## 6.3 SinkToMySQL 149 | 150 | ```java 151 | public class SinkToMySQL extends RichSinkFunction> { 152 | 153 | private PreparedStatement ps; 154 | 155 | private Connection connection; 156 | 157 | @Override 158 | public void open(Configuration parameters) throws Exception { 159 | super.open(parameters); 160 | connection = MyDruidUtils.getConnection(); 161 | String sql = "insert into student(name, age, address) values (?, ?, ?);"; 162 | ps = connection.prepareStatement(sql); 163 | } 164 | 165 | @Override 166 | public void close() throws Exception { 167 | super.close(); 168 | if (connection != null) { 169 | connection.close(); 170 | } 171 | if (ps != null) { 172 | ps.close(); 173 | } 174 | } 175 | 176 | @Override 177 | public void invoke(List value, Context context) throws Exception { 178 | for (Student student : value) { 179 | ps.setString(1, student.getName()); 180 | ps.setInt(2, student.getAge()); 181 | ps.setString(3, student.getAddress()); 182 | ps.addBatch(); 183 | } 184 | int[] count = ps.executeBatch(); 185 | } 186 | } 187 | ``` 188 | 189 | **上面的类就是自定义的 `Sink` 具体实现, `open` 获取数据库链接和初始化 `SQL`, `close` 时释放链接,每次落库具体操作在 `invoke` 方法中。** 190 | 191 | ## 6.4 项目结构和验证 192 | 193 | 具体的有三个核心类: 194 | 195 | - **模拟数据源:KafkaUtils.java** 196 | - **Flink Job:DataSourceFromKafka.java** 197 | - **自定义 Sink:SinkToMySQL.java** 198 | 199 | 由于有些代码之前文章也贴过了,就不重复展示,如果对于代码有疑惑的,请参考 `demo` 工程 200 | [https://github.com/Vip-Augus/flink-learning-note](https://github.com/Vip-Augus/flink-learning-note) 201 | 202 | 数据库建表语句: 203 | 204 | ```sql 205 | create table test.student 206 | ( 207 | id int auto_increment 208 | primary key, 209 | name varchar(20) null, 210 | age int(3) null, 211 | address varchar(120) null, 212 | create_time timestamp default CURRENT_TIMESTAMP null, 213 | modify_date datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '修改时间' 214 | )charset=utf8; 215 | ``` 216 | 217 | 编写好代码以及数据库创表,**在启动 `KafkaUtils` 和 `DataSourceFromKafka` 两个程序(点击 main 方法就能启动啦)之前**,请一定要记得启动 `Zookeeper` 和 `Kafka`,遗忘的话请回顾一下 [Flink 基础学习(三)数据源 DataSource]() ~ 218 | 219 | 最后我们去数据库验证落库结果: 220 | 221 | ![](./pics/datasink/sinkfunction_verify.png) 222 | 223 | 从数据的创建时间上,10s 的时间窗口操作得到了成功验证。 224 | 225 | # 7 单次操作和聚合操作 226 | 227 | 在 `RichSinkFunction` 类中, `IN` 是一个泛型参数,表示我们传入的参数可以自定义。 228 | 229 | 在前面简单的 `map` 一对一映射后,得到的输出也是单个的,`IN` 是 `Student` 类型;而在 `timedWindowAll` 时间窗口后,输出的一个 List`<`Student`>` 类型。 230 | 231 | 如果是单个对象,每次处理一个对象后都要进行一次落库,也就是每次都得获取一次数据库链接,在这种情况下,如果消息特别多,并发发送了成千上万条消息,数据库很可能就无法承受这么大的 `QPS`。 232 | 233 | **所以推荐使用的是 `List` 类型的批量操作,通过一定规则(时间窗口或者计数窗口)聚合一批数据,然后一次性插入多条记录,减少数据库的频繁操作,尽可能提高数据库的高可用。** 234 | 235 | 当然,如果是进行单次操作,只需要更换一下入参 `IN` 类型以及 `invoke` 方法的实现,然后在 `addSink` 方法前,进行的算子操作也改成单个操作的就能实现**单次操作**。 236 | 237 | # 8 总结 238 | 239 | 本次介绍了 `DataSink` 数据存储的基础概念和如何进行自定义扩展存储方法。比较了单次操作和聚合操作,更推荐使用聚合操作。 240 | 241 | **参考上面的例子,小伙伴们可以更换落库的目的地,通过修改 `invoke` 方法实现,将数据存储到自己业务上更合适的地方~** 242 | 243 | # 9 项目地址 244 | 245 | [https://github.com/Vip-Augus/flink-learning-note](https://github.com/Vip-Augus/flink-learning-note) 246 | 247 | ```sh 248 | git clone https://github.com/Vip-Augus/flink-learning-note 249 | ``` 250 | 251 | # 10 参考资料 252 | 253 | 1. [Flink 从 0 到 1 学习 —— Data Sink 介绍](http://www.54tianzhisheng.cn/2018/10/29/flink-sink/) 254 | 2. [Flink 从 0 到 1 学习 —— 如何自定义 Data Sink ?](http://www.54tianzhisheng.cn/2018/10/31/flink-create-sink/) 255 | 3. [Bundled Connectors](https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/connectors/) 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | -------------------------------------------------------------------------------- /note/2019-11-27-flink_learn_time.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [Time](#time) 6 | - [Processing Time 处理时间](#processing-time-%E5%A4%84%E7%90%86%E6%97%B6%E9%97%B4) 7 | - [Event Time 事件时间](#event-time-%E4%BA%8B%E4%BB%B6%E6%97%B6%E9%97%B4) 8 | - [Ingestion Time 摄取时间](#ingestion-time-%E6%91%84%E5%8F%96%E6%97%B6%E9%97%B4) 9 | - [设置时间属性](#%E8%AE%BE%E7%BD%AE%E6%97%B6%E9%97%B4%E5%B1%9E%E6%80%A7) 10 | - [Event Time and Watermarks](#event-time-and-watermarks) 11 | - [乱序事件场景](#%E4%B9%B1%E5%BA%8F%E4%BA%8B%E4%BB%B6%E5%9C%BA%E6%99%AF) 12 | - [Event Time 和 Watermark 的关系](#event-time-%E5%92%8C-watermark-%E7%9A%84%E5%85%B3%E7%B3%BB) 13 | - [Watermark 图示](#watermark-%E5%9B%BE%E7%A4%BA) 14 | - [并行流中的 Watermark](#%E5%B9%B6%E8%A1%8C%E6%B5%81%E4%B8%AD%E7%9A%84-watermark) 15 | - [延迟的元素](#%E5%BB%B6%E8%BF%9F%E7%9A%84%E5%85%83%E7%B4%A0) 16 | - [Watermark 实际使用例子](#watermark-%E5%AE%9E%E9%99%85%E4%BD%BF%E7%94%A8%E4%BE%8B%E5%AD%90) 17 | - [总结](#%E6%80%BB%E7%BB%93) 18 | - [项目地址](#%E9%A1%B9%E7%9B%AE%E5%9C%B0%E5%9D%80) 19 | - [参考资料](#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99) 20 | 21 | 22 | 23 | 24 | 25 | 前面的例子中有出现过 **时间窗口 `TimeWindow`** 这个词语,其实是两个概念,时间 `Time` 和窗口 `Window`。 26 | 27 | 本篇文章比较干货,主要翻译自官网(参考资料一), 来讲下关于 `Time` 的学习、理解以及配套的概念 `Watermark`。 28 | 29 | **`Watermark` 有两种译法:水位线、水印。由于个人暂时分不清,所以后面一律以英文 `Watermark` 出现** 30 | 31 | 32 | 33 | --- 34 | 35 | # Time 36 | 37 | 一共有三种时间类型:`Processing Time`、`Event Time` 和 `Ingestion Time` 38 | 39 | ![](./pics/time/flink_time_introduction.png) 40 | 41 | 图片参考官网,加上了一些注释配合理解,三种时间类型对应了三个发生位置,下面具体说下三种时间类型的区别。 42 | 43 | --- 44 | ## Processing Time 处理时间 45 | 46 | **`Processing Time`是指事件正在执行所在机器(部署应用服务器)的系统时间。** 47 | 48 | 当流式程序在 `Processing Time` 上运行时,所有基于时间的操作(如时间窗口)都将使用运行相应算子 `Operator` 所在计算机的系统时钟。 49 | 50 | 每小时 `Processing Time` 窗口将包括系统时钟指示整小时的时间之间到达特定操作员的所有记录。例如,如果应用程序在 9:15 am开始运行,则第一个每小时处理 `Processing Time` 将包括在 9:15 am 和 10:00 am 之间处理的事件,下一个窗口将包括在 10:00 am 和 11:00 am 之间处理的事件,依此类推。 51 | 52 | **`Processing Time` 是最简单的时间概念,不需要流和机器之间的协调。它提供了最佳的性能和最低的延迟。但是,在分布式和异步环境中,`Processing Time` 不能提供确定性,因为它容易受到记录到达系统(例如从消息队列)到达系统的速度,记录在系统内部操作员之间流动的速度的影响,以及中断(计划的或其他方式)。** 53 | 54 | --- 55 | ## Event Time 事件时间 56 | 57 | **`Event Time` 是每个事件在其生产设备 `Event producer` 上发生的时间。** 58 | 59 | 该时间通常在它们进入 `Flink` 之前嵌入到记录中,并且可以从每个记录中提取事件时间戳。 (可以想象成它是数据本身的一个属性,它的值保存的是时间) 60 | 61 | 在 `Event Time` 中,时间值取决于数据,而不取决于系统时间。 `Event Time` 程序必须指定如何生成 `Event Time` 的 `Watermark`,这是表示 `Event Time` 进度的机制。 62 | 63 | --- 64 | ## Ingestion Time 摄取时间 65 | 66 | **`Ingestion Time` 是事件进入Flink的时间。** 67 | 68 | 在源操作处,每条记录都将源的当前时间作为时间戳记,并且基于时间的操作(例如时间窗口)引用该时间戳记。 69 | 70 | **`Ingestion Time` 从概念上讲介于事件时间和处理时间之间。** 71 | 72 | 与 `Processing Time` 相比,它稍微贵一点(翻译的时候有点懵,应该是程序计算资源花费会增加,因为相比于前面两种类型,它会自动分配 `Watermark`),但结果却更可预测。 73 | 74 | 由于 `Ingestion Time` 使用稳定的时间戳(在源处分配了一次),因此对记录的不同窗口操作将引用相同的时间戳,而在 `Processing Time` 中,每个窗口的算子 `Operator` 都可以将记录分配给不同的窗口(基于本地系统时间和到达延误)。 75 | 76 | 与 `Processing Time` 相比,`Ingestion Time` 程序无法处理任何乱序事件或延迟数据,但程序无需指定如何生成 `Watermark`。 77 | 78 | 在内部,将 `Ingestion Time` 视为事件发生的时间,它具有自动分配时间戳和自动生成 `Watermark` 的功能。 79 | 80 | --- 81 | # 设置时间属性 82 | 83 | **`Setting a Time Characteristic`** 84 | 85 | 前面介绍了三种时间属性的概念和区别,下面来看下在实际中如何应用: 86 | 87 | **在 `Flink DataStream` 程序的起始步骤,通常设置基准时间特征。** 88 | 89 | 该设置定义数据流源的行为方式(例如,它们是否将分配时间戳),以及诸如 `KeyedStream.timeWindow(Time.seconds(30))` 之类的窗口操作应使用什么时间概念。 90 | 91 | 以下示例显示了一个 `Flink` 程序,该程序统计时间窗口(每小时)事件。 窗口的行为与时间特征相适应。 92 | 93 | ```java 94 | final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); 95 | env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime); 96 | // 另外可选: 97 | // env.setStreamTimeCharacteristic(TimeCharacteristic.IngestionTime); 98 | // env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime); 99 | DataStream stream = env.addSource(new FlinkKafkaConsumer09(topic, schema, props)); 100 | 101 | stream 102 | .keyBy( (event) -> event.getUser() ) 103 | .timeWindow(Time.hours(1)) 104 | .reduce( (a, b) -> a.add(b) ) 105 | .addSink(...); 106 | ``` 107 | 108 | 请注意,为了在事件时间中运行上面示例程序,程序需要使用直接为数据定义 `Processing Time` 并自己设定 `Watermark` 的生成规则,或者程序必须在源之后注入 `Timestamp Assigner` & `Watermark Generator` 。这些功能描述了如何访问事件时间戳,以及事件流呈现出何种程度的乱序。 109 | 110 | --- 111 | # Event Time and Watermarks 112 | 113 | 在为什么使用事件时间和 `Watermark` 这个问题上,引用了参考资料三的描述 114 | 115 | > 在进行 window 计算时,使用摄入时间或处理时间的消息都是以系统的墙上时间(wall clocks)为标准,因此事件都是按序到达的。 116 | > 117 | > 然而如果使用更为有意义的事件时间则会需要面对乱序事件问题(out-of-order events)和迟到事件问题(late events)。 118 | > 119 | > 针对这两个问题,Flink 主要采用了以水位线(watermark)为核心的机制来应对。 120 | 121 | 通过上面的描述,应该能对 `Watermark` 要解决的问题有个清晰的了解:**解决乱序事件** 122 | 123 | --- 124 | ## 乱序事件场景 125 | 126 | 如果数据源是 `kafka` 消息数据源,按照事件时间 `Event Time` 来统计,在理想情况下,消息按照事件顺序依次到达,时间窗口刚好收集的该时间段的事件,但很可惜,由于不可预估的外力阻挠,导致消息延迟,时间窗口内的数据将会少了延迟到达的事件。 127 | 128 | **所以使用 `Watermark` 来记录事件进行的进度,用收集到的消息来评估事件进度,判断还有没有事件没有到达,只有 `Watermark` 越过了时间窗口设定的时间,才认为窗口已经收集好数据** 129 | 130 | 举个具体一点的例子,设定了一个 3s 的时间窗口还有 10s 的乱序延时: 131 | 132 | ```java 133 | long maxUnOrderWatermark = 10000L; 134 | // watermark 设定成 当前时间戳 - 延时 135 | new Watermark(currentTimeStamp - maxUnOrderWatermark); 136 | ``` 137 | 138 | 在 [00:01 : 00:03] 窗口时间过去后,搜集到了 3 个时间,但是窗口先不触发计算,等待有可能延迟的事件。 139 | 140 | 例如在 06s 又有一个前面窗口的事件到来,由于在设定的延时时间内,它会被分配到正确窗口中,窗口中的元素变成了 4 个,然后在后续有新事件来临,`watermark` 被更新成大于 00:03,**这时 `Watermark` > 窗口结束时间,触发窗口计算,解决了事件延时到达的问题。** 141 | 142 | --- 143 | ## Event Time 和 Watermark 的关系 144 | 145 | **支持 `Event Time` 的流处理器需要一种测量 `Event Time` 进度的方法(`Watermark`)。例如,当事件时间超过一个小时结束时,需要通知构建每小时窗口的 `Operator`,以便该 `Operator` 可以关闭正在进行的窗口。** 146 | 147 | `Event Time` 可以独立于 `Processing Time`(由系统时间测量)进行。例如,在一个程序中,`operator` 的当前 `Event Time` 可能会稍微落后于 `Processing Time`(考虑到事件接收的延迟),而两者均以相同的速度进行。 另一方面,另一个流媒体程序可以通过快速转发已经在 `Kafka Topic` 主题(或另一个消息队列)中缓存的一些历史数据来在数周的事件时间内进行处理,而处理时间仅为几秒钟。 148 | 149 | --- 150 | ## Watermark 图示 151 | 152 | **`Flink` 中用于衡量事件时间进度的机制是水印 `Watermark`。 `Watermark` 作为数据流的一部分流动,并带有时间戳 `t`。 `Watermark(t)` 声明事件时间已在该流中达到时间 `t`,这意味着该流中不应再有时间戳 `t'<= t` 的元素(即时间戳早于或等于 `Watermark` 的事件)。** 153 | 154 | 下图显示了带有(逻辑)时间戳记的事件流,以及串联的 `Watermark`。 在此示例中,事件是按顺序(`In Order`)排列的(相对于其时间戳),这意味着 `Watermark` 只是流中的周期性标记。 155 | 156 | ![](./pics/time/stream_watermark_in_order.svg) 157 | 158 | **`Watermark` 对于乱序流(`Out Of Order`)至关重要,如下图所示,其中事件不是按其时间戳排序的。** 通常,`Watermark` 是一种声明,即到流中的那个点,直到某个时间戳的所有事件都应该到达。一旦 `Watermark` 到达 `Operator`,`Operator` 就可以将其内部事件时钟提前到 `Watermark` 的值。 159 | 160 | ![](./pics/time/stream_watermark_out_of_order.svg) 161 | 162 | > 请注意,`Evnet Time` 是由新创建的一个(或多个)流元素从产生它们的事件或触发了创建这些元素的 `Watermark` 中继承的。 163 | 164 | --- 165 | # 并行流中的 Watermark 166 | 167 | `Watermark` 在源函数处或源函数之后直接生成。源函数的每个并行子任务通常独立生成其水印。 这些水印定义了该特定并行源处的事件时间。 168 | 169 | 随着 `Watermark` 在流媒体程序中的流动,它们会提前到达其到达的 `Operator` 的 `Event Time`。 每当 `Operator` 提前其事件时间时,都会为其后续 `Operator` 在下游生成新的 `Watermark`。 170 | 171 | 一些运算符消费多个输入流;例如,并集 `union`、键控 `keyBy(…)` 或分区 `partition(…)` 函数的运算符。这样的 `Operator` 的当前事件时间是其输入流的事件时间中的最小值。 随着其输入流更新其事件时间,`Operator` 也将更新。 172 | 173 | 下图显示了流过并行流的事件和 `Watermark` 的示例,`Operator` 跟踪事件时间。 174 | 175 | ![](./pics/time/parallel_streams_watermarks.svg) 176 | 177 | (并行流用的不多,这里留个坑吧,待之后用到再来补=-=) 178 | 179 | --- 180 | # 延迟的元素 181 | 182 | 某些元素可能会违反 `Watermark` 条件,这意味着即使在发生 `Watermark(t)` 之后,也会出现更多时间戳为 `t'<= t` 的元素。实际上,在许多现实世界的设置中,某些元素可以任意延迟,从而无法指定某个事件时间戳记的所有元素都将发生的时间。此外,即使可以限制延迟,通常也不希望将 `Watermark` 延迟太多,因为这会导致事件时间窗的评估延迟过多。 183 | 184 | 由于这个原因,流式传输程序可能会明确期望某些延迟元素。延迟元素是指系统的事件时间时钟(由 `Watermark` 指示)在经过延迟元素时间戳之后的时间到达的元素。有关如何在事件时间窗口中使用延迟元素的更多信息,请参见[允许延迟](https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/stream/operators/windows.html#allowed-lateness)。 185 | 186 | --- 187 | # Watermark 实际使用例子 188 | 189 | 1. [Generating Timestamps / Watermarks](https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/event_timestamps_watermarks.html) 190 | 2. [Flink Watermark 机制浅析](https://www.whitewood.me/2018/06/01/Flink-Watermark-%E6%9C%BA%E5%88%B6%E6%B5%85%E6%9E%90/) 191 | 192 | **看了这两篇文章后,能对 `Watermark` 的设置有个基础的了解,在实际场景中,需要评估下面两者:定期 `Watermark`或标点 `Watermark`,了解两者差别后才使用。** 193 | 194 | --- 195 | # 总结 196 | 197 | 本篇主要讲了三种时间类型:`Processing Time`、`Event Time` 和 `Ingestion Time`,了解了它们所发生的位置,三者的使用差别,以及 `Watermark` 与 事件时间 `Event Time` 的关系,可以使用 `Watermark` 来解决乱序的事件流,请参考实际使用例子的链接,调整算法来达到你所需要解决的实际场景~ 198 | 199 | 以及本篇时间 `Time` 的介绍有点“太干”,学起来有点费力,如有其它学习建议或文章不对之处,请与我联系~ 200 | 201 | --- 202 | # 项目地址 203 | 204 | [https://github.com/Vip-Augus/flink-learning-note](https://github.com/Vip-Augus/flink-learning-note) 205 | 206 | ```sh 207 | git clone https://github.com/Vip-Augus/flink-learning-note 208 | ``` 209 | 210 | --- 211 | # 参考资料 212 | 213 | 1. [Event Time](https://ci.apache.org/projects/flink/flink-docs-release-1.9/zh/dev/event_time.html) 214 | 2. [Flink 从 0 到 1 学习 —— Flink 中几种 Time 详解](http://www.54tianzhisheng.cn/2018/12/11/Flink-time/) 215 | 3. [Flink Watermark 机制浅析](https://www.whitewood.me/2018/06/01/Flink-Watermark-%E6%9C%BA%E5%88%B6%E6%B5%85%E6%9E%90/) 216 | 4. [Flink 小贴士 (3): 轻松理解 Watermark](http://wuchong.me/blog/2018/11/18/flink-tips-watermarks-in-apache-flink-made-easy/) 217 | 5. [Generating Timestamps / Watermarks](https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/event_timestamps_watermarks.html) 218 | 6. [允许延迟](https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/stream/operators/windows.html#allowed-lateness) 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | -------------------------------------------------------------------------------- /note/2019-12-01-flink_learn_window.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [前言](#%E5%89%8D%E8%A8%80) 6 | - [Window 窗口是什么](#window-%E7%AA%97%E5%8F%A3%E6%98%AF%E4%BB%80%E4%B9%88) 7 | - [Window 按驱动类型分类](#window-%E6%8C%89%E9%A9%B1%E5%8A%A8%E7%B1%BB%E5%9E%8B%E5%88%86%E7%B1%BB) 8 | - [Time Windows](#time-windows) 9 | - [Tumbling Time Window 滚动时间窗口](#tumbling-time-window-%E6%BB%9A%E5%8A%A8%E6%97%B6%E9%97%B4%E7%AA%97%E5%8F%A3) 10 | - [Sliding Time Window 滑动时间窗口](#sliding-time-window--%E6%BB%91%E5%8A%A8%E6%97%B6%E9%97%B4%E7%AA%97%E5%8F%A3) 11 | - [CountWindows](#countwindows) 12 | - [Tunmling Count Window](#tunmling-count-window) 13 | - [Sliding Count Window](#sliding-count-window) 14 | - [Session Window](#session-window) 15 | - [Window 开放的三大核心 API](#window-%E5%BC%80%E6%94%BE%E7%9A%84%E4%B8%89%E5%A4%A7%E6%A0%B8%E5%BF%83-api) 16 | - [Window Assigner](#window-assigner) 17 | - [Trigger](#trigger) 18 | - [Evictor](#evictor) 19 | - [剖析 Window 实现机制](#%E5%89%96%E6%9E%90-window-%E5%AE%9E%E7%8E%B0%E6%9C%BA%E5%88%B6) 20 | - [使用例子](#%E4%BD%BF%E7%94%A8%E4%BE%8B%E5%AD%90) 21 | - [总结](#%E6%80%BB%E7%BB%93) 22 | - [项目地址](#%E9%A1%B9%E7%9B%AE%E5%9C%B0%E5%9D%80) 23 | - [参考资料](#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99) 24 | 25 | 26 | 27 | # 前言 28 | 29 | 前面讲了时间 `Time` 的概念和实际解决问题后,本篇来看下经常搭配使用的另一个关键工具:窗口 `Window`。 30 | 31 | 窗口也有三种类型可供选择使用: 32 | 33 | - **Tumbling Windows:滚动窗口** 34 | - **Sliding Windows:滑动窗口** 35 | - **Session Windows:会话窗口** 36 | 37 | 友情提示,本篇主要翻译自官网以及参考了 `wuchong` 大神的博客,内容比较干货,介绍这三种窗口的概念以及使用场景,希望看完能对 `Flink` 的窗口概念加深理解。 38 | 39 | 40 | 41 | # Window 窗口是什么 42 | 43 | `Windows` 是处理无限流的核心。`Windows` 将流分成有限大小的“存储桶”,我们可以在其上应用计算。**`Flink` 是一个优秀的流计算引擎,数据是源源不断的,它认为批处理 `Batch` 是一种特殊的流计算,在流中分割出一个个窗口,每个窗口相当于有限大小的空间,汇聚了待处理的数据。** 44 | 45 | 窗口式 `Flink` 程序的一般结构如下所示。第一个片段指的是键控流,第二个片段指的是非键控流。可以看到,唯一的区别是对键控流的 `keyBy(...)` 调用和对非键控流的 `window(...)` 变为 `windowAll(...)`。 46 | 47 | - Keyed Windows 键控流 48 | 49 | ```java 50 | stream 51 | .keyBy(...) <- keyed versus non-keyed windows 52 | .window(...) <- required: "assigner" 53 | [.trigger(...)] <- optional: "trigger" (else default trigger) 54 | [.evictor(...)] <- optional: "evictor" (else no evictor) 55 | [.allowedLateness(...)] <- optional: "lateness" (else zero) 56 | [.sideOutputLateData(...)] <- optional: "output tag" (else no side output for late data) 57 | .reduce/aggregate/fold/apply() <- required: "function" 58 | [.getSideOutput(...)] <- optional: "output tag" 59 | ``` 60 | 61 | - Non-Keyed Windows 非键控流 62 | 63 | ```java 64 | stream 65 | .windowAll(...) <- required: "assigner" 66 | [.trigger(...)] <- optional: "trigger" (else default trigger) 67 | [.evictor(...)] <- optional: "evictor" (else no evictor) 68 | [.allowedLateness(...)] <- optional: "lateness" (else zero) 69 | [.sideOutputLateData(...)] <- optional: "output tag" (else no side output for late data) 70 | .reduce/aggregate/fold/apply() <- required: "function" 71 | [.getSideOutput(...)] <- optional: "output tag" 72 | ``` 73 | 74 | --- 75 | # Window 按驱动类型分类 76 | 77 | ![](./pics/window/flink_window_xmind.png) 78 | 79 | 上图按照不同驱动类型,将窗口分成三类。 80 | 81 | - **时间驱动(Time Window,例如:每 10 秒钟)** 82 | - **数据驱动(Count Window,例如: 搜集到 100 个事件)** 83 | - **会话窗口(Session Window,一次会话中搜集到的事件)** 84 | 85 | 再往下细分,**分成了滚动窗口(Tumbling Window,窗口没有重叠)和滑动窗口(Sliding Window,窗口会有重叠的部分)** 86 | 87 | 所以 88 | 89 | 这里引用 `wuchong` 博客中关于 `Window` 的介绍 90 | 91 | > **我们举个具体的场景来形象地理解不同窗口的概念。假设,淘宝网会记录每个用户每次购买的商品个数,我们要做的是统计不同窗口中用户购买商品的总数。下图给出了几种经典的窗口切分概述图(圈中的数字代表该用户本次购买的商品个数):** 92 | 93 | ![](./pics/window/jark_window_intro.png) 94 | 95 | 从上图能够看出,`Raw stream data` 是原始数据流,数据输入方向为【从左到右】,根据左侧不同的窗口分类,将会得到右边相应的窗口,依据设定的窗口大小和时间间隔,每个窗口将会搜集到相应的数据(一个颜色块表示一个窗口)。 96 | 97 | 下面将分别介绍这三种窗口类型,其中计数 `Count` 和时间 `Time` 窗口可以选择使用滚动 `Tumbling` 或滑动 `Sliding` 功能,区别在于统计窗口计算的触发时机和窗口是否有重叠。 98 | 99 | --- 100 | ## Time Windows 101 | 102 | 顾名思义,`Time Window` 按时间分组流元素。例如,一分钟的滚动时间窗口将收集一分钟的元素,并在一分钟后将功能应用于窗口中的所有元素。 103 | 104 | 在前面的文章中讲过 `Flink` 中的时间概念,分别是 `Event Time` 事件时间、`Processing Time` 处理时间和 `Ingestion Time` 注入时间,其中 `Event Time` 可能引起的乱序事件,使用 `Watermark` 去兼容处理,在 `Flink` 中,时间类型和窗口机制是解耦的,所以设定不同的时间类型,不需要修改窗口的计算逻辑,就能达到想要的计算结果。 105 | 106 | ### Tumbling Time Window 滚动时间窗口 107 | 108 | 例如下面例子中,表示搜集 5s 内的事件,做统计 `sum` 计算。 109 | 110 | ```java 111 | DataStream input = ...; 112 | // 时间属性的设定,EventTime 模式,用基于数据中自带的时间戳 113 | env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime); 114 | input 115 | .keyBy() 116 | .window(TumblingEventTimeWindows.of(Time.seconds(5))) 117 | .sum(1); 118 | ``` 119 | 120 | **在上面时间属性的设定 `TimeCharacteristic.EventTime`,需要与后面算子 `window` 操作的 `TumblingEventTimeWindows.of(Time.seconds(5))` 保持一致。后面例子中,将会省去时间属性的设定,贴出核心的代码** 121 | 122 | ### Sliding Time Window 滑动时间窗口 123 | 124 | 有时候我们会需要每 5min 后,统计前面 60min 内事件的聚合值。 125 | 126 | 这时需要滑动窗口,保存上一次时间窗口的输入值,给下一个窗口使用重叠的部分。所以一个数据在滑动窗口中,可以出现在多个窗口中。 127 | 128 | ```java 129 | DataStream windowData = pvData 130 | .keyBy("itemId") 131 | .timeWindow(Time.minutes(60), Time.minutes(5)) 132 | .aggregate(new CountAgg(), new WindowResultFunction()); 133 | ``` 134 | 135 | --- 136 | ## CountWindows 137 | 138 | `Apache Flink` 还具有计数窗口。滚动计数窗口为 100 时,将在一个窗口中收集 100 个事件,并在添加第 100 个元素时触发该窗口的计算。 139 | 140 | 在 `DataStream API` 中,滚动和滑动计数窗口的定义如下: 141 | 142 | ### Tunmling Count Window 143 | 144 | 例如每次想要统计 100 个事件的合计值,只要从数据源处,累加满 100 个事件就触发计算,可以使用下面的 `API` 145 | 146 | ```java 147 | DataStream windowCounts = text 148 | .flatMap(...) 149 | .keyBy("word") 150 | .countWindow(100) 151 | .sum(1); 152 | ``` 153 | 154 | ### Sliding Count Window 155 | 156 | 在一些场景下,有可能来了 5 个数据后,要统计前 100 个数据的合计值,每个数据可以被分配到多个窗口中,那么就可以使用滑动窗口实现。 157 | 158 | ```java 159 | DataStream windowCounts = text 160 | .flatMap(...) 161 | .keyBy("word") 162 | .window(SlidingEventTimeWindows.of(5, 100) 163 | .sum(1); 164 | ``` 165 | 166 | --- 167 | ## Session Window 168 | 169 | `Session Window` 会话窗口分配器 `Assigner` 按活动会话对元素进行分组。 170 | 171 | **与 `Tumbling` 滚动窗口和 `Sliding` 滑动窗口相比,会话窗口不重叠且没有固定的开始和结束时间。** 172 | 173 | 相反,当会话窗口在一定时间段内未接收到元素时,即在发生不活动间隙时,它将关闭。会话窗口分配器可以配置有静态会话间隔,也可以配置有会话间隔提取器功能,该功能定义不活动的时间长度。 当此时间段到期时,当前会话将关闭,随后的元素将分配给新的会话窗口。 174 | 175 | ```java 176 | DataStream input = ...; 177 | input 178 | .keyBy() 179 | .window(EventTimeSessionWindows.withGap(Time.minutes(10))) 180 | .(); 181 | ``` 182 | 183 | 其中,`withGap` 方法中的入参,表示会话间隔的大小,表示超过这个时间没有新数据进入,该会话就会关闭,之后的数据就会进入下一个窗口。 184 | 185 | --- 186 | # Window 开放的三大核心 API 187 | 188 | **核心有三个组件:窗口分配器 Window Assigner 、触发器 Trigger、'驱逐者' Evictor,下面来 一一介绍** 189 | 190 | ## Window Assigner 191 | 192 | 该组件主要功能是决定数据该分发到哪个窗口,它的作用可以类比于 `Spring MVC` 中的 `Dispatcher`。 193 | 194 | ![](./pics/window/window_assigner_impls.png) 195 | 196 | 上图左侧是 `Flink` 窗口实现的包结构,三大组件在对应的目录下,清晰明了。 197 | 198 | 底部是 `Window Assigner` 的继承类,在调用 `WindowedStream window(WindowAssigner assigner)` 这类方法时,可以传入上述的窗口分发器,在里面实现自定义的窗口分发逻辑。 199 | 200 | ## Trigger 201 | 202 | 每个窗口都有一个触发器,该触发器决定何时评估或清除该窗口。 203 | 204 | 对于每个插入到窗口中的元素以及先前注册的计时器超时时,将触发该触发器。 205 | 206 | ![](./pics/window/trigger_impls.png) 207 | 208 | 上图展示了触发器的继承类,从中可以看出,它可以根据时间或者计数来触发,表示这个窗口的数据已收集完成,可以触发计算逻辑。 209 | 210 | ## Evictor 211 | 212 | 直译为 ‘驱逐者’,作用类似于过滤器 `fliter`,在 `trigger` 后执行,如果设定了 `evictor`,将会去除不符合条件的数据(默认是不设定的,不会驱逐) 213 | 214 | ![](./pics/window/evictor_impls.png) 215 | 216 | 通过这三大组件,可以实现自定义窗口逻辑,决定数据如何分配、何时触发计算以及哪些数据要被提前去除,详细使用例子可以参考官网的示例:https://flink.apache.org/news/2015/12/04/Introducing-windows.html 217 | 218 | # 剖析 Window 实现机制 219 | 220 | **前面介绍了三大组件,接下来就来看下在实际应用中,它们的流程是如何串起来。** 221 | 222 | `Flink` 的内置时间和计数窗口涵盖了各种常见的窗口用例。 223 | 224 | 但是,当然有些应用程序需要自定义窗口逻辑,而 `Flink` 的内置窗口无法解决这些逻辑。为了也支持需要非常特定的窗口语义的应用程序,`DataStream API` 公开了其窗口机制内部的接口。 225 | 226 | 这些接口可以非常精细地控制窗口的构建和评估方式。下图描述了 `Flink` 的窗口机制,并介绍了其中涉及的组件。 227 | 228 | ![](./pics/window/flink_window_mechanism.png) 229 | 230 | 到达窗口运算符的元素将传递给 `WindowAssigner`。` WindowAssigner` 将元素分配给一个或多个窗口,可能会创建新窗口。窗口本身只是元素列表的标识符,并且可以提供一些可选的元信息,例如在使用 `TimeWindow` 时的开始和结束时间。请注意,可以将元素添加到多个窗口,这也意味着元素可以同时存在于多个窗口(图中的元素 6 就存在于第一个和第三个窗口中)。 231 | 232 | 每个窗口都有一个触发器 `Trigger`,该触发器决定该窗口何时被计算或清除。对于每个插入到窗口中的元素以及先前注册的计时器超时时,将触发对应的 `Trigger`。 233 | 234 | 对于每个事件,触发器都可以决定触发(即计算),清除(删除窗口并丢弃其内容),或者触发然后清除窗口。仅触发的触发器会计算窗口并保持其原样,即所有元素保留在窗口中,并在下次触发时再次计算。一个窗口可以被计算多次,并且一直存在,直到被清除为止。请注意,在清除窗口之前,它会一直消耗内存。 235 | 236 | 触发 `Trigger` 时,可以将窗口元素列表提供给 `Evictor` (如果有的话)。驱逐者可以遍历列表,并决定从列表的开头删除一些元素,即删除一些首先进入窗口的元素。 其余元素进入到下一步计算函数 `Evaluation Function`。 如果未定义 `Evictor`,则触发器将所有窗口元素直接移交给计算函数。 237 | 238 | **关于驱逐者 `Evictor`,可以参考绿色线(元素 5 被移除了)和橙色线(没有定义 `Evicotr`,所有元素流入计算函数)。** 239 | 240 | 241 | `Evaluation Function` 计算函数接收一个窗口的元素(可能由 `Evictor` 过滤),并为该窗口计算一个或多个结果元素。`DataStream API` 接受不同类型的计算函数,包括预定义的聚合函数,例如 `sum(),min(),max()` 以及 `ReduceFunction`,`FoldFunction` 或 `WindowFunction`。`WindowFunction` 是最通用的计算函数,它接收窗口对象(即窗口的元数据),窗口元素列表以及窗口键(在键控窗口的情况下)作为参数。 242 | 243 | 以上就是一个完整的窗口计算流程,经历了 `Window Assigner` -> `Trigger` -> `Evictor` -> `Evaluation Function` 的过程,最终获得结果。 244 | 245 | --- 246 | # 使用例子 247 | 248 | 这里需要翻出前面写的 [HelloWorld 的例子](https://github.com/Vip-Augus/flink-learning-note/blob/master/note/2019-10-13-flink_learn_hello_world.md),参考以下 `Demo` 249 | 250 | ```java 251 | public class SocketWindowWordCount { 252 | public static void main(String[] args) throws Exception { 253 | String hostName = "127.0.0.1"; 254 | int port = 9000; 255 | final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); 256 | DataStream text = env.socketTextStream("localhost", port, "\n"); 257 | DataStream windowCounts = text 258 | .flatMap(new FlatMapFunction() { 259 | @Override 260 | public void flatMap(String value, Collector out) { 261 | for (String word : value.split("\\s")) { 262 | out.collect(new WordWithCount(word, 1L)); 263 | } 264 | } 265 | }) 266 | .keyBy("word") 267 | .timeWindow(Time.seconds(5), Time.seconds(1)) 268 | .reduce(new ReduceFunction() { 269 | @Override 270 | public WordWithCount reduce(WordWithCount a, WordWithCount b) { 271 | return new WordWithCount(a.getWord(), a.getCount() + b.getCount()); 272 | } 273 | }); 274 | 275 | // print the results with a single thread, rather than in parallel 276 | windowCounts.print().setParallelism(1); 277 | env.execute("Socket Window WordCount"); 278 | } 279 | } 280 | ``` 281 | 282 | 打开 9000 端口,往窗口发送数据,程序接收到数据进行解析,接着使用 `timeWindow` 时间窗口搜集数据,窗口大小为 5s,滑动统计的时间间隔为 1s,于是就有了下图的输出结果: 283 | 284 | ![](./pics/helloworld/flink_hello_world_time_winodw.png) 285 | 286 | 每秒输出当前 5s 内搜集到的数据,时间窗口统计正确运行。 287 | 288 | 289 | # 总结 290 | 291 | `Time` 时间和 `Window` 窗口是 `Flink` 的亮点,所以这两篇对它们的学习是十分必要的,了解它们的概念和原理,可以更好的去使用它们。 292 | 293 | `Flink` 是一个流处理器,提供了非常强大的运算符,帮助我们计算、聚合数据。同时提供了窗口机制,将流划分成一个个区间,对于区间,也就是窗口中的元素进行计算,达到批处理的作用。 294 | 295 | 本篇介绍了 `Window` 是什么,它的分类,滑动窗口 `Sliding` 和滚动窗口 `Tumbling`,时间 `Time` 和计数 `Count` 驱动,介绍了三大核心组件以及 `Window` 的机制,剩下的源码分析,建议小伙伴们去看下 `wuchong` 大神写的分析 :[Flink 原理与实现:Window 机制](http://wuchong.me/blog/2016/05/25/flink-internals-window-mechanism/) 296 | 297 | 如有其它学习建议或文章不对之处,请与我联系~(可在 `Github` 中提 `Issue` 或掘金中联系) 298 | 299 | --- 300 | # 项目地址 301 | 302 | [https://github.com/Vip-Augus/flink-learning-note](https://github.com/Vip-Augus/flink-learning-note) 303 | 304 | ```sh 305 | git clone https://github.com/Vip-Augus/flink-learning-note 306 | ``` 307 | 308 | --- 309 | # 参考资料 310 | 311 | 1. [Flink 原理与实现:Window 机制](http://wuchong.me/blog/2016/05/25/flink-internals-window-mechanism/) 312 | 2. [Flink 从 0 到 1 学习 —— 介绍Flink中的Stream Windows](http://www.54tianzhisheng.cn/2018/12/08/Flink-Stream-Windows/) 313 | 3. [Windows](https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/stream/operators/windows.html#windows) 314 | 4. [Introducing Stream Windows in Apache Flink](https://flink.apache.org/news/2015/12/04/Introducing-windows.html) 315 | 316 | 317 | 318 | 319 | 320 | 321 | -------------------------------------------------------------------------------- /note/pics/Flink_study_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/Flink_study_diagram.png -------------------------------------------------------------------------------- /note/pics/datasink/flink_demo_flow_chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/datasink/flink_demo_flow_chart.png -------------------------------------------------------------------------------- /note/pics/datasink/sinkfuncaiton_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/datasink/sinkfuncaiton_diagram.png -------------------------------------------------------------------------------- /note/pics/datasink/sinkfunction_customsink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/datasink/sinkfunction_customsink.png -------------------------------------------------------------------------------- /note/pics/datasink/sinkfunction_data_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/datasink/sinkfunction_data_flow.png -------------------------------------------------------------------------------- /note/pics/datasink/sinkfunction_datasource.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/datasink/sinkfunction_datasource.png -------------------------------------------------------------------------------- /note/pics/datasink/sinkfunction_printsinkfunction_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/datasink/sinkfunction_printsinkfunction_diagram.png -------------------------------------------------------------------------------- /note/pics/datasink/sinkfunction_source&job.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/datasink/sinkfunction_source&job.png -------------------------------------------------------------------------------- /note/pics/datasink/sinkfunction_verify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/datasink/sinkfunction_verify.png -------------------------------------------------------------------------------- /note/pics/datasource/StreamExecutionEnvironment_DataSource.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/datasource/StreamExecutionEnvironment_DataSource.png -------------------------------------------------------------------------------- /note/pics/datasource/flink_data_source_connector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/datasource/flink_data_source_connector.png -------------------------------------------------------------------------------- /note/pics/datasource/flink_datasource_kafka.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/datasource/flink_datasource_kafka.png -------------------------------------------------------------------------------- /note/pics/datasource/flink_datasource_kafka_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/datasource/flink_datasource_kafka_result.png -------------------------------------------------------------------------------- /note/pics/datasource/flink_datasource_mind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/datasource/flink_datasource_mind.png -------------------------------------------------------------------------------- /note/pics/datasource/flink_rich_source_function.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/datasource/flink_rich_source_function.png -------------------------------------------------------------------------------- /note/pics/helloworld/flink_dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/helloworld/flink_dashboard.png -------------------------------------------------------------------------------- /note/pics/helloworld/flink_debug_method.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/helloworld/flink_debug_method.png -------------------------------------------------------------------------------- /note/pics/helloworld/flink_demo_overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/helloworld/flink_demo_overview.png -------------------------------------------------------------------------------- /note/pics/helloworld/flink_hello_world_time_winodw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/helloworld/flink_hello_world_time_winodw.png -------------------------------------------------------------------------------- /note/pics/helloworld/flink_helloworld_process.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/helloworld/flink_helloworld_process.png -------------------------------------------------------------------------------- /note/pics/helloworld/flink_run_demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/helloworld/flink_run_demo.png -------------------------------------------------------------------------------- /note/pics/introduction/alibaba_flink_preview.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/introduction/alibaba_flink_preview.jpeg -------------------------------------------------------------------------------- /note/pics/introduction/api-stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/introduction/api-stack.png -------------------------------------------------------------------------------- /note/pics/introduction/bounded-unbounded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/introduction/bounded-unbounded.png -------------------------------------------------------------------------------- /note/pics/introduction/flink_application_semantic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/introduction/flink_application_semantic.png -------------------------------------------------------------------------------- /note/pics/introduction/flink_architecture.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/introduction/flink_architecture.jpeg -------------------------------------------------------------------------------- /note/pics/introduction/flink_distract_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/introduction/flink_distract_architecture.png -------------------------------------------------------------------------------- /note/pics/introduction/flink_jobManager_worker.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/introduction/flink_jobManager_worker.jpg -------------------------------------------------------------------------------- /note/pics/introduction/flink_state_checkpoint_introducation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/introduction/flink_state_checkpoint_introducation.png -------------------------------------------------------------------------------- /note/pics/introduction/flink_storm_throughput_contrast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/introduction/flink_storm_throughput_contrast.png -------------------------------------------------------------------------------- /note/pics/introduction/flink_strom_delayed_contrast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/introduction/flink_strom_delayed_contrast.png -------------------------------------------------------------------------------- /note/pics/introduction/levels_of_abstraction.svg: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 31 | 33 | 35 | 36 | 38 | image/svg+xml 39 | 41 | 42 | 43 | 44 | 45 | 48 | 51 | 55 | 59 | Stateful 65 | Stream Processing 71 | 75 | 79 | DataStream 85 | / 91 | DataSet 97 | API 103 | 107 | 111 | Table API 117 | 121 | 125 | SQL 131 | Core 137 | APIs 143 | Declarative DSL 149 | High 155 | - 161 | level Language 167 | Low 173 | - 179 | level building block 185 | (streams, state, [event] time) 191 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /note/pics/introduction/system_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/introduction/system_architecture.png -------------------------------------------------------------------------------- /note/pics/time/flink_time_introduction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/time/flink_time_introduction.png -------------------------------------------------------------------------------- /note/pics/time/stream_watermark_in_order.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | 22 | 32 | 34 | 36 | 37 | 39 | image/svg+xml 40 | 42 | 43 | 44 | 45 | 46 | 49 | 52 | 56 | 60 | 64 | Stream 70 | (in order) 76 | 80 | 84 | 88 | 7 94 | 98 | W(11) 104 | W(20) 110 | Watermark 116 | 120 | 124 | 128 | 132 | 9 138 | 142 | 146 | 9 152 | 156 | 160 | 10 166 | 170 | 174 | 11 180 | 184 | 188 | 14 194 | 198 | 202 | 15 208 | 212 | 216 | 17 222 | Event 228 | Event timestamp 234 | 238 | 242 | 246 | 250 | 18 256 | 260 | 264 | 20 270 | 274 | 278 | 19 284 | 288 | 292 | 21 298 | 302 | 306 | 23 312 | 313 | 314 | 315 | -------------------------------------------------------------------------------- /note/pics/time/stream_watermark_out_of_order.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | 22 | 32 | 34 | 36 | 37 | 39 | image/svg+xml 40 | 42 | 43 | 44 | 45 | 46 | 49 | 52 | 56 | 60 | 64 | Stream 70 | (out of order) 76 | 80 | 84 | 88 | 7 94 | 98 | W(11) 104 | W(17) 110 | 114 | 118 | 11 124 | 128 | 132 | 15 138 | 142 | 146 | 9 152 | 156 | 160 | 12 166 | 170 | 174 | 14 180 | 184 | 188 | 17 194 | 198 | 202 | 12 208 | 212 | 216 | 22 222 | 226 | 230 | 20 236 | 240 | 244 | 17 250 | 254 | 258 | 19 264 | 268 | 272 | 21 278 | Watermark 284 | 288 | 292 | Event 298 | Event timestamp 304 | 308 | 312 | 313 | 314 | 315 | -------------------------------------------------------------------------------- /note/pics/transformation/flink_transformation_demo_methods.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/transformation/flink_transformation_demo_methods.png -------------------------------------------------------------------------------- /note/pics/transformation/flink_transformation_min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/transformation/flink_transformation_min.png -------------------------------------------------------------------------------- /note/pics/transformation/flink_transformation_minBy&min_diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/transformation/flink_transformation_minBy&min_diff.png -------------------------------------------------------------------------------- /note/pics/transformation/flink_transformation_minBy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/transformation/flink_transformation_minBy.png -------------------------------------------------------------------------------- /note/pics/transformation/flink_transformation_official_desc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/transformation/flink_transformation_official_desc.png -------------------------------------------------------------------------------- /note/pics/window/evictor_impls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/window/evictor_impls.png -------------------------------------------------------------------------------- /note/pics/window/flink_window_mechanism.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/window/flink_window_mechanism.png -------------------------------------------------------------------------------- /note/pics/window/flink_window_method.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/window/flink_window_method.png -------------------------------------------------------------------------------- /note/pics/window/flink_window_sensor_intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/window/flink_window_sensor_intro.png -------------------------------------------------------------------------------- /note/pics/window/flink_window_xmind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/window/flink_window_xmind.png -------------------------------------------------------------------------------- /note/pics/window/jark_window_intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/window/jark_window_intro.png -------------------------------------------------------------------------------- /note/pics/window/trigger_impls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/window/trigger_impls.png -------------------------------------------------------------------------------- /note/pics/window/window_assigner_impls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vip-Augus/flink-learning-note/d787dca3d432a4f1f654a86d7bc5671c5e08e4fb/note/pics/window/window_assigner_impls.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 19 | 21 | 4.0.0 22 | 23 | cn.sevenyuan 24 | flink-quick-start 25 | 1.0-SNAPSHOT 26 | jar 27 | 28 | Flink Quickstart Job 29 | http://www.myorganization.org 30 | 31 | 32 | UTF-8 33 | 1.9.0 34 | 1.8 35 | 2.11 36 | ${java.version} 37 | ${java.version} 38 | 39 | 40 | 41 | 42 | apache.snapshots 43 | Apache Development Snapshot Repository 44 | https://repository.apache.org/content/repositories/snapshots/ 45 | 46 | false 47 | 48 | 49 | true 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | org.apache.flink 59 | flink-java 60 | ${flink.version} 61 | provided 62 | 63 | 64 | org.apache.flink 65 | flink-streaming-java_${scala.binary.version} 66 | ${flink.version} 67 | provided 68 | 69 | 70 | 71 | org.projectlombok 72 | lombok 73 | 1.16.4 74 | 75 | 76 | 77 | 78 | 86 | 87 | 88 | 89 | 90 | org.slf4j 91 | slf4j-log4j12 92 | 1.7.7 93 | runtime 94 | 95 | 96 | log4j 97 | log4j 98 | 1.2.17 99 | runtime 100 | 101 | 102 | 103 | com.google.guava 104 | guava 105 | 28.1-jre 106 | 107 | 108 | 109 | 110 | org.apache.flink 111 | flink-connector-kafka_2.11 112 | 1.9.0 113 | 114 | 115 | 116 | 117 | com.alibaba 118 | fastjson 119 | 1.2.61 120 | 121 | 122 | 123 | 124 | redis.clients 125 | jedis 126 | 3.1.0 127 | 128 | 129 | 130 | 131 | 132 | mysql 133 | mysql-connector-java 134 | 8.0.16 135 | 136 | 137 | 138 | 139 | com.alibaba 140 | druid 141 | 1.1.20 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | org.apache.maven.plugins 153 | maven-compiler-plugin 154 | 3.1 155 | 156 | ${java.version} 157 | ${java.version} 158 | 159 | 160 | 161 | 162 | 163 | 164 | org.apache.maven.plugins 165 | maven-shade-plugin 166 | 3.0.0 167 | 168 | 169 | 170 | package 171 | 172 | shade 173 | 174 | 175 | 176 | 177 | org.apache.flink:force-shading 178 | com.google.code.findbugs:jsr305 179 | org.slf4j:* 180 | log4j:* 181 | 182 | 183 | 184 | 185 | 187 | *:* 188 | 189 | META-INF/*.SF 190 | META-INF/*.DSA 191 | META-INF/*.RSA 192 | 193 | 194 | 195 | 196 | 197 | cn.sevenyuan.watermark.UnOrderEventTimeWindowDemo 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | add-dependencies-for-IDEA 221 | 222 | 223 | 224 | idea.version 225 | 226 | 227 | 228 | 229 | 230 | org.apache.flink 231 | flink-java 232 | ${flink.version} 233 | compile 234 | 235 | 236 | org.apache.flink 237 | flink-streaming-java_${scala.binary.version} 238 | ${flink.version} 239 | compile 240 | 241 | 242 | 243 | 244 | 245 | 246 | -------------------------------------------------------------------------------- /src/main/java/cn/sevenyuan/BatchJob.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package cn.sevenyuan; 20 | 21 | import org.apache.flink.api.java.ExecutionEnvironment; 22 | 23 | /** 24 | * Skeleton for a Flink Batch Job. 25 | * 26 | *

For a tutorial how to write a Flink batch application, check the 27 | * tutorials and examples on the Flink Website. 28 | * 29 | *

To package your application into a JAR file for execution, 30 | * change the main class in the POM.xml file to this class (simply search for 'mainClass') 31 | * and run 'mvn clean package' on the command line. 32 | */ 33 | public class BatchJob { 34 | 35 | public static void main(String[] args) throws Exception { 36 | // set up the batch execution environment 37 | final ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment(); 38 | 39 | /* 40 | * Here, you can start creating your execution plan for Flink. 41 | * 42 | * Start with getting some data from the environment, like 43 | * env.readTextFile(textPath); 44 | * 45 | * then, transform the resulting DataSet using operations 46 | * like 47 | * .filter() 48 | * .flatMap() 49 | * .join() 50 | * .coGroup() 51 | * 52 | * and many more. 53 | * Have a look at the programming guide for the Java API: 54 | * 55 | * http://flink.apache.org/docs/latest/apis/batch/index.html 56 | * 57 | * and the examples 58 | * 59 | * http://flink.apache.org/docs/latest/apis/batch/examples.html 60 | * 61 | */ 62 | 63 | // execute program 64 | env.execute("Flink Batch Java API Skeleton"); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/cn/sevenyuan/StreamingJob.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package cn.sevenyuan; 20 | 21 | import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; 22 | 23 | /** 24 | * Skeleton for a Flink Streaming Job. 25 | * 26 | *

For a tutorial how to write a Flink streaming application, check the 27 | * tutorials and examples on the Flink Website. 28 | * 29 | *

To package your application into a JAR file for execution, run 30 | * 'mvn clean package' on the command line. 31 | * 32 | *

If you change the name of the main class (with the public static void main(String[] args)) 33 | * method, change the respective entry in the POM.xml file (simply search for 'mainClass'). 34 | */ 35 | public class StreamingJob { 36 | 37 | public static void main(String[] args) throws Exception { 38 | // set up the streaming execution environment 39 | final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); 40 | 41 | /* 42 | * Here, you can start creating your execution plan for Flink. 43 | * 44 | * Start with getting some data from the environment, like 45 | * env.readTextFile(textPath); 46 | * 47 | * then, transform the resulting DataStream using operations 48 | * like 49 | * .filter() 50 | * .flatMap() 51 | * .join() 52 | * .coGroup() 53 | * 54 | * and many more. 55 | * Have a look at the programming guide for the Java API: 56 | * 57 | * http://flink.apache.org/docs/latest/apis/streaming/index.html 58 | * 59 | */ 60 | 61 | // execute program 62 | env.execute("Flink Streaming Java API Skeleton"); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/cn/sevenyuan/datasource/DataSourceFromCollection.java: -------------------------------------------------------------------------------- 1 | package cn.sevenyuan.datasource; 2 | 3 | import cn.sevenyuan.domain.Student; 4 | import com.google.common.collect.Lists; 5 | import org.apache.flink.api.common.functions.FlatMapFunction; 6 | import org.apache.flink.api.common.functions.MapFunction; 7 | import org.apache.flink.streaming.api.datastream.DataStreamSource; 8 | import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; 9 | import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; 10 | import org.apache.flink.util.Collector; 11 | 12 | import java.util.List; 13 | 14 | /** 15 | * 来源于集合 16 | * @author JingQ at 2019-09-22 17 | */ 18 | public class DataSourceFromCollection { 19 | 20 | public static void main(String[] args) throws Exception { 21 | StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); 22 | 23 | // DataStreamSource source1 = collection1(env); 24 | // SingleOutputStreamOperator operator1 = source1.map(new MapFunction() { 25 | // @Override 26 | // public Student map(Student student) throws Exception { 27 | // Student result = new Student(); 28 | // result.setId(student.getId()); 29 | // result.setName(student.getName()); 30 | // result.setAge(student.getAge()); 31 | // result.setAddress("加密地址"); 32 | // return result; 33 | // } 34 | // }); 35 | // operator1.print(); 36 | 37 | 38 | DataStreamSource source2 = collection2(env); 39 | SingleOutputStreamOperator operator2 = source2.flatMap(new FlatMapFunction() { 40 | @Override 41 | public void flatMap(Long aLong, Collector collector) throws Exception { 42 | if (aLong % 2 == 0) { 43 | collector.collect(new Student(aLong.intValue(), "name" + aLong, aLong.intValue(), "加密地址")); 44 | } 45 | } 46 | }); 47 | operator2.print(); 48 | 49 | 50 | env.execute("test collection source"); 51 | } 52 | 53 | private static DataStreamSource collection1(StreamExecutionEnvironment env) { 54 | List studentList = Lists.newArrayList( 55 | new Student(1, "name1", 23, "address1"), 56 | new Student(2, "name2", 23, "address2"), 57 | new Student(3, "name3", 23, "address3") 58 | ); 59 | return env.fromCollection(studentList); 60 | } 61 | 62 | /** 63 | * 生成 一段序列 64 | * @param env 运行环境 65 | * @return 序列输入流 66 | */ 67 | private static DataStreamSource collection2(StreamExecutionEnvironment env) { 68 | return env.generateSequence(1, 20); 69 | } 70 | } -------------------------------------------------------------------------------- /src/main/java/cn/sevenyuan/datasource/DataSourceFromFile.java: -------------------------------------------------------------------------------- 1 | package cn.sevenyuan.datasource; 2 | 3 | import cn.sevenyuan.domain.Student; 4 | import org.apache.flink.api.common.functions.FlatMapFunction; 5 | import org.apache.flink.api.java.io.TextInputFormat; 6 | import org.apache.flink.api.java.typeutils.TypeExtractor; 7 | import org.apache.flink.streaming.api.datastream.DataStreamSource; 8 | import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; 9 | import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; 10 | import org.apache.flink.streaming.api.functions.source.FileProcessingMode; 11 | 12 | 13 | import org.apache.flink.core.fs.Path; 14 | import org.apache.flink.util.Collector; 15 | 16 | import java.net.URL; 17 | 18 | /** 19 | * 文件输入流 20 | * @author JingQ at 2019-09-22 21 | */ 22 | public class DataSourceFromFile { 23 | 24 | public static void main(String[] args) throws Exception { 25 | StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); 26 | // 读取文件地址 27 | URL fileUrl = DataSourceFromFile.class.getClassLoader().getResource("datasource/student.txt"); 28 | String filePath = fileUrl.getPath(); 29 | 30 | // 简单的文字文件输入流 31 | // DataStreamSource textFileSource = 32 | // env.readTextFile(filePath); 33 | // SingleOutputStreamOperator textFileOperator = textFileSource.map(new MapFunction() { 34 | // @Override 35 | // public Student map(String s) throws Exception { 36 | // String[] tokens = s.split("\\W+"); 37 | // return new Student(Integer.valueOf(tokens[0]), tokens[1], Integer.valueOf(tokens[2]), "加密地址"); 38 | // } 39 | // }); 40 | // textFileOperator.print(); 41 | 42 | 43 | // 指定格式和监听类型 44 | Path pa = new Path(filePath); 45 | TextInputFormat inputFormat = new TextInputFormat(pa); 46 | DataStreamSource complexFileSource = 47 | env.readFile(inputFormat, filePath, FileProcessingMode.PROCESS_CONTINUOUSLY, 100L, 48 | TypeExtractor.getInputFormatTypes(inputFormat)); 49 | SingleOutputStreamOperator complexFileOperator = complexFileSource.flatMap(new FlatMapFunction() { 50 | @Override 51 | public void flatMap(String value, Collector out) throws Exception { 52 | String[] tokens = value.split("\\W+"); 53 | if (tokens.length > 1) { 54 | out.collect(new Student(Integer.valueOf(tokens[0]), tokens[1], Integer.valueOf(tokens[2]), "加密地址")); 55 | } 56 | } 57 | }); 58 | complexFileOperator.print(); 59 | 60 | 61 | env.execute("test file source"); 62 | } 63 | 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/cn/sevenyuan/datasource/DataSourceFromSocket.java: -------------------------------------------------------------------------------- 1 | package cn.sevenyuan.datasource; 2 | 3 | import org.apache.flink.api.common.functions.FlatMapFunction; 4 | import org.apache.flink.api.java.tuple.Tuple2; 5 | import org.apache.flink.streaming.api.datastream.DataStreamSource; 6 | import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; 7 | import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; 8 | import org.apache.flink.util.Collector; 9 | 10 | /** 11 | * 读出 socket 数据源 12 | * @author JingQ at 2019-09-22 13 | */ 14 | public class DataSourceFromSocket { 15 | 16 | public static void main(String[] args) throws Exception { 17 | 18 | StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); 19 | 20 | DataStreamSource source = env 21 | .socketTextStream("localhost", 9000); 22 | 23 | SingleOutputStreamOperator> operator = source 24 | .flatMap(new FlatMapFunction>() { 25 | @Override 26 | public void flatMap(String value, Collector> out) throws Exception { 27 | String[] tokens = value.split("\\W+"); 28 | for (String token : tokens) { 29 | out.collect(Tuple2.of(token, 1)); 30 | } 31 | } 32 | }) 33 | .keyBy(0) 34 | .sum(1); 35 | operator.print(); 36 | 37 | env.execute("test socket datasource"); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/cn/sevenyuan/datasource/custom/DataSourceFromKafka.java: -------------------------------------------------------------------------------- 1 | package cn.sevenyuan.datasource.custom; 2 | 3 | import cn.sevenyuan.domain.Student; 4 | import cn.sevenyuan.sink.SinkToMySQL; 5 | import cn.sevenyuan.util.KafkaUtils; 6 | import com.alibaba.fastjson.JSONObject; 7 | import com.google.common.collect.Lists; 8 | import org.apache.flink.api.common.serialization.SimpleStringSchema; 9 | import org.apache.flink.streaming.api.datastream.DataStream; 10 | import org.apache.flink.streaming.api.datastream.DataStreamSource; 11 | import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; 12 | import org.apache.flink.streaming.api.functions.windowing.AllWindowFunction; 13 | import org.apache.flink.streaming.api.windowing.time.Time; 14 | import org.apache.flink.streaming.api.windowing.windows.TimeWindow; 15 | import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; 16 | import org.apache.flink.util.Collector; 17 | 18 | import java.util.List; 19 | import java.util.Properties; 20 | 21 | /** 22 | * 官方库 kafka 数据源(准确来说是 kafka 连接器 connector) 23 | * @author JingQ at 2019-09-22 24 | */ 25 | public class DataSourceFromKafka { 26 | 27 | public static void main(String[] args) throws Exception { 28 | StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); 29 | 30 | Properties props = new Properties(); 31 | props.put("bootstrap.servers", KafkaUtils.BROKER_LIST); 32 | props.put("zookeeper.connect", "localhost:2181"); 33 | props.put("group.id", KafkaUtils.TOPIC_STUDENT); 34 | props.put("key.deserializer", KafkaUtils.KEY_SERIALIZER); 35 | props.put("value.deserializer", KafkaUtils.VALUE_SERIALIZER); 36 | props.put("auto.offset.reset", "latest"); 37 | 38 | DataStreamSource dataStreamSource = env.addSource(new FlinkKafkaConsumer( 39 | KafkaUtils.TOPIC_STUDENT, 40 | new SimpleStringSchema(), 41 | props 42 | )).setParallelism(1); 43 | 44 | 45 | // 数据下沉 46 | addMySQLSink(dataStreamSource); 47 | env.execute("test custom kafka datasource"); 48 | } 49 | 50 | private static void addMySQLSink(DataStreamSource dataStreamSource) { 51 | // 从 kafka 读数据,然后进行 map 映射转换 52 | DataStream dataStream = dataStreamSource.map(value -> JSONObject.parseObject(value, Student.class)); 53 | // 不需要 keyBy 分类,所以使用 windowAll,每 10s 统计接收到的数据,批量插入到数据库中 54 | dataStream 55 | .timeWindowAll(Time.seconds(10)) 56 | .apply(new AllWindowFunction, TimeWindow>() { 57 | @Override 58 | public void apply(TimeWindow window, Iterable values, Collector> out) throws Exception { 59 | List students = Lists.newArrayList(values); 60 | if (students.size() > 0) { 61 | System.out.println("最近 10 秒汇集到 " + students.size() + " 条数据"); 62 | out.collect(students); 63 | } 64 | } 65 | }) 66 | .addSink(new SinkToMySQL()); 67 | // 输出结果如下: 68 | // 最近 10 秒汇集到 3 条数据 69 | // success insert number : 3 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/cn/sevenyuan/datasource/custom/DataSourceFromRedis.java: -------------------------------------------------------------------------------- 1 | package cn.sevenyuan.datasource.custom; 2 | 3 | import org.apache.flink.api.common.functions.MapFunction; 4 | import org.apache.flink.streaming.api.datastream.DataStreamSource; 5 | import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; 6 | import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; 7 | 8 | /** 9 | * 自定义 DataSource,测试从 redis 取数据 10 | * @author JingQ at 2019-09-22 11 | */ 12 | public class DataSourceFromRedis { 13 | 14 | public static void main(String[] args) throws Exception { 15 | StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); 16 | 17 | DataStreamSource customSource = env.addSource(new MyRedisDataSourceFunction()); 18 | SingleOutputStreamOperator operator = customSource 19 | .map((MapFunction) value -> "当前最大值为 : " + value); 20 | operator.print(); 21 | env.execute("test custom redis datasource function"); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/cn/sevenyuan/datasource/custom/MyRedisDataSourceFunction.java: -------------------------------------------------------------------------------- 1 | package cn.sevenyuan.datasource.custom; 2 | 3 | import cn.sevenyuan.util.RedisUtils; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.apache.flink.configuration.Configuration; 6 | import org.apache.flink.streaming.api.functions.source.RichSourceFunction; 7 | 8 | /** 9 | * @author JingQ at 2019-09-22 10 | */ 11 | public class MyRedisDataSourceFunction extends RichSourceFunction { 12 | 13 | @Override 14 | public void open(Configuration parameters) throws Exception { 15 | super.open(parameters); 16 | // noop 17 | } 18 | 19 | @Override 20 | public void run(SourceContext ctx) throws Exception { 21 | while (true) { 22 | String maxNumber = RedisUtils.get("maxNumber", String.class); 23 | ctx.collect(StringUtils.isBlank(maxNumber) ? "0" : maxNumber); 24 | // 隔 1 s 执行程序 25 | Thread.sleep(1000); 26 | } 27 | } 28 | 29 | @Override 30 | public void cancel() { 31 | // noop 32 | } 33 | 34 | @Override 35 | public void close() throws Exception { 36 | super.close(); 37 | RedisUtils.close(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/cn/sevenyuan/domain/Student.java: -------------------------------------------------------------------------------- 1 | package cn.sevenyuan.domain; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.Date; 6 | 7 | /** 8 | * @author JingQ at 2019-09-22 9 | */ 10 | @Data 11 | public class Student { 12 | 13 | private int id; 14 | 15 | private String name; 16 | 17 | private int age; 18 | 19 | private String address; 20 | 21 | private Date checkInTime; 22 | 23 | private long successTimeStamp; 24 | 25 | public Student() { 26 | } 27 | 28 | public Student(int id, String name, int age, String address) { 29 | this.id = id; 30 | this.name = name; 31 | this.age = age; 32 | this.address = address; 33 | } 34 | 35 | public static Student of(int id, String name, int age, String address, long timeStamp) { 36 | Student student = new Student(id, name, age, address); 37 | student.setSuccessTimeStamp(timeStamp); 38 | return student; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/cn/sevenyuan/domain/WordWithCount.java: -------------------------------------------------------------------------------- 1 | package cn.sevenyuan.domain; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author JingQ at 2019-09-18 7 | */ 8 | @Data 9 | public class WordWithCount { 10 | 11 | private String word; 12 | 13 | private long count; 14 | 15 | public WordWithCount() {} 16 | 17 | public WordWithCount(String word, long count) { 18 | this.word = word; 19 | this.count = count; 20 | } 21 | 22 | @Override 23 | public String toString() { 24 | return word + " : " + count; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/cn/sevenyuan/hotest/itemcount/CountAgg.java: -------------------------------------------------------------------------------- 1 | package cn.sevenyuan.hotest.itemcount; 2 | 3 | import org.apache.flink.api.common.functions.AggregateFunction; 4 | 5 | /** 6 | * COUNT 统计的聚合函数实现,每出现一条记录加一 7 | * @author JingQ at 2019-09-28 8 | */ 9 | public class CountAgg implements AggregateFunction { 10 | 11 | @Override 12 | public Long createAccumulator() { 13 | return 0L; 14 | } 15 | 16 | @Override 17 | public Long add(UserBehavior value, Long accumulator) { 18 | return accumulator + 1; 19 | } 20 | 21 | @Override 22 | public Long getResult(Long accumulator) { 23 | return accumulator; 24 | } 25 | 26 | @Override 27 | public Long merge(Long a, Long b) { 28 | // 返回输出结果,这里提前聚合掉数据,减少 state 的存储压力 29 | return a + b; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/cn/sevenyuan/hotest/itemcount/HotItems.java: -------------------------------------------------------------------------------- 1 | package cn.sevenyuan.hotest.itemcount; 2 | 3 | import cn.sevenyuan.datasource.DataSourceFromFile; 4 | import org.apache.flink.api.common.functions.FilterFunction; 5 | import org.apache.flink.api.java.io.PojoCsvInputFormat; 6 | import org.apache.flink.api.java.typeutils.PojoTypeInfo; 7 | import org.apache.flink.api.java.typeutils.TypeExtractor; 8 | import org.apache.flink.streaming.api.TimeCharacteristic; 9 | import org.apache.flink.streaming.api.datastream.DataStream; 10 | import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; 11 | 12 | import java.io.File; 13 | import java.net.URL; 14 | import org.apache.flink.core.fs.Path; 15 | import org.apache.flink.streaming.api.functions.timestamps.AscendingTimestampExtractor; 16 | import org.apache.flink.streaming.api.windowing.time.Time; 17 | 18 | /** 19 | * reference http://wuchong.me/blog/2018/11/07/use-flink-calculate-hot-items/ 20 | * @author JingQ at 2019-09-28 21 | */ 22 | public class HotItems { 23 | 24 | public static void main(String[] args) throws Exception { 25 | 26 | StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); 27 | // 为了打印到控制台的结果不乱序,配置全局的并发度为 1(对正确性没有影响) 28 | env.setParallelism(1); 29 | // 以下步骤,是为了使用 PojoCsvInputFormat,读取 csv 文件并转成 POJO 30 | URL fileUrl = HotItems.class.getClassLoader().getResource("UserBehavior.csv"); 31 | Path filePath = Path.fromLocalFile(new File("/Users/jingqi/Deploy/Project/IdeaProject/flink-quick-start/src/main/resources/UserBehavior.csv")); 32 | PojoTypeInfo pojoTypeInfo = (PojoTypeInfo) TypeExtractor.createTypeInfo(UserBehavior.class); 33 | // 由于 Java 反射抽取出来的字段顺序是不确定的,需要显示指定文件中字段的顺序 34 | String[] fieldOrder = new String[]{"userId", "itemId", "categoryId", "behavior", "timestamp"}; 35 | // 创建 PojoCsvInputFormat 36 | PojoCsvInputFormat csvInputFormat = new PojoCsvInputFormat<>(filePath, pojoTypeInfo, fieldOrder); 37 | 38 | // 开始创建输入源 39 | DataStream dataSource = env.createInput(csvInputFormat, pojoTypeInfo); 40 | 41 | // 一、设置 EventTime 模式,用基于数据中自带的时间戳(默认是【事件处理】processing time) 42 | env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime); 43 | // 二、指定获取业务时间,以及生成 Watermark(用来追踪事件的概念,用来指示当前处理到什么时刻的数据) 44 | DataStream timeData = dataSource 45 | .assignTimestampsAndWatermarks(new AscendingTimestampExtractor() { 46 | @Override 47 | public long extractAscendingTimestamp(UserBehavior element) { 48 | // 原始单位是秒,需要乘以 1000 ,转换成毫秒 49 | return element.getTimestamp() * 1000; 50 | } 51 | }); 52 | 53 | // 使用过滤算子 filter,筛选出操作行为中是 pv 的数据 54 | DataStream pvData = timeData 55 | .filter(new FilterFunction() { 56 | @Override 57 | public boolean filter(UserBehavior value) throws Exception { 58 | return "pv".equals(value.getBehavior()); 59 | } 60 | }); 61 | 62 | // 设定滑动窗口 sliding window,每隔五分钟统计最近一个小时的每个商品的点击量 63 | // 经历过程 dataStream -> keyStream -> dataStream 64 | DataStream windowData = pvData 65 | .keyBy("itemId") 66 | .timeWindow(Time.minutes(60), Time.minutes(5)) 67 | .aggregate(new CountAgg(), new WindowResultFunction()); 68 | 69 | // 统计最热门商品 70 | DataStream topItems = windowData 71 | .keyBy("windowEnd") 72 | .process(new TopNHotItems(3)); 73 | 74 | topItems.print(); 75 | env.execute("Test Hot Items Job"); 76 | } 77 | 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/cn/sevenyuan/hotest/itemcount/ItemViewCount.java: -------------------------------------------------------------------------------- 1 | package cn.sevenyuan.hotest.itemcount; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * 商品点击量(窗口操作的输出类型) 7 | * 8 | * @author JingQ at 2019-09-28 9 | */ 10 | @Data 11 | public class ItemViewCount { 12 | 13 | /** 14 | * 商品 ID 15 | */ 16 | private long itemId; 17 | 18 | /** 19 | * 窗口结束时间戳 20 | */ 21 | private long windowEnd; 22 | 23 | /** 24 | * 商品的点击数 25 | */ 26 | private long viewCount; 27 | 28 | public static ItemViewCount of(long itemId, long windowEnd, long viewCount) { 29 | ItemViewCount result = new ItemViewCount(); 30 | result.setItemId(itemId); 31 | result.setWindowEnd(windowEnd); 32 | result.setViewCount(viewCount); 33 | return result; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/cn/sevenyuan/hotest/itemcount/TopNHotItems.java: -------------------------------------------------------------------------------- 1 | package cn.sevenyuan.hotest.itemcount; 2 | 3 | import com.google.common.collect.Lists; 4 | import org.apache.flink.api.common.state.ListState; 5 | import org.apache.flink.api.common.state.ListStateDescriptor; 6 | import org.apache.flink.api.java.tuple.Tuple; 7 | import org.apache.flink.configuration.Configuration; 8 | import org.apache.flink.streaming.api.functions.KeyedProcessFunction; 9 | import org.apache.flink.util.Collector; 10 | 11 | import java.sql.Timestamp; 12 | import java.util.ArrayList; 13 | import java.util.Comparator; 14 | import java.util.List; 15 | 16 | /** 17 | * 求某个窗口中前 N 名的热门点击商品,key 为窗口时间,输出 TopN 的字符结果串 18 | * @author JingQ at 2019-09-29 19 | */ 20 | public class TopNHotItems extends KeyedProcessFunction { 21 | 22 | private final int topSize; 23 | 24 | public TopNHotItems(int topSize) { 25 | this.topSize = topSize; 26 | } 27 | 28 | /** 29 | * 用于存储商品与点击数的状态,待收取同一个窗口的数据后,再触发 TopN 计算 30 | */ 31 | private ListState itemState; 32 | 33 | @Override 34 | public void open(Configuration parameters) throws Exception { 35 | super.open(parameters); 36 | // 重载 open 方法,注册状态 37 | ListStateDescriptor itemViewCountListStateDescriptor = 38 | new ListStateDescriptor( 39 | "itemState-state", 40 | ItemViewCount.class 41 | ); 42 | // 用来存储收到的每条 ItemViewCount 状态,保证在发生故障时,状态数据的不丢失和一致性 43 | itemState = getRuntimeContext().getListState(itemViewCountListStateDescriptor); 44 | } 45 | 46 | @Override 47 | public void processElement(ItemViewCount value, Context ctx, Collector out) throws Exception { 48 | // 每条数据都保存到状态中 49 | itemState.add(value); 50 | // 注册 windowEnd + 1 的 EventTime Timer,当触发时,说明收起了属于 windowEnd 窗口的所有数据 51 | ctx.timerService().registerEventTimeTimer(value.getWindowEnd() + 1); 52 | } 53 | 54 | @Override 55 | public void onTimer(long timestamp, OnTimerContext ctx, Collector out) throws Exception { 56 | // 获取收到的所有商品点击量 57 | List allItems = Lists.newArrayList(itemState.get()); 58 | // 提前清除状态中的数据,释放空间 59 | itemState.clear(); 60 | // 按照点击量从大到小排序(也就是按照某字段正向排序,然后进行反序) 61 | allItems.sort(Comparator.comparing(ItemViewCount::getViewCount).reversed()); 62 | // 将排名信息格式化成 String 63 | StringBuilder result = new StringBuilder(); 64 | result.append("================================== TEST =================================\n"); 65 | result.append("时间: ").append(new Timestamp(timestamp - 1)).append("\n"); 66 | // 遍历点击事件到结果中 67 | int realSize = allItems.size() < topSize ? allItems.size() : topSize; 68 | for (int i = 0; i < realSize; i++) { 69 | ItemViewCount item = allItems.get(i); 70 | if (item == null) { 71 | continue; 72 | } 73 | result.append("No ").append(i).append(":") 74 | .append(" 商品 ID=").append(item.getItemId()) 75 | .append(" 游览量=").append(item.getViewCount()) 76 | .append("\n"); 77 | } 78 | result.append("================================== END =================================\n\n"); 79 | out.collect(result.toString()); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/cn/sevenyuan/hotest/itemcount/UserBehavior.java: -------------------------------------------------------------------------------- 1 | package cn.sevenyuan.hotest.itemcount; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * 简单的用户行为 7 | * @author JingQ at 2019-09-28 8 | */ 9 | @Data 10 | public class UserBehavior { 11 | 12 | /** 13 | * 用户 ID 14 | */ 15 | private long userId; 16 | 17 | /** 18 | * 商品 ID 19 | */ 20 | private long itemId; 21 | 22 | /** 23 | * 商品类目 ID 24 | */ 25 | private int categoryId; 26 | 27 | /** 28 | * 用户行为,包括("pv", "buy", "cart", "fav") 29 | */ 30 | private String behavior; 31 | 32 | /** 33 | * 行为发生的时间戳,模拟数据中自带了时间属性 34 | */ 35 | private long timestamp; 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/cn/sevenyuan/hotest/itemcount/WindowResultFunction.java: -------------------------------------------------------------------------------- 1 | package cn.sevenyuan.hotest.itemcount; 2 | 3 | import org.apache.flink.api.java.tuple.Tuple; 4 | import org.apache.flink.api.java.tuple.Tuple1; 5 | import org.apache.flink.streaming.api.functions.windowing.WindowFunction; 6 | import org.apache.flink.streaming.api.windowing.windows.TimeWindow; 7 | import org.apache.flink.util.Collector; 8 | 9 | /** 10 | * 用于输出窗口的结果 11 | * 12 | * WindowFunction 函数定义 WindowFunction 13 | * @author JingQ at 2019-09-28 14 | */ 15 | public class WindowResultFunction implements WindowFunction { 16 | 17 | @Override 18 | public void apply( 19 | Tuple tuple, // 窗口的主键,即 itemId 20 | TimeWindow window, // 窗口 21 | Iterable input, // 聚合函数,即 count 值 22 | Collector out) // 输出类型是 ItemViewCount 23 | throws Exception { 24 | Long itemId = ((Tuple1) tuple).f0; 25 | Long count = input.iterator().next(); 26 | out.collect(ItemViewCount.of(itemId, window.getEnd(), count)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/cn/sevenyuan/sink/SinkToMySQL.java: -------------------------------------------------------------------------------- 1 | package cn.sevenyuan.sink; 2 | 3 | import cn.sevenyuan.domain.Student; 4 | import cn.sevenyuan.util.MyDruidUtils; 5 | import org.apache.flink.configuration.Configuration; 6 | import org.apache.flink.streaming.api.functions.sink.RichSinkFunction; 7 | 8 | import java.sql.Connection; 9 | import java.sql.PreparedStatement; 10 | import java.util.List; 11 | 12 | /** 13 | * 将数据下沉到 MySQL 数据库中 14 | * @author JingQ at 2019-09-30 15 | */ 16 | public class SinkToMySQL extends RichSinkFunction> { 17 | 18 | private PreparedStatement ps; 19 | 20 | private Connection connection; 21 | 22 | @Override 23 | public void open(Configuration parameters) throws Exception { 24 | super.open(parameters); 25 | // 获取链接 26 | connection = MyDruidUtils.getConnection(); 27 | String sql = "insert into student(name, age, address) values (?, ?, ?);"; 28 | ps = connection.prepareStatement(sql); 29 | } 30 | 31 | @Override 32 | public void close() throws Exception { 33 | super.close(); 34 | // 关闭连接和释放资源 35 | if (connection != null) { 36 | connection.close(); 37 | } 38 | if (ps != null) { 39 | ps.close(); 40 | } 41 | } 42 | 43 | @Override 44 | public void invoke(List value, Context context) throws Exception { 45 | // 遍历数据集合 46 | for (Student student : value) { 47 | ps.setString(1, student.getName()); 48 | ps.setInt(2, student.getAge()); 49 | ps.setString(3, student.getAddress()); 50 | ps.addBatch(); 51 | } 52 | // 一次性插入时间窗口聚合起来的数据 53 | int[] count = ps.executeBatch(); 54 | System.out.println("success insert number : " + count.length); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/cn/sevenyuan/transformation/MyExtendTimestampExtractor.java: -------------------------------------------------------------------------------- 1 | package cn.sevenyuan.transformation; 2 | 3 | import org.apache.flink.streaming.api.functions.AssignerWithPeriodicWatermarks; 4 | import org.apache.flink.streaming.api.watermark.Watermark; 5 | 6 | import javax.annotation.Nullable; 7 | 8 | /** 9 | * 简单时间抽取器,返回当前时间即可,不需要类型限制 10 | * @author JingQ at 2019-10-19 11 | */ 12 | public class MyExtendTimestampExtractor implements AssignerWithPeriodicWatermarks { 13 | @Nullable 14 | @Override 15 | public Watermark getCurrentWatermark() { 16 | return new Watermark(System.currentTimeMillis()); 17 | } 18 | 19 | @Override 20 | public long extractTimestamp(T element, long previousElementTimestamp) { 21 | return System.currentTimeMillis(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/cn/sevenyuan/transformation/MyTimestampExtractor.java: -------------------------------------------------------------------------------- 1 | package cn.sevenyuan.transformation; 2 | 3 | import cn.sevenyuan.domain.Student; 4 | import org.apache.flink.streaming.api.functions.AssignerWithPeriodicWatermarks; 5 | import org.apache.flink.streaming.api.watermark.Watermark; 6 | 7 | import javax.annotation.Nullable; 8 | 9 | /** 10 | * @author JingQ at 2019-09-24 11 | */ 12 | public class MyTimestampExtractor implements AssignerWithPeriodicWatermarks { 13 | 14 | 15 | @Nullable 16 | @Override 17 | public Watermark getCurrentWatermark() { 18 | return new Watermark(System.currentTimeMillis()); 19 | } 20 | 21 | @Override 22 | public long extractTimestamp(Student element, long previousElementTimestamp) { 23 | return System.currentTimeMillis(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/cn/sevenyuan/transformation/StudentTimeAndWindowTransformation.java: -------------------------------------------------------------------------------- 1 | package cn.sevenyuan.transformation; 2 | 3 | import cn.sevenyuan.domain.Student; 4 | import cn.sevenyuan.transformation.studentwindow.CountStudentAgg; 5 | import cn.sevenyuan.transformation.studentwindow.StudentViewCount; 6 | import cn.sevenyuan.transformation.studentwindow.WindowStudentResultFunction; 7 | import com.google.common.collect.Lists; 8 | import org.apache.flink.streaming.api.TimeCharacteristic; 9 | import org.apache.flink.streaming.api.datastream.DataStream; 10 | import org.apache.flink.streaming.api.datastream.DataStreamSource; 11 | import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; 12 | import org.apache.flink.streaming.api.functions.timestamps.AscendingTimestampExtractor; 13 | import org.apache.flink.streaming.api.windowing.time.Time; 14 | 15 | import java.util.List; 16 | 17 | 18 | /** 19 | * 20 | * Time 和 Window 的结合 21 | * @author JingQ at 2019-09-26 22 | */ 23 | public class StudentTimeAndWindowTransformation { 24 | 25 | public static void main(String[] args) throws Exception{ 26 | StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); 27 | // 设定按照【事件发生】的时间进行处理 28 | env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime); 29 | // 来自集合的数据源 30 | DataStreamSource collectionSource = env.fromCollection(getCollection()); 31 | // 分配时间戳和添加水印,接着按照 id 进行分类 keyStream,最后进行聚合 32 | DataStream windowData = collectionSource 33 | .assignTimestampsAndWatermarks(new AscendingTimestampExtractor() { 34 | @Override 35 | public long extractAscendingTimestamp(Student element) { 36 | return element.getSuccessTimeStamp(); 37 | } 38 | }) 39 | .keyBy("id") 40 | .timeWindow(Time.milliseconds(100), Time.milliseconds(10)) 41 | .aggregate(new CountStudentAgg(), new WindowStudentResultFunction()); 42 | windowData.print(); 43 | env.execute("test Tumbling window"); 44 | } 45 | 46 | 47 | 48 | 49 | 50 | 51 | private static List getCollection() { 52 | return Lists.newArrayList( 53 | Student.of(1, "第一种商品名字 1", 0, "test", 1569640890385L), 54 | Student.of(2, "第一种商品名字 2",0, "test", 1569640890386L), 55 | Student.of(3, "第一种商品名字 3",0, "test", 1569640890387L), 56 | Student.of(4, "第一种商品名字 4",0, "test", 1569640890388L), 57 | Student.of(5, "第一种商品名字 5",0, "test", 1569640890389L), 58 | Student.of(6, "第一种商品名字 6",0, "test", 1569640890390L), 59 | Student.of(7, "第一种商品名字 7",0, "test", 1569640890391L), 60 | Student.of(8, "第一种商品名字 8",0, "test", 1569640890392L), 61 | Student.of(9, "第一种商品名字 9",0, "test", 1569640890393L), 62 | Student.of(10, "第一种商品名字 10", 0, "test", 1569640890394L), 63 | Student.of(11, "第一种商品名字 11", 0, "test", 1569640890395L), 64 | Student.of(12, "第一种商品名字 12", 0, "test", 1569640890396L), 65 | Student.of(13, "第一种商品名字 13", 0, "test", 1569640890397L), 66 | Student.of(14, "第一种商品名字 14", 0, "test", 1569640890398L), 67 | Student.of(15, "第一种商品名字 15", 0, "test", 1569640890399L), 68 | Student.of(16, "第一种商品名字 16", 0, "test", 1569640890400L), 69 | Student.of(17, "第一种商品名字 17", 0, "test", 1569640890401L), 70 | 71 | Student.of(1, "第二种商品名字 1", 0, "test", 1569640890401L), 72 | Student.of(2, "第二种商品名字 2", 0, "test",1569640890402L), 73 | Student.of(3, "第二种商品名字 3", 0, "test",1569640890403L), 74 | Student.of(4, "第二种商品名字 4", 0, "test",1569640890404L), 75 | Student.of(5, "第二种商品名字 5", 0, "test",1569640890405L), 76 | Student.of(6, "第二种商品名字 6", 0, "test",1569640890406L), 77 | Student.of(7, "第二种商品名字 7", 0, "test",1569640890407L), 78 | Student.of(8, "第二种商品名字 8", 0, "test",1569640890408L), 79 | Student.of(9, "第二种商品名字 9", 0, "test",1569640890409L), 80 | Student.of(10, "第二种商品名字 10", 0, "test", 1569640890410L), 81 | Student.of(11, "第二种商品名字 11", 0, "test", 1569640890411L), 82 | Student.of(12, "第二种商品名字 12", 0, "test", 1569640890412L), 83 | Student.of(13, "第二种商品名字 13", 0, "test", 1569640890413L), 84 | Student.of(14, "第二种商品名字 14", 0, "test", 1569640890414L), 85 | Student.of(15, "第二种商品名字 15", 0, "test", 1569640890415L), 86 | Student.of(16, "第二种商品名字 16", 0, "test", 1569640890416L), 87 | Student.of(17, "第二种商品名字 17", 0, "test", 1569640890417L), 88 | 89 | Student.of(1, "第三种商品名字 1", 0, "test", 1569640890418L), 90 | Student.of(2, "第三种商品名字 2", 0, "test",1569640890419L), 91 | Student.of(3, "第三种商品名字 3", 0, "test",1569640890420L), 92 | Student.of(4, "第三种商品名字 4", 0, "test",1569640890421L), 93 | Student.of(5, "第三种商品名字 5", 0, "test",1569640890422L), 94 | Student.of(6, "第三种商品名字 6", 0, "test",1569640890423L), 95 | Student.of(7, "第三种商品名字 7", 0, "test",1569640890424L), 96 | Student.of(8, "第三种商品名字 8", 0, "test",1569640890425L), 97 | Student.of(9, "第三种商品名字 9", 0, "test",1569640890426L), 98 | Student.of(10, "第三种商品名字 10", 0, "test", 1569640890427L), 99 | Student.of(11, "第三种商品名字 11", 0, "test", 1569640890428L), 100 | Student.of(12, "第三种商品名字 12", 0, "test", 1569640890429L), 101 | Student.of(13, "第三种商品名字 13", 0, "test", 1569640890430L), 102 | Student.of(14, "第三种商品名字 14", 0, "test", 1569640890431L), 103 | Student.of(15, "第三种商品名字 15", 0, "test", 1569640890432L), 104 | Student.of(16, "第三种商品名字 16", 0, "test", 1569640890433L), 105 | Student.of(17, "第三种商品名字 17", 0, "test", 1569640890434L) 106 | ); 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/cn/sevenyuan/transformation/studentwindow/CountStudentAgg.java: -------------------------------------------------------------------------------- 1 | package cn.sevenyuan.transformation.studentwindow; 2 | 3 | import cn.sevenyuan.domain.Student; 4 | import org.apache.flink.api.common.functions.AggregateFunction; 5 | 6 | /** 7 | * COUNT 统计的聚合函数实现,将结果累加,每遇到一条记录进行加一 8 | * 9 | * reference http://wuchong.me/blog/2018/11/07/use-flink-calculate-hot-items/ 10 | * 11 | * @author JingQ at 2019-09-28 12 | */ 13 | public class CountStudentAgg implements AggregateFunction { 14 | 15 | @Override 16 | public Long createAccumulator() { 17 | return 0L; 18 | } 19 | 20 | @Override 21 | public Long add(Student value, Long accumulator) { 22 | return accumulator + 1; 23 | } 24 | 25 | @Override 26 | public Long getResult(Long accumulator) { 27 | return accumulator; 28 | } 29 | 30 | @Override 31 | public Long merge(Long a, Long b) { 32 | return a + b; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/cn/sevenyuan/transformation/studentwindow/StudentViewCount.java: -------------------------------------------------------------------------------- 1 | package cn.sevenyuan.transformation.studentwindow; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * 学生的统计基础类 7 | * 8 | * @author JingQ at 2019-09-28 9 | */ 10 | @Data 11 | public class StudentViewCount { 12 | 13 | private int id; 14 | 15 | /** 16 | * 窗口结束时间 17 | */ 18 | private long windowEnd; 19 | 20 | /** 21 | * 同一个 id 下的统计数量 22 | */ 23 | private long viewCount; 24 | 25 | public static StudentViewCount of(int id, long windowEnd, long count) { 26 | StudentViewCount result = new StudentViewCount(); 27 | result.setId(id); 28 | result.setWindowEnd(windowEnd); 29 | result.setViewCount(count); 30 | return result; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/cn/sevenyuan/transformation/studentwindow/WindowStudentResultFunction.java: -------------------------------------------------------------------------------- 1 | package cn.sevenyuan.transformation.studentwindow; 2 | 3 | import org.apache.flink.api.java.tuple.Tuple; 4 | import org.apache.flink.api.java.tuple.Tuple1; 5 | import org.apache.flink.streaming.api.functions.windowing.WindowFunction; 6 | import org.apache.flink.streaming.api.windowing.windows.TimeWindow; 7 | import org.apache.flink.util.Collector; 8 | 9 | /** 10 | * 用于输出统计学生的结果 11 | * 12 | * @author JingQ at 2019-09-28 13 | */ 14 | public class WindowStudentResultFunction implements WindowFunction { 15 | 16 | 17 | @Override 18 | public void apply(Tuple tuple, TimeWindow window, Iterable input, Collector out) throws Exception { 19 | int id = ((Tuple1) tuple).f0; 20 | long count = input.iterator().next(); 21 | out.collect(StudentViewCount.of(id, window.getEnd(), count)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/cn/sevenyuan/util/KafkaUtils.java: -------------------------------------------------------------------------------- 1 | package cn.sevenyuan.util; 2 | 3 | import cn.sevenyuan.domain.Student; 4 | import com.alibaba.fastjson.JSON; 5 | import org.apache.commons.lang3.RandomUtils; 6 | import org.apache.kafka.clients.producer.KafkaProducer; 7 | import org.apache.kafka.clients.producer.ProducerRecord; 8 | 9 | import java.util.Date; 10 | import java.util.Properties; 11 | 12 | /** 13 | * kafka 工具类 14 | * @author JingQ at 2019-09-22 15 | */ 16 | public class KafkaUtils { 17 | 18 | public static final String BROKER_LIST = "localhost:9092"; 19 | 20 | public static final String TOPIC_STUDENT = "student"; 21 | 22 | public static final String KEY_SERIALIZER = "org.apache.kafka.common.serialization.StringSerializer"; 23 | 24 | public static final String VALUE_SERIALIZER = "org.apache.kafka.common.serialization.StringSerializer"; 25 | 26 | public static void writeToKafka() throws Exception { 27 | Properties props = new Properties(); 28 | props.put("bootstrap.servers", BROKER_LIST); 29 | props.put("key.serializer", KEY_SERIALIZER); 30 | props.put("value.serializer", VALUE_SERIALIZER); 31 | KafkaProducer producer = new KafkaProducer<>(props); 32 | 33 | // 制造传递的对象 34 | int randomInt = RandomUtils.nextInt(1, 100); 35 | Student stu = new Student(randomInt, "name" + randomInt, randomInt, "=-="); 36 | stu.setCheckInTime(new Date()); 37 | // 发送数据 38 | ProducerRecord record = new ProducerRecord<>(TOPIC_STUDENT, null, null, JSON.toJSONString(stu)); 39 | producer.send(record); 40 | System.out.println("kafka 已发送消息 : " + JSON.toJSONString(stu)); 41 | producer.flush(); 42 | } 43 | 44 | public static void main(String[] args) throws Exception { 45 | while (true) { 46 | Thread.sleep(3000); 47 | writeToKafka(); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/cn/sevenyuan/util/MyDruidUtils.java: -------------------------------------------------------------------------------- 1 | package cn.sevenyuan.util; 2 | 3 | import com.alibaba.druid.pool.DruidDataSource; 4 | 5 | import java.sql.Connection; 6 | 7 | /** 8 | * 数据库连接池工具类 9 | * @author JingQ at 2019-09-30 10 | */ 11 | public class MyDruidUtils { 12 | 13 | private static DruidDataSource dataSource; 14 | 15 | public static Connection getConnection() throws Exception { 16 | // 使用 Druid 管理链接 17 | dataSource = new DruidDataSource(); 18 | dataSource.setDriverClassName("com.mysql.jdbc.Driver"); 19 | dataSource.setUrl("jdbc:mysql://localhost:3306/test"); 20 | dataSource.setUsername("root"); 21 | dataSource.setPassword("12345678"); 22 | // 初始链接数、最大连接数、最小闲置数 23 | dataSource.setInitialSize(10); 24 | dataSource.setMaxActive(50); 25 | dataSource.setMinIdle(2); 26 | // 返回链接 27 | return dataSource.getConnection(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/cn/sevenyuan/util/RedisUtils.java: -------------------------------------------------------------------------------- 1 | package cn.sevenyuan.util; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import lombok.extern.java.Log; 5 | import redis.clients.jedis.Jedis; 6 | import redis.clients.jedis.JedisPool; 7 | import redis.clients.jedis.JedisPoolConfig; 8 | 9 | /** 10 | * 缓存 redis 工具 11 | * @author JingQ at 2019-09-22 12 | */ 13 | @Log 14 | public class RedisUtils { 15 | 16 | public static final String HOST = "localhost"; 17 | 18 | public static final int PORT = 6379; 19 | 20 | public static final int TIMEOUT = 100000; 21 | 22 | public static final JedisPool JEDIS_POOL = new JedisPool(new JedisPoolConfig(), HOST, PORT, TIMEOUT, null); 23 | 24 | public static final Jedis JEDIS = JEDIS_POOL.getResource(); 25 | 26 | /** 27 | * 获取简单 jedis 对象 28 | * @return jedis 29 | */ 30 | public static Jedis getJedis() { 31 | return JEDIS; 32 | } 33 | 34 | 35 | public static void close() { 36 | JEDIS_POOL.close(); 37 | JEDIS.close(); 38 | } 39 | 40 | public static void set(String key, Object value) { 41 | String realValue = JSON.toJSONString(value); 42 | getJedis().set(key, realValue); 43 | } 44 | 45 | public static T get(String key, Class classType) { 46 | String value = getJedis().get(key); 47 | try { 48 | return JSON.parseObject(value, classType); 49 | } catch (Exception e) { 50 | log.info("无法转换对象,确认 json 对应的属性类 classType 正确"); 51 | return null; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/cn/sevenyuan/watermark/EventTimeData.txt: -------------------------------------------------------------------------------- 1 | 2 3 1 7 3 5 9 6 12 17 10 16 19 11 18 2 | 3 | 001,1575129602000 4 | 001,1575129603000 5 | 001,1575129601000 6 | 001,1575129607000 7 | 001,1575129603000 8 | 001,1575129605000 9 | 001,1575129609000 10 | 001,1575129606000 11 | 001,1575129612000 12 | 001,1575129617000 13 | 001,1575129610000 14 | 001,1575129616000 15 | 001,1575129619000 16 | 001,1575129611000 17 | 001,1575129618000 18 | -------------------------------------------------------------------------------- /src/main/java/cn/sevenyuan/watermark/UnOrderEventTimeWindowDemo.java: -------------------------------------------------------------------------------- 1 | package cn.sevenyuan.watermark; 2 | 3 | import com.google.common.base.Joiner; 4 | import org.apache.flink.api.common.functions.MapFunction; 5 | import org.apache.flink.api.java.tuple.Tuple; 6 | import org.apache.flink.api.java.tuple.Tuple2; 7 | import org.apache.flink.streaming.api.TimeCharacteristic; 8 | import org.apache.flink.streaming.api.datastream.DataStream; 9 | import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; 10 | import org.apache.flink.streaming.api.functions.windowing.WindowFunction; 11 | import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows; 12 | import org.apache.flink.streaming.api.windowing.time.Time; 13 | import org.apache.flink.streaming.api.windowing.windows.TimeWindow; 14 | import org.apache.flink.util.Collector; 15 | 16 | import java.text.SimpleDateFormat; 17 | import java.util.ArrayList; 18 | import java.util.Collections; 19 | import java.util.Iterator; 20 | import java.util.List; 21 | 22 | /** 23 | * 乱序事件时间窗口,验证 Watermark,解决超过窗口时间达到的事件,成功加入对应的窗口,触发窗口计算 24 | *

25 | * 参考链接 https://juejin.im/post/5bf95810e51d452d705fef33 26 | * 27 | * @author JingQ at 2019-11-26 28 | */ 29 | public class UnOrderEventTimeWindowDemo { 30 | 31 | public static void main(String[] args) throws Exception { 32 | //定义socket的端口号 33 | int port = 9010; 34 | //获取运行环境 35 | StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); 36 | //设置使用eventtime,默认是使用processtime 37 | env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime); 38 | //设置并行度为1,默认并行度是当前机器的cpu数量 39 | env.setParallelism(1); 40 | //连接socket获取输入的数据 41 | DataStream text = env.socketTextStream("127.0.0.1", port, "\n"); 42 | 43 | //解析输入的数据 44 | DataStream> inputMap = text.map(new MapFunction>() { 45 | @Override 46 | public Tuple2 map(String value) throws Exception { 47 | String[] arr = value.split(","); 48 | return new Tuple2<>(arr[0], Long.parseLong(arr[1])); 49 | } 50 | }); 51 | 52 | //抽取timestamp和生成watermark,封装成继承接口的类 53 | DataStream> waterMarkStream = inputMap.assignTimestampsAndWatermarks(new WordCountPeriodicWatermarks()); 54 | 55 | DataStream window = waterMarkStream.keyBy(0) 56 | //按照消息的EventTime分配窗口,和调用TimeWindow效果一样 57 | .window(TumblingEventTimeWindows.of(Time.seconds(4))) 58 | .apply(new WindowFunction, String, Tuple, TimeWindow>() { 59 | /** 60 | * 对window内的数据进行排序,保证数据的顺序 61 | * @param tuple 62 | * @param window 63 | * @param input 64 | * @param out 65 | * @throws Exception 66 | */ 67 | @Override 68 | public void apply(Tuple tuple, TimeWindow window, Iterable> input, Collector out) throws Exception { 69 | String key = tuple.toString(); 70 | List arrarList = new ArrayList<>(); 71 | List eventTimeList = new ArrayList<>(); 72 | Iterator> it = input.iterator(); 73 | while (it.hasNext()) { 74 | Tuple2 next = it.next(); 75 | arrarList.add(next.f1); 76 | eventTimeList.add(String.valueOf(next.f1).substring(8,10)); 77 | } 78 | Collections.sort(arrarList); 79 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); 80 | String result = "\n 键值 : " + key + "\n " + 81 | "触发窗内数据个数 : " + arrarList.size() + "\n " + 82 | "触发窗起始数据: " + sdf.format(arrarList.get(0)) + "\n " + 83 | "触发窗最后(可能是延时)数据:" + sdf.format(arrarList.get(arrarList.size() - 1)) 84 | + "\n " + 85 | "窗口内的事件数据:" + Joiner.on(",").join(eventTimeList) + "\n" + 86 | "实际窗起始和结束时间: " + sdf.format(window.getStart()) + "《----》" + sdf.format(window.getEnd()) + " \n \n "; 87 | 88 | out.collect(result); 89 | } 90 | }); 91 | //测试-把结果打印到控制台即可 92 | window.print(); 93 | 94 | //注意:因为flink是懒加载的,所以必须调用execute方法,上面的代码才会执行 95 | env.execute("eventtime-watermark"); 96 | 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/cn/sevenyuan/watermark/WordCountPeriodicWatermarks.java: -------------------------------------------------------------------------------- 1 | package cn.sevenyuan.watermark; 2 | 3 | import org.apache.flink.api.java.tuple.Tuple2; 4 | import org.apache.flink.streaming.api.functions.AssignerWithPeriodicWatermarks; 5 | import org.apache.flink.streaming.api.watermark.Watermark; 6 | 7 | import java.text.SimpleDateFormat; 8 | 9 | /** 10 | * 计数,周期性生成水印 11 | * 12 | * @author JingQ at 2019-12-01 13 | */ 14 | public class WordCountPeriodicWatermarks implements AssignerWithPeriodicWatermarks> { 15 | 16 | private Long currentMaxTimestamp = 0L; 17 | 18 | // 最大允许的乱序时间是 3 s 19 | private final Long maxOutOfOrderness = 3000L; 20 | 21 | private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); 22 | 23 | @Override 24 | public Watermark getCurrentWatermark() { 25 | return new Watermark(currentMaxTimestamp - maxOutOfOrderness); 26 | } 27 | 28 | 29 | @Override 30 | public long extractTimestamp(Tuple2 element, long previousElementTimestamp) { 31 | //定义如何提取timestamp 32 | long timestamp = element.f1; 33 | currentMaxTimestamp = Math.max(timestamp, currentMaxTimestamp); 34 | long id = Thread.currentThread().getId(); 35 | System.out.println("线程 ID :"+ id + 36 | " 键值 :" + element.f0 + 37 | ",事件事件:[ "+sdf.format(element.f1)+ 38 | " ],currentMaxTimestamp:[ "+ 39 | sdf.format(currentMaxTimestamp)+" ],水印时间:[ "+ 40 | sdf.format(getCurrentWatermark().getTimestamp())+" ]"); 41 | return timestamp; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/cn/sevenyuan/wordcount/SocketTextStreamWordCount.java: -------------------------------------------------------------------------------- 1 | package cn.sevenyuan.wordcount; import org.apache.flink.api.common.functions.FlatMapFunction; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.util.Collector; import org.apache.flink.api.java.tuple.Tuple2; /** * socket 数据源 * 统计输入文字的次数,输入通过空格分割 * 没有时间限制 * @author JingQ at 2019-09-21 */ public class SocketTextStreamWordCount { public static void main(String[] args) throws Exception { String hostName = "127.0.0.1"; int port = 9000; // 设置运行环境 StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); // 获取数据源 DataStreamSource stream = env.socketTextStream(hostName, port); // 计数 SingleOutputStreamOperator> sum = stream.flatMap((new LineSplitter())) .keyBy(0) .sum(1); // 输出 sum.print(); env.execute("Java Word from SocketTextStream Example"); } public static final class LineSplitter implements FlatMapFunction> { @Override public void flatMap(String s, Collector> collector) throws Exception { String[] tokens = s.toLowerCase().split("\\W+"); for (String token : tokens) { if (token.length() > 0) { collector.collect(new Tuple2(token, 1)); } } } } } -------------------------------------------------------------------------------- /src/main/java/cn/sevenyuan/wordcount/SocketWindowWordCount.java: -------------------------------------------------------------------------------- 1 | package cn.sevenyuan.wordcount; 2 | 3 | import cn.sevenyuan.domain.WordWithCount; 4 | import org.apache.flink.api.common.functions.FlatMapFunction; 5 | import org.apache.flink.api.common.functions.ReduceFunction; 6 | import org.apache.flink.api.java.utils.ParameterTool; 7 | import org.apache.flink.streaming.api.datastream.DataStream; 8 | import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; 9 | import org.apache.flink.streaming.api.windowing.time.Time; 10 | import org.apache.flink.util.Collector; 11 | 12 | /** 13 | * socket 数据源 14 | * 统计输入文字的次数,输入通过空格分割 15 | * 有时间限制,统计时间窗口为 5s 之内的字符 16 | * @author JingQ at 2019-09-18 17 | */ 18 | public class SocketWindowWordCount { 19 | 20 | public static void main(String[] args) throws Exception { 21 | 22 | // the port to connect to 23 | String hostName = "127.0.0.1"; 24 | int port = 9000; 25 | 26 | // get the execution environment 27 | final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); 28 | 29 | // get input data by connecting to the socket 30 | DataStream text = env.socketTextStream("localhost", port, "\n"); 31 | 32 | // parse the data, group it, window it, and aggregate the counts 33 | DataStream windowCounts = text 34 | .flatMap(new FlatMapFunction() { 35 | @Override 36 | public void flatMap(String value, Collector out) { 37 | for (String word : value.split("\\s")) { 38 | out.collect(new WordWithCount(word, 1L)); 39 | } 40 | } 41 | }) 42 | .keyBy("word") 43 | .timeWindow(Time.seconds(5), Time.seconds(1)) 44 | .reduce(new ReduceFunction() { 45 | @Override 46 | public WordWithCount reduce(WordWithCount a, WordWithCount b) { 47 | return new WordWithCount(a.getWord(), a.getCount() + b.getCount()); 48 | } 49 | }); 50 | 51 | // print the results with a single thread, rather than in parallel 52 | windowCounts.print().setParallelism(1); 53 | 54 | env.execute("Socket Window WordCount"); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/resources/datasource/student.txt: -------------------------------------------------------------------------------- 1 | 1 name1 21 a 2 | 2 name2 22 b 3 | 3 name3 23 c 4 | 4 name4 24 d 5 | 5 name5 25 e 6 | 1 name6 21 f 7 | 2 name7 22 g 8 | 3 name8 23 h 9 | 4 name9 24 i 10 | 5 name10 25 j 11 | 1 name11 26 k 12 | 2 name12 27 l 13 | 3 name13 28 m 14 | 4 name14 29 n 15 | 5 name15 30 o -------------------------------------------------------------------------------- /src/main/resources/datasource/student_min&minBy.txt: -------------------------------------------------------------------------------- 1 | 1 name19 21 a 2 | 1 name18 21 b 3 | 1 name17 21 c 4 | 1 name16 21 d 5 | 1 name15 21 e 6 | 1 name14 21 f 7 | 1 name13 21 g 8 | 1 name12 21 h 9 | 1 name11 21 i 10 | 1 name10 21 j 11 | 1 name9 20 k 12 | 1 name8 21 l 13 | 1 name7 21 m 14 | 1 name6 21 n 15 | 1 name5 21 o 16 | 1 name4 21 p 17 | 1 name3 21 q 18 | 1 name2 21 r 19 | 1 name1 10 s -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # Licensed to the Apache Software Foundation (ASF) under one 3 | # or more contributor license agreements. See the NOTICE file 4 | # distributed with this work for additional information 5 | # regarding copyright ownership. The ASF licenses this file 6 | # to you under the Apache License, Version 2.0 (the 7 | # "License"); you may not use this file except in compliance 8 | # with the License. You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | ################################################################################ 18 | 19 | log4j.rootLogger=INFO, console 20 | 21 | log4j.appender.console=org.apache.log4j.ConsoleAppender 22 | log4j.appender.console.layout=org.apache.log4j.PatternLayout 23 | log4j.appender.console.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p %-60c %x - %m%n 24 | --------------------------------------------------------------------------------