Spark Streaming
流式处理框架,7*24h 不间断运行
对比Storm
Storm处理流式数据是:来一条,出一条。
是纯实时处理。
Spark Streaming 处理逻辑
会等待一段时间,可以认为设置等待多久。
比如等待10s,那么等到10s后,会把这期间的所有数据批量处理,然后输出结果。
简而言之:来一段时间内的数据,一起处理。
Storm | Spark Streaming | |
---|---|---|
处理流式数据特点 | 纯实时的。1 => 1 | 会等待一段时间,然后统一处理。是准实时处理 |
特点 | 擅长处理汇总型业务。 | 吞吐量高,相对而言处理快。擅长处理复杂的业务(因为批处理的原因)。支持SparkSQL, SparkCore |
总结 | 微批处理 | |
另外 | 可以设置等待时间很短,那么可以达到Storm的效果 |
对比 SparkCore / SparkSQL
Spark Streaming 处理的是 DStream, 类比SparkSQL的DF。
SparkStreaming中有 Transformation / outputOperator 算子,类比于SparkCore的 Transformation / Action算子。
具体描述:
- SparkStreaming是微批处理数据,7*24h不间断运行
- SparkStreaming处理数据时,首先启动一个job,这个job使用一个task来一直接收数据。task将一段时间内接收到的数据封装到一个batch中。
- batch没有分布式计算特性,被封装到一个RDD中,这个RDD又被封装到一个DStream中
- 生成DStream之后,SparkStreaming启动job处理这个DStream。
- SS 底层操作的就是 DStream。 DStream有自己的Transformation类算子,lazy执行,需要 outputOperator类算子出发执行。
关于一段时间的细节:
假设batchInterval = 5s, SS 启动后,(假设SS处理这一个批次数据的时间是3s),
0-5s:一直接收数据;
5-8s:一边接收、一边处理数据;
8-10s:只接收数据;
10-13s: 接收+处理
13-15s:只接收
可看出,由于后面的集群5s内有2s是休息的,这个集群不能被充分利用。不好。
同理,当假设SS处理的时间超过5s,那么 最终接收来的数据是放到内存中会有OOM (out of memeory) 风险。如果接收的数据放到内存和磁盘里,会加大延迟。
Code
import org.apache.spark.SparkConf;
import org.apache.spark.SparkContext;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.FlatMapFunction;
import org.apache.spark.api.java.function.Function2;
import org.apache.spark.api.java.function.PairFunction;
import org.apache.spark.api.java.function.VoidFunction;
import org.apache.spark.broadcast.Broadcast;
import org.apache.spark.streaming.Durations;
import org.apache.spark.streaming.api.java.JavaDStream;
import org.apache.spark.streaming.api.java.JavaPairDStream;
import org.apache.spark.streaming.api.java.JavaReceiverInputDStream;
import org.apache.spark.streaming.api.java.JavaStreamingContext;
import scala.Tuple2;
import java.util.Arrays;
import java.util.Iterator;
/**
* @author
* @since 6/21/20 10:14 AM
*/
public class SparkStreamingTest {
public static void main(String[] args) throws InterruptedException {
SparkConf conf = new SparkConf().setMaster("local[2]").setAppName("SparkStreamingTest");
// 此处写local 等价于 local[1],是模拟 单线程. local[2] 指定2个线程。一个线程用于接收数据,另一个用于处理生成的DStream
JavaSparkContext sc = new JavaSparkContext(conf);
sc.setLogLevel("WARN");
JavaStreamingContext jsc = new JavaStreamingContext(sc, Durations.seconds(5));
/*
设置5s之后,
从此处开始,
接受5s内的数据,一次处理
*/
JavaReceiverInputDStream<String> socketTextStream = jsc.socketTextStream("192.168.101.130", 9999);
// 在指定的ip机器上,$ nc -lk 9999
JavaDStream<String> words = socketTextStream.flatMap(new FlatMapFunction<String, String>() {
// flatMap: one to many
private static final long serialVersionUID = 1L;
@Override
public Iterator<String> call(String lines) throws Exception {
return Arrays.asList(lines.split(" ")).iterator();
}
});
JavaPairDStream<String, Integer> pairWords = words.mapToPair(new PairFunction<String, String, Integer>() {
private static final long serialVersionUID = 1L;
@Override
public Tuple2<String, Integer> call(String word) throws Exception {
return new Tuple2<String, Integer>(word, 1);
}
});
// k-v 格式的DStream,内部是 k-v格式的RDD
JavaPairDStream<String, Integer> reduceByKeyDStream = pairWords.reduceByKey(new Function2<Integer, Integer, Integer>() {
private static final long serialVersionUID = 1L;
@Override
public Integer call(Integer v1, Integer v2) throws Exception {
return v1 + v2;
}
});
// outputOperator to trigger. e.g. print(), foreachRDD()
// reduceByKey.print(10); // 某一个 outputOp算子
/*
* 从DStream 中拿到 RDD/pairRDD,对RDD进行操作transformation/action。如果不action的化,Dsream也不会触发。
* 注意:只有对rdd 进行 action时, reduceByKeyDStream.foreachRDD才会真正触发。
* 广播:可以动态改变广播变量。通过读取外部文件,修改外部文件。
*/
reduceByKeyDStream.foreachRDD(new VoidFunction<JavaPairRDD<String, Integer>>() { // foreachRDD 也是触发类算子
private static final long serialVersionUID = 1L;
// get each RDD
@Override
public void call(JavaPairRDD<String, Integer> pairRDD) throws Exception {
// 注意:这里的RDD 执行的地方不同于 上面DStream的在executor里,
// 此处rdd是在 Driver里执行
System.out.println("Driver ....................,每5s打印一次。总是要打印的。无论有没有数据过来。有Driver的地方有 sparkContext");
System.out.println("Driver side 可以广播东西");
SparkContext context = pairRDD.context(); // 必须从 pairRDD处拿到 context,不能直接用main里定义的。
JavaSparkContext sc = new JavaSparkContext(context);
Broadcast<String> hello = sc.broadcast("hello"); // 此处还可以读取外部的文件。当外部文件更新时,5s后也可以读取到新的文件。 实现:动态改变广播变量
String value = hello.value();
System.out.println(value);
// transform pairRDD to another pairRDD
JavaPairRDD<String, Integer> newPairRDD = pairRDD.mapToPair(new PairFunction<Tuple2<String, Integer>, String, Integer>() {
private static final long serialVersionUID = 1L;
@Override
public Tuple2<String, Integer> call(Tuple2<String, Integer> tuple2) throws Exception {
System.out.println("Executor....................只有有数据过来处理时,才打印");
return new Tuple2<>(tuple2._1 + "~", tuple2._2);
}
});
// sout
newPairRDD.foreach(new VoidFunction<Tuple2<String, Integer>>() {
@Override
public void call(Tuple2<String, Integer> tuple2) throws Exception {
System.out.println("rdd.foreach ......................., only when data comes");
System.out.println(tuple2);
}
});
}
});
jsc.start(); // 因为SS是7*24h, 需要启动
jsc.awaitTermination(); // 等待被终结
// jsc.stop(); // 这个stop 是调用不到的 ??? 可以停止
/*
一直循环这段代码
*/
}
}
在指定的ip机器上:
$ nc -lk 9999
之后,在cli界面 手动输入一些字符
附:
关于 nc -lk 9999 什么意思?
nc is netcat, 9999 is port
nc
-l 开启 监听模式,用于指定nc将处于监听模式。通常 这样代表着为一个 服务等待客户端来链接指定的端口。
-p<通信端口> 设置本地主机使用的通信端口。有可能会关闭
-k<通信端口>强制 nc 待命链接.当客户端从服务端断开连接后,过一段时间服务端也会停止监听。 但通过选项 -k 我们可以强制服务器保持连接并继续监听端口。