实时大数据计算中,Flink的滑动窗口,乱序,允许消息迟到机制


前几天写了《实时大数据计算中,Spark的滑动窗口和允许消息迟到机制》,这几天一时兴起,对Flink的实时计算机制进行记录。

乱序和迟到的定义

Flink的实时计算控制好像比Spark多出了一个,就是关于乱序数据的处理,在Flink的定义中,乱序与迟到由相同点和不同点,首先相同点在于,它们都指代,处于时间窗口之外的数据,其中乱序范围称为乱序界,迟到范围称为迟到界,每个窗口都有自己的乱序界和迟到界,以窗口W1为例,假设窗口范围是 [00:00,00:05) ,允许乱序时间为10s,迟到时间为30s。

         窗口                    ──  allowedLateness=30s  ──
          │                     │                           │
          ▼                     ▼                           ▼
00:00   00:05 ←  乱序界10s  →  00:15  ←    迟到界30s     →  00:35
          ▲                     ▲                           ▲
          │                     │                           │
       乱序阶段             允许迟到阶段                  彻底关闭
      (等10秒)            (再等30秒)                  (丢弃)

乱序和迟到机制的设置一般要跟窗口绑定,才会生效。没有窗口了,数据一般就是来一条处理一条,输出一条,代码层面不影响编译。

窗口计算机制,乱序消息,迟到消息

在Spark Streaming中,窗口时间一到达,就会触发一次窗口内计算,后续针对迟到的数据还会再算一次,但是这哥操作算作更新。在Flink中,窗口的计算是由消息的事件时间来决定的,只是如果设置的策略是noWatermarks(),那么窗口计算的触发机制就是按照窗口事件来计算,这个比较简单,这里不作讨论。
如果一段连续的消息,它们的事件时间如下所示,从左到右,代表消息依次到达Flink程序。

00:01  00:02

这两条消息,对应的事件时间分别是00:01,00:02。最大事件时间是00:02,达不到触发窗口计算的要求,此时数据会积累起来,不会去计算。
【Note1:消息到达Flink的时间,即,程序处理时间,不参与窗口计算触发机制】

知识点1:消息到达Flink的时间,即,程序处理时间,不参与窗口计算触发机制。

知识点2:程序始终会去记录并更新最大事件时间。

进一步考虑,接下来又来了两条消息,它们的事件时间分别是00:07,00:04。如下所示。

00:01  00:02  00:07  00:04  00:09  00:08

此时最大事件时间来到了00:07,超出了窗口区间的右端点,此时如果出现窗口范围内的消息,就称为乱序消息。但依然不会触发窗口计算。事件时间为00:04的消息累积在W1范围内,00:07,00:09,00:08这三条消息累积在W2范围内。
进一步考虑,接下来出现了一条消息,如下所示。

00:01  00:02  00:03  00:07  00:04  00:09  00:08  00:15

这个消息超出了W1的乱序界,触发W1的窗口计算。数据 00:01,00:02,00:03,00:04,会按照预设的窗口进行计算。此时可以推断,触发窗口计算的代码逻辑是

int watermask = MaxEventTime - 10s;
if(watermask >= window_right){
   triggerWindow();
}

其中,window_right是窗口区间的右端。一旦计算,窗口关闭。
再进一步,如果后续还继续出现需要在W1中计算的数据,如下所示

00:01  00:02  00:07  00:04  00:09  00:08  00:15  00:03

那么这个时间事件为00:03的消息被称为迟到消息,这个消息依然会纳入W1窗口内计算,只是这次计算算作是更新操作。迟到更新是针对每条迟到数据进行判定的,即,允许每条消息比自身事件时间迟到30s,具体判断逻辑如下

Watermark >= window_end 且 eventTime < Watermark 且 eventTime + allowedLateness >= Watermark

这三个判断条件分别代表窗口已关闭该数据属于迟到数据仍在宽限期内
前两个比较好理解,但是第三个,仍在宽限期内可能不大好理解。但这样解释会清晰些,即,“当前的最大事件时间还没有到我不能迟到的程序,所以我还能进去参与计算”。

代码实现

核心创建数据的类

package com.edata.bigdata.flink;

import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.api.common.serialization.DeserializationSchema;
import org.apache.flink.connector.kafka.source.KafkaSource;
import org.apache.flink.connector.kafka.source.enumerator.initializer.OffsetsInitializer;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.WindowedStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.assigners.SlidingEventTimeWindows;
import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
import org.apache.kafka.clients.consumer.OffsetResetStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Duration;
import java.util.Properties;
import java.io.Serializable;


public class StreamSource implements Serializable {

    public Logger logger = LoggerFactory.getLogger(this.getClass());
    public StreamExecutionEnvironment env;
    //yyyy-MM-dd HH:mm:ss:SSS
    public String dateFormat;


    public <T> DataStream<T> createDataStream(Properties sourceProps,
                                              Properties offsetProps,
                                              long maxOutOfOrderSecond,
                                              long idleness,
                                              Class<T> clazz) {


        DeserializationSchema<T> jsonSchema = new JsonDeserializationSchema<>(clazz);
        KafkaSource<T> kafkaSource = KafkaSource
                .<T>builder()
                .setBootstrapServers(sourceProps.getProperty("kafka.bootstrap.servers"))
                .setTopics(sourceProps.getProperty("subscribe"))
                .setGroupId(sourceProps.getProperty("group.id"))
                .setValueOnlyDeserializer(jsonSchema)
                .setStartingOffsets(OffsetsInitializer.committedOffsets(OffsetResetStrategy.EARLIEST))
                .setProperties(offsetProps)
                .build();

        WatermarkStrategy<T> watermarkStrategy = WatermarkStrategy
                .<T>forBoundedOutOfOrderness(Duration.ofSeconds(maxOutOfOrderSecond))
                .withTimestampAssigner(new EventTimeExtractor<>(clazz, dateFormat))
                .withIdleness(Duration.ofSeconds(idleness));
//        WatermarkStrategy<T> watermarkStrategy = WatermarkStrategy.noWatermarks();
        return env.fromSource(kafkaSource, watermarkStrategy, "Kafka Source");

    }

    public <T> WindowedStream<T, String, TimeWindow> applySlidingWindows(
            DataStream<T> dataStream,
            long windowSize,
            long slidingSize,
            long lateness,
            Class<T> clazz) {

        //先根据key进行分组,相同key的数据会分到一起,作为一组,每个组有各自的统计窗口。
        return dataStream.keyBy(new EventKeyExtractor<>(clazz))
                .window(SlidingEventTimeWindows.of(Duration.ofSeconds(windowSize), Duration.ofSeconds(slidingSize)))
                .allowedLateness(Duration.ofSeconds(lateness));
    }
    public void start(String jobName) throws Exception {
        if (env != null) {
            env.execute(jobName);
        }
    }
    public StreamSource(StreamExecutionEnvironment env, String dateFormat) {
        this.env = env;
        this.dateFormat = dateFormat;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值