Flink学习笔记-Window简单示例
Flink学习(十四)Flink窗口、时间和水位线

Flink学习(⼗四)Flink窗⼝、时间和⽔位线Flink 框架中⽀持事件时间、摄⼊时间和处理时间三种。
⽽当我们在流式计算环境中数据从 Source 产⽣,再到转换和输出,这个过程由于⽹络和反压的原因会导致消息乱序。
因此,需要有⼀个机制来解决这个问题,这个特别的机制就是“⽔位线”。
Flink 的窗⼝和时间根据窗⼝数据划分的不同,⽬前 Flink ⽀持如下 3 种:滚动窗⼝,窗⼝数据有固定的⼤⼩,窗⼝中的数据不会叠加;滑动窗⼝,窗⼝数据有固定的⼤⼩,并且有⽣成间隔;会话窗⼝,窗⼝数据没有固定的⼤⼩,根据⽤户传⼊的参数进⾏划分,窗⼝数据⽆叠加。
Flink 中的时间分为三种:事件时间(Event Time),即事件实际发⽣的时间;摄⼊时间(Ingestion Time),事件进⼊流处理框架的时间;处理时间(Processing Time),事件被处理的时间。
下⾯的图详细说明了这三种时间的区别和联系:事件时间(Event Time事件时间(Event Time)指的是数据产⽣的时间,这个时间⼀般由数据⽣产⽅⾃⾝携带,⽐如 Kafka 消息,每个⽣成的消息中⾃带⼀个时间戳代表每条数据的产⽣时间。
Event Time 从消息的产⽣就诞⽣了,不会改变,也是我们使⽤最频繁的时间。
利⽤ Event Time 需要指定如何⽣成事件时间的“⽔印”,并且⼀般和窗⼝配合使⽤,具体会在下⾯的“⽔印”内容中详细讲解。
我们可以在代码中指定 Flink 系统使⽤的时间类型为 EventTime:final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();//设置时间属性为 EventTimeenv.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);DataStream<MyEvent> stream = env.addSource(new FlinkKafkaConsumer09<MyEvent>(topic, schema, props));stream.keyBy( (event) -> event.getUser() ).timeWindow(Time.hours(1)).reduce( (a, b) -> a.add(b) ).addSink(...);Flink 注册 EventTime 是通过 InternalTimerServiceImpl.registerEventTimeTimer 来实现的可以看到,该⽅法有两个⼊参:namespace 和 time,其中 time 是触发定时器的时间,namespace 则被构造成为⼀个 TimerHeapInternalTimer 对象,然后将其放⼊KeyGroupedInternalPriorityQueue 队列中。
flink的windowall的用法

Flink的windowAll的用法什么是Flink的windowAllFlink是一个快速且可扩展的开源流处理框架,它提供了丰富的窗口操作函数来处理无限流数据。
窗口操作是流处理中的一个重要概念,它允许我们按照一定的规则将数据划分为有限大小的“窗口”,并对每个窗口进行聚合、转换或分析操作。
在Flink中,窗口操作可以根据不同的需求进行定义,如时间窗口、计数窗口和会话窗口等。
而windowAll是Flink提供的一种特殊的窗口操作,它与其他窗口操作不同之处在于,windowAll针对整个流数据集进行操作,而不对个别元素或者特定的键进行操作。
windowAll可以将所有来自输入流的元素分配到窗口中,然后执行聚合、排序或者其他操作,最后产生输出结果。
在本文中,我们将深入探讨Flink中windowAll的用法及实例,并介绍如何使用它来解决实际的数据处理问题。
使用windowAll的场景windowAll适用于那些不需要按照特定的键或者条件对数据进行分组操作,而是希望对整个数据集进行窗口操作的场景。
通常情况下,windowAll用于以下两种场景:1.数据预处理:在一些应用中,我们可能希望在对数据进行分组和聚合之前,对整个数据集进行一些数据清洗或者准备工作。
这时候可以使用windowAll对整个数据集进行操作,将不同的数据归并到一起,然后进行预处理操作。
2.数据分析和排序:有时候,我们需要对整个数据集进行一些全局性的分析或者排序操作。
例如,我们要对一个无限的数字流进行累加操作,不关心元素之间的关系,只需要将所有输入元素累加起来。
这种情况下,windowAll非常适合。
除了以上两种场景,还有其他一些特殊的应用场景,可能需要使用windowAll来处理数据。
在实际使用中,我们需要根据具体的需求来决定是否使用windowAll。
windowAll的使用方法使用windowAll的关键是定义窗口的类型和规则。
Flink支持多种窗口类型,如时间窗口、计数窗口、会话窗口等,每种窗口都有其特定的属性和使用条件。
Flink学习笔记

一、计算引擎的发展首先第一代的计算引擎,无疑就是 Hadoop 承载的 MapReduce。
这里大家应该都不会对MapReduce 陌生,它将计算分为两个阶段,分别为 Map 和 Reduce。
对于上层应用来说,就不得不想方设法去拆分算法,甚至于不得不在上层应用实现多个 Job 的串联,以完成一个完整的算法,例如迭代计算。
由于这样的弊端,催生了支持 DAG 框架的产生。
因此,支持 DAG 的框架被划分为第二代计算引擎。
如 Tez 以及更上层的 Oozie。
这里我们不去细究各种 DAG 实现之间的区别,不过对于当时的 Tez 和 Oozie 来说,大多还是批处理的任务。
接下来就是以 Spark 为代表的第三代的计算引擎。
第三代计算引擎的特点主要是 Job 内部的DAG 支持(不跨越Job),以及强调的实时计算。
在这里,很多人也会认为第三代计算引擎也能够很好的运行批处理的 Job。
随着第三代计算引擎的出现,促进了上层应用快速发展,例如各种迭代计算的性能以及对流计算和 SQL 等的支持。
Flixxxxnk 的诞生就被归在了第四代。
这应该主要表现在 Flixxxxnk 对流计算的支持,以及更一步的实时性上面。
当然Flixxxxnk 也可以支持 Batch 的任务,以及DAG 的运算。
①DAG是什么DAG(Directed Acyclic Graph)有向无环图。
有向无环图指的是一个无回路的有向图。
如果有一个非有向无环图,且A点出发向B经C可回到A,形成一个环。
将从C到A的边方向改为从A到C,则变成有向无环图。
有向无环图的生成树个数等于入度非零的节点的入度积。
在中,如果一个无法从某个顶点出发经过若干条边回到该点,则这个图是一个有向无环图(DAG图)。
图是数据结构中最复杂的,泛指无组织的结构体由关联关系形成图。
DAG(有向无环图)是拓扑图的一种,比较有规律,适合做血缘关系描述。
深度挖掘,机器学习,统计学都是跟图有关的,由于图不像其他数据结构一样有一个合理的组织,因此寻址的时间复杂度和空间复杂度一般都超过O(n的平方),就是要用双倍的内存或时间来查找整个图。
flink window 代码实例

flink window 代码实例Flink是一个比较优秀的分布式流处理框架,它的window窗口是一个很重要的特性。
它允许我们对无界流数据进行切分,以一定的规则对这些数据进行分析计算,这也是实时流处理的核心问题。
那么下面我们就来看一下如何通过flink的window API来实现这个特性。
首先我们要明确两个概念:窗口和触发器。
窗口是一个固定大小的区间,而触发器是一种可以定义何时执行计算的规则。
窗口的划分方式有很多种,例如按照时间、按照数据数量、按照事件等方式来划分。
而触发器可以根据需求设定,例如固定时间触发、数据数量触发等等。
接下来,我们就将分步骤来具体讲解如何实现flink的window 功能。
1. 引入依赖在pom.xml中增加以下依赖:```<dependency><groupId>org.apache.flink</groupId><artifactId>flink-java</artifactId><version>${flink.version}</version></dependency><dependency><groupId>org.apache.flink</groupId><artifactId>flink-streaming-java_${scala.binary.version}</artifactId><version>${flink.version}</version></dependency>```2. 连接到Flink集群我们需要首先连接到一个Flink集群才能进行后续操作:```StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();env.setParallelism(1); // 设置并行数为1env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime); // 设置时间属性为EventTime```3. 定义数据流可以从文件中读取数据,也可以从其他数据源中读取数据。
FlinkCumulateWindow

FlinkCumulateWindowFlink 累计窗⼝之前⼀直⽐较遗憾,Flink Sql 没有 Trigger 功能,长时间的窗⼝不能在中途触发计算,输出中间结果。
⽽很多实时指标是⼩时、天级的累集窗⼝,⽐如⼤屏中的当⽇ pv、uv,整体是⼀天中所有访问的次数和⽤户数,但是需要实时更新,⽐如每 10S 更新⼀次截⽌到当前的pv、uv。
这种场景使⽤ Streaming Api 很容易实现,就是个天的翻滚窗⼝加上 10S 的 Trigger 就可以了.windowAll(TumblingEventTimeWindows.of(Time.days(1), Time.hours(-8))).trigger(ContinuousProcessingTimeTrigger.of(Time.seconds(10)))Flink 1.13 版本以前,Sql 中还不能实现这个功能,1.13 添加了 CUMULATE 窗⼝,可以⽀持这种场景。
(感谢 antigeneral 同学提醒有这个功能)以下为官⽹介绍:累积窗⼝在某些情况下⾮常有⽤,例如在固定窗⼝间隔内提前触发的滚动窗⼝。
例如,每⽇仪表板绘制从 00:00 到10:00 处每分钟累积的 UV, UV 表⽰从 00:00 到 10:00 的 UV 总数。
这可以通过 CUMULATE 窗⼝轻松地实现。
CUMULATE 函数将元素分配给覆盖初始步长间隔内的窗⼝,并每⼀步扩展到⼀个步长(保持窗⼝开始固定),直到最⼤窗⼝⼤⼩。
您可以将 CUMULATE 函数视为⾸先应⽤ TUMBLE 最⼤窗⼝⼤⼩的窗⼝,然后将每个滚动窗⼝拆分为具有相同窗⼝开例如,您可以有⼀个 1 ⼩时步长和 1 天最⼤⼤⼩的累积窗⼝,并且您将获得每天的窗⼝:[00:00, 01:00), [00:00, 02:00), [00:00, 03:00), ..., [00:00, 24:00)。
这些CUMULATE函数根据列分配窗⼝。
flink的windowall的用法

flink的windowall的用法Flink中的`WindowAll`是一种窗口类型,它适用于在整个数据流上执行滚动窗口计算。
与其他窗口类型不同,`WindowAll`不需要将输入流分区,因此适用于全局计算场景。
要使用`WindowAll`,需要使用`ExecutionEnvironment`创建一个批处理环境,并使用`env.fromElements()`将数据加载到DataSet 中。
然后,可以使用`windowAll()`方法指定窗口大小和滑动间隔,并通过`apply()`方法传递自定义的窗口函数来执行计算。
例如,以下代码将创建一个DataSet,并使用`WindowAll`在整个数据集上执行滚动窗口计算:```val env = ExecutionEnvironment.getExecutionEnvironmentval data: DataSet[(String, Int)] = env.fromElements(("A", 1), ("B", 2), ("C", 3), ("D", 4), ("E", 5))// 每两个元素作为一个窗口,滑动步长为1val result = data.windowAll(SlidingProcessingTimeWindows.of(Time.seconds(2), Time.seconds(1))).apply(new WindowFunction[(String, Int), String, TimeWindow] {override def apply(window: TimeWindow, input:Iterable[(String, Int)], out: Collector[String]): Unit = {var sum = 0for (elem <- input) {sum += elem._2}out.collect(s"Window ${window.getStart}-${window.getEnd}: $sum")}})```此代码将会在每两个元素作为一个窗口,滑动步长为1的情况下,统计数据集合中每个窗口的元素值之和。
flink sql window join写法

flink sql window join写法一、概述Flink SQL窗口连接(Window Join)是一种在Flink SQL中实现窗口操作和连接的语法,用于对数据进行窗口化处理并执行连接操作。
窗口连接允许在数据窗口内对数据进行聚合、过滤和比较,从而获得更精细的数据分析结果。
二、窗口连接语法Flink SQL窗口连接的语法如下:```sqlSELECT 列1, 列2, ..., 窗口函数(列)FROM 表WINDOW window_name AS (PARTITION BY 列1 ORDER BY 列2)```其中,`SELECT`语句用于指定要选择的列和窗口函数;`FROM`语句指定要连接的表;`WINDOW`语句定义了窗口名称和窗口定义,包括分区(`PARTITION BY`)和排序(`ORDER BY`)子句。
三、窗口函数的用法窗口函数用于在窗口内对数据进行操作,常见的窗口函数包括聚合函数(如`SUM`、`AVG`、`COUNT`等)、过滤函数(如`FILTER`)和比较函数(如`RANK`、`ROW_NUMBER`等)。
以下是一个使用窗口函数的示例:```sqlSELECT product_id, SUM(sales_amount) OVER (PARTITION BYproduct_category ORDER BY sale_date) AS total_salesFROM sales_tableWINDOW product_category AS (PARTITION BY product_id), sale_date AS (ORDER BY sale_time)```上述示例中,使用`SUM`聚合函数对每个产品类别内的销售金额进行求和,并按销售日期进行排序。
通过窗口连接,可以在一个查询中获取每个产品类别在不同时间段的总销售额。
四、注意事项在使用窗口连接时,需要注意以下几点:1. 窗口函数的参数可以自定义,根据需要选择合适的函数和参数。
flux.window的用法

flux.window的用法Flux是一款流行的前端框架,用于构建现代化的Web应用程序。
在Flux 中,window对象用于管理应用程序的全局状态和事件。
本文将介绍flux.window 的用法,包括其基本概念、使用方法和常见用法示例。
一、基本概念Flux.window是Flux框架中的一个重要组成部分,它提供了一种用于管理应用程序全局状态和事件的方式。
在Flux应用程序中,window对象通常作为应用程序的根容器,用于容纳其他组件和状态管理对象。
Flux.window对象具有以下特点:1.它是应用程序的根容器,容纳其他组件和状态管理对象。
2.它提供了访问应用程序状态和事件的方法,例如getAppState()和handleEvent()。
3.它允许组件之间进行通信和交互,从而实现应用程序的协作和一致性。
二、使用方法使用Flux.window对象,您可以轻松地管理应用程序的状态和事件。
以下是使用Flux.window的一些基本方法:1.创建Flux.window对象:您可以使用Flux框架提供的构造函数来创建一个window对象。
例如:varwindow=newFlux.Window();2.访问应用程序状态:您可以使用getAppState()方法来访问应用程序的状态。
该方法返回一个包含应用程序状态的对象的引用。
例如:varappState=window.getAppState();3.处理应用程序事件:您可以使用handleEvent()方法来处理应用程序的事件。
该方法接受一个事件对象作为参数,并返回一个布尔值表示是否处理了该事件。
例如:window.handleEvent(eventObject);4.注册组件:您可以将组件注册到窗口对象中,以便在需要时访问它们的状态和事件。
例如:window.registerComponent("myComponent",MyComponent);5.发布事件:您可以使用窗口对象来发布自定义事件,以便其他组件可以监听这些事件并进行相应的处理。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Flink学习笔记-Window简单示例WindowFlink中Window可以将无限流切分成有限流,是处理有限流的核心组件,现在Flink中Window可以是时间驱动的Time Window,也可以是数据驱动的Count Window。
基于时间的窗口操作:在每个相同的时间间隔对Stream中的记录进行处理,通常各个时间间隔内的窗口操作处理的记录数不固定。
基于数据驱动的窗口操作:可以在Stream中选择固定数量的记录作为一个窗口,对该窗口中的记录进行处理。
窗口类型Tumbling Window(滚动窗口):窗口间的元素无重复一个翻滚窗口分配器的每个数据元分配给指定的窗口的窗口大小。
翻滚窗具有固定的尺寸,不重叠。
Sliding Window(滑动窗口):窗口间的元素可能重复该滑动窗口分配器分配元件以固定长度的窗口。
与翻滚窗口分配器类似,窗口大小由窗口大小参数配置。
附加的窗口滑动参数控制滑动窗口的启动频率。
因此,如果幻灯片小于窗口大小,则滑动窗口可以重叠。
在这种情况下,数据元被分配给多个窗口。
Session Window(会话窗口)在会话窗口中按活动会话分配器组中的数据元。
与翻滚窗口和滑动窗口相比,会话窗口不重叠并且没有固定的开始和结束时间。
相反,当会话窗口在一段时间内没有接收到数据元时,即当发生不活动的间隙时,会关闭会话窗口。
会话窗口分配器可以配置静态会话间隙或会话间隙提取器函数,该函数定义不活动时间段的长度。
当此期限到期时,当前会话将关闭,后续数据元将分配给新的会话窗口。
Global Window(全局窗口)一个全局性的窗口分配器分配使用相同的Keys相同的单个的所有数据元全局窗口。
此窗口方案仅在您还指定自定义触发器时才有用。
否则,将不执行任何计算,因为全局窗口没有我们可以处理聚合数据元的自然结束。
Window Function在窗口触发后,负责对窗口内的元素进行计算。
Window Function分为两类: 增量聚合和全量聚合。
增量聚合: 窗口不维护原始数据,只维护中间结果,每次基于中间结果和增量数据进行聚合。
如: ReduceFunction、AggregateFunction等。
全量聚合: 窗口需要维护全部原始数据,窗口触发进行全量聚合。
如:ProcessWindowFunction。
Time的分类Event-Time :事件时间是每个事件在其生产设备上发生的时间。
Ingestion-Time :摄取时间是事件进入Flink的时间。
Processing-Time :处理时间是指执行相应算子操作的机器的系统时间。
不设置Time 类型,默认是processingTime。
可以如下方式修改时间特性:env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);Watermark主要解决延迟数据、乱序问题Watermark是Apache Flink为了处理EventTime窗口计算提出的一种机制,本质上也是一种时间戳,由Apache Flink Source或者自定义的Watermark生成器按照需求Punctuated或者Periodic两种方式生成的一种系统Event,与普通数据流Event一样流转到对应的下游算子,接收到Watermark Event的算子以此不断调整自己管理的EventTime Clock。
Watermark两种生成方式:Periodic - 周期性(一定时间间隔或者达到一定的记录条数)产生一个Watermark,默认周期为200毫秒。
在实际的生产中Periodic的方式必须结合时间和积累条数两个维度继续周期性产生Watermark,否则在极端情况下会有很大的延时。
接口 AssignerWithPeriodicWatermarksPunctuated:数据流中每一个递增的EventTime都会产生一个Watermark。
没有时间周期规律,可打断的生成Watermark。
在实际的生产中Punctuated方式在TPS很高的场景下会产生大量的Watermark在一定程度上对下游算子造成压力,所以只有在实时性要求非常高的场景才会选择Punctuated的方式进行Watermark 的生成。
接口 AssignerWithPunctuatedWatermarks在基于Event-Time的流处理应用中,每个数据有两个必需的信息:时间戳:事件发生的时间Watermark:算子通过Watermark推断当前的事件时间。
Watermark用于通知算子没有比水位更小的时间戳的事件会发生了。
基于时间的窗口会根据事件时间将一个数据分配给某个窗口。
每个时间窗口都有一个开始时间戳和结束时间戳。
所有内置的窗口分配器都会提供一个默认的触发器,一旦时间超过某个窗口的结束时间,触发器就会触发对这个窗口的计算。
API简单示例import java.sql.Timestampimport java.text.SimpleDateFormatimport java.util.Propertiesimport mon.functions.AggregateFunctionimport mon.serialization.SimpleStringSchemaimport mon.state.{ListState, ListStateDescriptor}import org.apache.flink.configuration.Configurationimport org.apache.flink.streaming.api.TimeCharacteristicimportorg.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor import org.apache.flink.streaming.api.functions.{AssignerWithPeriodicWatermarks, AssignerWithPunctuatedWatermarks, KeyedProcessFunction}import org.apache.flink.streaming.api.scala._import org.apache.flink.streaming.api.scala.function.WindowFunctionimport org.apache.flink.streaming.api.watermark.Watermarkimport org.apache.flink.streaming.api.windowing.time.Timeimport org.apache.flink.streaming.api.windowing.windows.TimeWindowimport org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumerimport org.apache.flink.util.Collectorimport scala.collection.mutable.ListBuffercase class UserBehavior(userId:Long, itemId:Long, behavior:String, timestamp:Long)case class ItemCount(itemId:Long, count:Long, timestamp:Long)object TopNHotItemsGenerator {def main(args: Array[String]): Unit = {val env = StreamExecutionEnvironment.getExecutionEnvironmentenv.setParallelism(1)// 设置水位间隔,默认200毫秒env.getConfig.setAutoWatermarkInterval(100)// 设置时间特性env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)val dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss")val properties = new Properties()properties.setProperty("bootstrap.servers", "192.168.0.1:9092")properties.setProperty("group.id", "tmp-1")properties.setProperty("key.deserializer","mon.serialization.StringDeserializer")properties.setProperty("value.deserializer","mon.serialization.StringDeserializer")properties.setProperty("auto.offset.reset", "latest")val dataStream = env.addSource(new FlinkKafkaConsumer[String]("tmp1", new SimpleStringSchema(), properties)).map(data => {val dataArray = data.split(",")UserBehavior(dataArray(0).trim().toLong, dataArray(1).trim().toLong,dataArray(2).trim(), dateFormat.parse(dataArray(3).trim()).getTime)}).assignAscendingTimestamps(_.timestamp) // 数据中提取时间戳/** 定义水位生成方式.assignTimestampsAndWatermarks(newBoundedOutOfOrdernessTimestampExtractor[UserBehavior](Time.seconds(1)) {override def extractTimestamp(t: UserBehavior): Long = {t.timestamp}}).assignAscendingTimestamps(new PeriodicWatermarksAssigner()).assignAscendingTimestamps(new PunctuatedWatermarksAssigner())*/dataStream.print()val aggregateStream = dataStream.filter(_.behavior == "click").keyBy(_.itemId).timeWindow(Time.hours(1), Time.minutes(5)).aggregate(new CountAggregate(), new WindowAggregateResult()) // 窗口聚合aggregateStream.print()val processStream = aggregateStream.keyBy(_.timestamp).process(new TopNHotItemsProcessFunction(2))processStream.print()env.execute("topn hot items")}}class CountAggregate() extends AggregateFunction[UserBehavior, Long, Long] {override def createAccumulator(): Long = 0Loverride def add(in: UserBehavior, acc: Long): Long = acc + 1override def getResult(acc: Long): Long = accoverride def merge(acc: Long, acc1: Long): Long = acc + acc1}class AvgAggregate() extends AggregateFunction[UserBehavior, (Long, Long), Double] {override def createAccumulator(): (Long, Long) = (0L, 0L)override def add(in: UserBehavior, acc: (Long, Long)): (Long, Long) = (acc._1 + in.timestamp, acc._2 + 1)override def getResult(acc: (Long, Long)): Double = acc._1 / acc._2override def merge(acc: (Long, Long), acc1: (Long, Long)): (Long, Long) = (acc._1 + acc1._1, acc._2 + acc1._2)}class WindowAggregateResult() extends WindowFunction[Long, ItemCount, Long, TimeWindow] {override def apply(key: Long, window: TimeWindow, input: Iterable[Long], out: Collector[ItemCount]): Unit = {out.collect(ItemCount(key, input.iterator.next(), window.getEnd))}}class TopNHotItemsProcessFunction(topN: Int) extends KeyedProcessFunction[Long, ItemCount, String] {var itemsState:ListState[ItemCount] = _override def open(parameters: Configuration): Unit = {super.open(parameters)itemsState = getRuntimeContext.getListState(new ListStateDescriptor[ItemCount]("hot-items-state", classOf[ItemCount]))}override def processElement(value: ItemCount, context: KeyedProcessFunction[Long, ItemCount, String]#Context, collector: Collector[String]): Unit = {itemsState.add(value)// 注册事件时间定时器context.timerService().registerEventTimeTimer(value.timestamp + 1)}override def onTimer(timestamp: Long, ctx: KeyedProcessFunction[Long, ItemCount, String]#OnTimerContext, out: Collector[String]): Unit = {val items:ListBuffer[ItemCount] = new ListBuffer[ItemCount]()import scala.collection.JavaConversions._for (item <- itemsState.get()) {items += item}val topNHotItems = items.sortBy(_.count)(Ordering.Long.reverse).take(topN)val result: StringBuilder = new StringBuilder()result.append("time: ").append(new Timestamp(timestamp - 1)).append("\n")for (i <- topNHotItems.indices) {val hotItem = topNHotItems(i)result.append("top%d: itemId %d count %d".format(i + 1, hotItem.itemId, hotItem.count)).append("\n")}result.append("====================")out.collect(result.toString())itemsState.clear()}}class PeriodicWatermarksAssigner extends AssignerWithPeriodicWatermarks[UserBehavior] {val bound: Long = 60000; //延时一分钟var maxTs: Long = Long.MinValue //最大时间戳override def getCurrentWatermark: Watermark = {new Watermark(maxTs - bound)}override def extractTimestamp(element: UserBehavior, l: Long): Long = {maxTs = maxTs.max(element.timestamp)element.timestamp}}class PunctuatedWatermarksAssigner extends AssignerWithPunctuatedWatermarks[UserBehavior] { val bound: Long = 60000; //延时一分钟override def checkAndGetNextWatermark(element: UserBehavior, l: Long): Watermark = {if (element.behavior == "") {new Watermark(l - bound)} else {null}}override def extractTimestamp(element: UserBehavior, l: Long): Long = element.timestamp}// 侧输出流处理class CProcessFunctioin extends ProcessFunction[(String, Int), String] {lazy val outputTag: OutputTag[String] = new OutputTag[String]("coutput")override def processElement(element: (String, Int), context: ProcessFunction[(String, Int), String]#Context, collector: Collector[String]): Unit = {if (element._2 < 15) {context.output(outputTag, "cot %s %d".format(element._1, element._2))} else {collector.collect("%s %d".format(element._1, element._2))}}}。