同样的算子其输出结果在(批/流)中的不同表现行为

  • 摘要
  • 1.流处理和批处理的api
  • 2.DataSet批处理reduce
  • 3.DatStream
  • 3.1 DatStream流处理reduce
  • 3.2 DatStream批处理reduce
  • 4.分析结果
  • 5.我们重点来分析流处理结果:
  • 6.总结


摘要

流处理和批处理很多算子基本都是一样的,比如reduce,map,flatMap等等。但是有些时候流处理和批处理同样的算子输出结果是不同的,这一点对于新手有时候难以理解,这篇文将就来谈谈这个问题,我们以reduce这个算了为例子来做展示。

1.流处理和批处理的api

  • final ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
    上面的env获取的数据源,其实是DataSet, 意思就是这是一个有界批处理入口。
  • final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
    上面的env获取的数据源其实是DataStream,意思是默认情况下这是一个无界流处理入口。
    注意:DataSet基本上只能用于批处理,而DataStream则既支持批处理也支持流处理,它通过一个参数控制此模式:env.setRuntimeMode(RuntimeExecutionMode.BATCH); 换句话说其实DataSet已经可以完全被取代了,而事实上也确实如此,在官网中有这么一句话:
    Starting with Flink 1.12 the DataSet API has been soft deprecated.
    但是下面的代码我们依旧演示了DataSet批处理

2.DataSet批处理reduce

import org.apache.flink.api.java.DataSet;
import org.apache.flink.api.java.ExecutionEnvironment;
import org.apache.flink.api.java.tuple.Tuple2;

public class reduceDemo {
public static void main(String[] args) throws Exception {
final ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();

DataSet<Tuple2<String,Long>> text = env.fromElements(
            Tuple2.of("a", 1L),
            Tuple2.of("a", 2L),
            Tuple2.of("b", 1L),
            Tuple2.of("b", 3L),
            Tuple2.of("c", 100L)
    );
 text.groupBy(0).reduce((x1,x2)-> Tuple2.of(x1.f0,x1.f1+x2.f1)).print();


}

}

输出结果为:
(a,3)
(b,4)
(c,100)

3.DatStream

3.1 DatStream流处理reduce

import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;

public class reduceDemo {
//reduce和我们想的还是有些不同
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        //定义两条流
        DataStream<Tuple2<String, Long>> stream1 = env.fromElements(
                Tuple2.of("a", 1L),
                Tuple2.of("a", 2L),
                Tuple2.of("b", 1L),
                Tuple2.of("b", 3L),
                Tuple2.of("c", 100L)
        );
     stream1.keyBy(0).reduce((x0,x1)-> Tuple2.of(x0.f0,x0.f1+x1.f1)).print();
        env.execute();
    }

}
输出结果为:
 (a,1)
 (a,3)
 (b,1)
 (b,4)
 (c,100)

3.2 DatStream批处理reduce

只需要在DataStream流处理代码中加上,env.setRuntimeMode(RuntimeExecutionMode.BATCH);即可, 建议读者自己试下,在这种模式下的输出结果,和DataSet批处理模式的结果完全一样。换句话说,DataStream可以通过env.setRuntimeMode(RuntimeExecutionMode.BATCH)运行在批处理模式下,该模式其实完全取代了DataSet api.

4.分析结果

上面的两个代码逻辑是一模一样的,都是根据第一个元素进行分区分组,根据第二个元素做求和。批处理的结果我们很容易接收,而结果也是我们想的样子。

(a,3)
(b,4)
(c,100)
流处理结果:
(a,1)
(a,3)
(b,1)
(b,4)
(c,100)
这个样子对很多新手来说很难接受,为什么不是我想要的样子,你只所以有这个疑问是因为你还没有理解批处理和流出的重大区别, 对批处理来说在输入数据一样的情况下,结果也一定只有一种。 而对于流处理来说,流处理是无解的,你不知道什么时候结束,换句话说我们也不关心什么时候结束,我们只关心中间的结果,流处理是每来一个数据进行一次逻辑运算,而这个结果就是实实在在的当前的结果,你不要总是想着最终结果,流处理没有最终结果的概念。

5.我们重点来分析流处理结果:

  1. 对a来说: 第一次来了个(a,1)不进行计算即当前结果就是(a,1),第二次来了个(a,2)则计算结果为(a,3)–>产生的中间结果就是**(a,1)和(a,3)**
  2. 对b来说:第一次来了个(b,1)不进行计算即当前结果就是(b,1),第二次来了个(b,3)则计算结果为(b,4)–>产生的中间结果就是(b,1)和(b,4)
  3. 对c来说: 第一次来了个(c,100)不进行计算即当前结果就是**(c,100)**

所以流处理最终的结果就是:(a,1)(a,3) (b,1)(b,4) (c,100)

所以对a来说计算结果从a,1到a,3这两个都是真正的结果,只不过随着流数据的到来这个结果会不断往前推进,故而落库的时候这个数据的Key一般用作唯一主键来进行对数据的更新,假设我们将计算结果存入mysql, 那么其实对流数据来说最后的结果也是:(a,3), (b,4) (c,100)这样看下来,在输入流保持不变的时候,其实批处理和流处理最终的处理结果是一致的。

6.总结

综上所述,同样的算子,在批处理和流处理的行为本质上是一样的,产生的结果是不同的。 这里读者一定要理解流处理,流处理从source-etl-sink这整个流程中结果是不断发出的,落地库中的结果也是在不断被更新的(需要唯一主键保证),这是和批处理最大的区别,批处理往落地库输出结果只有一次,而流处理是在不断的输出处理结果。 重点要理解六处理中:不断产生不断更新结果 这两个词。