Flink详细笔记(十一)Flink 之 EventTime、WaterMarks

1 EventTime + WaterMarks(延迟机制)处理实时数据

  接下来,我们从并行Source 和 非并行Source 两个方向,来使用 EventTime 处理实时数据。(接下来示例,设置延迟为0s,即不延迟)

1.1 非并行Source


       非并行Source,以 socketTextStream为例来介绍 Flink使用 EventTime 处理实时数据。

1.1.1 代码实现

/**
 * TODO 非并行Source EventTime
 *
 * @author 多易教育-行哥
 * @Date 2020-06-18
 */
public class EventTimeDemo {

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

        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        //设置EventTime作为时间标准
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);

        //读取source,并指定(1581490623000,Mary,3)中哪个字段为EventTime时间
        //WaterMarks:是Flink中窗口延迟触发的机制。Time.seconds(0)表示无延迟。
        SingleOutputStreamOperator<String> source = env.socketTextStream("localhost", 8888).assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor<String>(Time.seconds(0)) {
            @Override
            public long extractTimestamp(String line) {
                String[] split = line.split(",");
                return Long.parseLong(split[0]);
            }
        });

        SingleOutputStreamOperator<Tuple2<String, Integer>> mapOperator = source.map(line -> {
            String[] split = line.split(",");
            return Tuple2.of(split[1], Integer.parseInt(split[2]));
        }).returns(Types.TUPLE(Types.STRING,Types.INT));

        KeyedStream<Tuple2<String, Integer>, Tuple> keyedStream = mapOperator.keyBy(0);
        //EventTime滚动窗口
        WindowedStream<Tuple2<String, Integer>, Tuple, TimeWindow> windowedStream = keyedStream.window(TumblingEventTimeWindows.of(Time.seconds(5)));

        SingleOutputStreamOperator<Tuple2<String, Integer>> sum = windowedStream.sum(1);

        sum.print();

        env.execute("EventTimeDemo");
    }
}

1.1.2 结果演示

在这里插入图片描述

备注: (1581490623000转换后为:2020-02-12 14:57:03
             1581490624000转换后为:2020-02-12 14:57:04)


当我们在Socket中输入如下数据:
1581490623000,Mary,2
1581490624000,John,3
1581490624500,Clerk,1
1581490624998,Maria,4
1581490624999,Mary,3
1581490626000,Mary,3
1581490630800,Steve,3     (2020-02-12 14:57:10.800)

窗口定义的时间是:含头不含尾。即:[0,5),
图片解析:(我们定义滚动窗口为5s,我们分析图片发现到4998时,并没有输出内容。因为4998还没超过5s,窗口规定是>=临界值时触发,所以当我们输入4999临界时,我们发现输出内容了,说明一个窗口滚动完成,输出内容包含4999这个时间的值;当输入6000时,6000在[5,10)之间没有>10,所以不输出。输入30800【2020-02-12 14:57:10.800)】,已经超过10s,所以结果只输出1个 (Mary,3),因为Steve已经被分到另一个窗口了)

还有一个问题,就是:当输入到 4999 时,只是Mary这个分组满足5s这个条件,但是其它分组John,Clerk 等也同步输出结果了。显然这不符合逻辑。为什么会出现这种情况呢?是因为SocketStream 是非并行数据流,所以才会出现这样子的结果。(接下来我们就是用并行数据流KafkaSource来分析)
 

 1.2  并行Source

并行Source,以 KafkaSouce 为例来介绍 Flink使用 EventTime 处理实时数据。

1.2.1 代码 

   并行KafkaSource EventTime示例(读取 topic为 window_demo中的消息),代码如下所示:

/**
 * TODO 并行KafkaSource EventTime示例(读取 topic为 window_demo中的消息)
 *
 * @author 多易教育-行哥
 * @Date 2020-06-18
 */
public class EventTimeDemo {

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

        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        //设置EventTime作为时间标准
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);

        //Kafka props
        Properties properties = new Properties();
        //指定Kafka的Broker地址
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.204.210:9092,192.168.204.211:9092,192.168.204.212:9092");
        //指定组ID
        properties.put(ConsumerConfig.GROUP_ID_CONFIG, "flinkDemoGroup");
        //如果没有记录偏移量,第一次从最开始消费
        properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");

        FlinkKafkaConsumer<String> kafkaSource = new FlinkKafkaConsumer("window_demo", new SimpleStringSchema(), properties);
        
        //2.通过addSource()方式,创建 Kafka DataStream
        //读取source,并指定(1581490623000,Mary,3)中哪个字段为EventTime时间
        SingleOutputStreamOperator<String> source = env.addSource(kafkaSource).assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor<String>(Time.seconds(0)) {
            @Override
            public long extractTimestamp(String line) {
                String[] split = line.split(",");
                return Long.parseLong(split[0]);
            }
        });

        SingleOutputStreamOperator<Tuple2<String, Integer>> mapOperator = source.map(line -> {
            String[] split = line.split(",");
            return Tuple2.of(split[1], Integer.parseInt(split[2]));
        }).returns(Types.TUPLE(Types.STRING,Types.INT));

        KeyedStream<Tuple2<String, Integer>, Tuple> keyedStream = mapOperator.keyBy(0);
        //EventTime滚动窗口
        WindowedStream<Tuple2<String, Integer>, Tuple, TimeWindow> windowedStream = keyedStream.window(TumblingEventTimeWindows.of(Time.seconds(5)));

        SingleOutputStreamOperator<Tuple2<String, Integer>> sum = windowedStream.sum(1);

        sum.print();

        env.execute("EventTimeDemo");
    }
}

1.2.2 测试

创建 Topic 命令如下:
      bin/kafka-topics.sh --create --zookeeper 192.168.204.210:2181,192.168.204.211:2181,192.168.204.212:2181 --replication-factor 1 --partitions 3 --topic window_demo

(特别注意一下:此处创建了3个分区)

 创建 Topic 成功截图

在这里插入图片描述

 使用命令,写入数据到Kafka:
       bin/kafka-console-producer.sh --broker-list 192.168.204.210:9092 --topic window_demo

使用命令写入以下数据:

    1581490623000,Mary,2
    1581490624000,John,3
    1581490624500,Clerk,1
    1581490624998,Maria,4
    1581490624999,Mary,3
 

1.2.3 结果

在这里插入图片描述

1.3 分析总结

 在1.并行Source一例中,当我们输入1581490624999,Mary,3时,我们看到控制台会直接帮我们输出计算结果。

       但是,在使用 KafkaSource 时,我们连续输入了 3次1581490624999,Mary,3,我们才看到控制台帮我们输出计算了结果。

       那这是为什么呢?这是 并行Source 和 非并行Source 的原因导致的(这里涉及到 KafkaSource 创建的 topic,有 3 个分区的原因,如下图所示)

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_37933018/article/details/106878486