同样的算子其输出结果在(批/流)中的不同表现行为
- 摘要
- 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.我们重点来分析流处理结果:
- 对a来说: 第一次来了个(a,1)不进行计算即当前结果就是(a,1),第二次来了个(a,2)则计算结果为(a,3)–>产生的中间结果就是**(a,1)和(a,3)**
- 对b来说:第一次来了个(b,1)不进行计算即当前结果就是(b,1),第二次来了个(b,3)则计算结果为(b,4)–>产生的中间结果就是(b,1)和(b,4)
- 对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这整个流程中结果是不断发出的,落地库中的结果也是在不断被更新的(需要唯一主键保证),这是和批处理最大的区别,批处理往落地库输出结果只有一次,而流处理是在不断的输出处理结果。 重点要理解六处理中:不断产生 和 不断更新结果 这两个词。