Flink中的时间

及时流处理 是有状态流处理的扩展,实现及时流处理的 时间 起到了很大的作用。

在Flink的时间概念中主要分为下面两种:

事件时间: 事件时间是每个单独事件在其生产设备上发生的时间。

处理时间: 处理时间是指执行相应操作的机器的系统时间。

flink mysql cdc 修改时区 flink 时间戳_字符串


在Flink中为了衡量事件时间的进度,引入了 watermark 机制。

watermark 将作为数据流的一部分流动,而且带有时间戳的属性,如此在 乱序流 中,我们就可以判断出哪些事件是迟到的(即后面到达的事件的时间戳小于此时的水印)。

flink mysql cdc 修改时区 flink 时间戳_时间戳_02



watermark的生成

在java中想要分配时间戳和水印需要用到 assignTimestampsAndWatermarks() 方法:

// 有序流生成watermark
        source.assignTimestampsAndWatermarks(WatermarkStrategy
                .<String>forMonotonousTimestamps()
                .withTimestampAssigner(new SerializableTimestampAssigner<String>() { // 得到时间戳
                    @Override
                    public long extractTimestamp(String s, long l) {
                        // 我这里是字符串,字符串的格式为 timestamp msg
                        // 当然你也可以有自己的POJO类,并且里面有timestamp这个属性,你就可以直接返回该属性了
                        return Long.parseLong(s.split(" ")[0]); // 返回时间戳,单位是毫秒
                    }
                }));

        // 无序流生成watermark
        source.assignTimestampsAndWatermarks(WatermarkStrategy
                .<String>forBoundedOutOfOrderness(
                        Duration.ofSeconds(2)) // 允许迟到时间2秒
                .withTimestampAssigner(new SerializableTimestampAssigner<String>() { // 得到时间戳
                    @Override
                    public long extractTimestamp(String s, long l) {
                        // 我这里是字符串,字符串的格式为 timestamp msg
                        // 当然你也可以有自己的POJO类,并且里面有timestamp这个属性,你就可以直接返回该属性了
                        return Long.parseLong(s.split(" ")[0]); // 返回时间戳,单位是毫秒
                    }
                }));


Flink中的窗口

聚合事件(例如,计数、总和)在流上的工作方式与批处理不同。因为流是不断的,不可能将流中的所有元素进行计算。

流上的聚合(计数、总和等)由窗口限定,

例如 “过去 5 分钟的计数”“最后 100 个元素的总和” 都算是一种窗口,只不过前者是 时间驱动 的后者是 数据驱动 的。

上面的分类只是大致的,而按照不同的分类窗口划分为以下几类:

滚动窗口: 将每个元素分配给指定窗口大小的窗口。滚动窗口具有固定大小并且不重叠。

滑动窗口: 将元素分配给固定长度的窗口。类似滚动窗口,但是允许重叠(一个元素分配给多个窗口)

会话窗口: 按活动会话对元素进行分组。会话窗口不重叠,没有固定的开始和结束时间。当会话窗口在一段时间内没有接收到元素时,当出现不活动间隙时,会话窗口将关闭。

全局窗口: 将具有相同键的所有元素分配给同一个全局窗口 。全局窗口没有自然结束,若不指定自定义触发器则不会计算。


更详细的可以看官方窗口文档


窗口函数

在定义了窗口分配器之后,我们需要使用 window function 指定我们想要在每个窗口上执行的计算。

在流上使用窗口的步骤大致如下:

flink mysql cdc 修改时区 flink 时间戳_时间戳_03

source
                .assignTimestampsAndWatermarks(WatermarkStrategy // 设置watermark
                        // Event自定义类,其中有Timestamp类型属性timestamp
                        .<Event>forBoundedOutOfOrderness(Duration.ofSeconds(2)) // 乱序流、允许迟到2秒
                        // 指定时间戳
                        .withTimestampAssigner((SerializableTimestampAssigner<Event>) (event, l) -> event.getTimestamp().getTime())
                )
                .keyBy(Event::getUser) // 按Event的user属性分组
                .window(
                        // 参数:窗口长度,偏移量
                        TumblingEventTimeWindows.of(Time.hours(2), Time.hours(0)) // 2小时的滚动事件时间窗口
                        // 其它窗口
                        // 滑动   SlidingEventTimeWindows
                        // 会话   EventTimeSessionWindows
                )
                .maxBy("timestamp")  // 简单用个max求最大时间戳举例
                .print();  // sink输出

使用归约函数会有一个问题,就是气要求输入类型和输出类型一致,并不够灵活,于是在窗口函数中Flink还提供了聚合函数 AggregateFunction , 它有三个参数 输入类型(IN)、累加器类型(ACC)、输出类型(OUT) ,这样就使得整个流结果的输出更加灵活。