Spark Streaming 用于流式数据的处理。 Spark Streaming 支持的数据输入源很多,例如: Kafka、Flume、 Twitter、 ZeroMQ 和简单的 TCP 套接字等等。数据输入后可以用 Spark 的高度抽象原语如: map、 reduce、 join、 window 等进行运算。而结果也能保存在很多地方,如 HDFS,数据库等。
Spark Streaming 使用离散化流(discretized stream)作为抽象表示,叫作 DStream。 DStream 是随时间推移而收到的数据的序列。在内部,每个时间区间收到的数据都作为 RDD 存在,而 DStream 是由这些 RDD 所组成的序列(因此得名"离散化")。 所以简单来将, DStream 就是对 RDD 在实时数据处理场景的一种封装。
Spark Streaming准实时、微批次数据处理框架。
简单的wordcount程序
java
package src.main;
import org.apache.spark.SparkConf;
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.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;
public class streaming {
public static void main(String[] args) throws InterruptedException {
SparkConf sparkAppConf = new SparkConf().setMaster("local[2]").setAppName("sparkStreamingDemo");
JavaStreamingContext javaStreamingContext = new JavaStreamingContext(sparkAppConf, Durations.seconds(3));
JavaReceiverInputDStream<String> lines = javaStreamingContext.socketTextStream("localhost", 9999);
JavaDStream<String> mapDS = lines.flatMap(new FlatMapFunction<String, String>() {
@Override
public Iterator<String> call(String s) throws Exception {
return Arrays.asList(s.split(" ")).iterator();
}
});
JavaPairDStream<String, Integer> mapKeyDS = mapDS.mapToPair(new PairFunction<String, String, Integer>() {
@Override
public Tuple2<String, Integer> call(String s) throws Exception {
return new Tuple2<>(s, 1);
}
});
JavaPairDStream<String, Integer> reduceDS = mapKeyDS.reduceByKey(new Function2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer integer, Integer integer2) throws Exception {
return integer + integer2;
}
});
reduceDS.print();
javaStreamingContext.start();
javaStreamingContext.awaitTermination();
}
}
shell
nc -lk 9999
hello world
hello spark
hello spark
Discretized Stream 是 Spark Streaming 的基础抽象,代表持续性的数据流和经过各种 Spark 原语操作后的结果数据流。在内部实现上, DStream 是一系列连续的 RDD 来表示。每个 RDD 含有一段时间间隔内的数据。
对数据的操作也是按照RDD为单位来进行的。
计算过程由spark engine来完成
DStream创建
RDD队列
仅供测试使用。通过使用 ssc.queueStream(queueOfRDDs)来创建 DStream,每一个推送到这个队列中的 RDD,都会作为一个 DStream 处理
java
package src.main.streaming;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.function.Function2;
import org.apache.spark.api.java.function.PairFunction;
import org.apache.spark.streaming.Durations;
import org.apache.spark.streaming.api.java.JavaInputDStream;
import org.apache.spark.streaming.api.java.JavaPairDStream;
import org.apache.spark.streaming.api.java.JavaStreamingContext;
import scala.Tuple2;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
public class streamingDemo1 {
public static void main(String[] args) throws InterruptedException {
SparkConf sparkAppConf = new SparkConf().setMaster("local[2]").setAppName("sparkStreamingDemo");
JavaStreamingContext javaStreamingContext = new JavaStreamingContext(sparkAppConf, Durations.seconds(4));
Queue<JavaRDD<Integer>> rddQueue = new LinkedList<JavaRDD<Integer>>();
JavaInputDStream<Integer> inputDStream = javaStreamingContext.queueStream(rddQueue, false);
JavaPairDStream<Integer, Integer> mappedDS = inputDStream.mapToPair(new PairFunction<Integer, Integer, Integer>() {
@Override
public Tuple2<Integer, Integer> call(Integer integer) throws Exception {
return new Tuple2<>(integer, 1);
}
});
JavaPairDStream<Integer, Integer> result = mappedDS.reduceByKey(new Function2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer integer, Integer integer2) throws Exception {
return integer + integer2;
}
});
result.print();
javaStreamingContext.start();
ArrayList<Integer> list = new ArrayList<>();
for (int j = 0; j < 300; j++) {
list.add(j);
} for (int i = 0; i < 5; i++) {
JavaRDD<Integer> rdd = javaStreamingContext.sparkContext().parallelize(list, 10);
rddQueue.add(rdd);
Thread.sleep(2000);
} javaStreamingContext.awaitTermination();
}
}
自定义数据源
自定义数据源需要继承org.apache.spark.streaming.receiver.Receiver
类,然后实现onStart
以及onStop
的方法,构造方法中需要传入一个StorageLevel
对象。 使用的时候,需要sparkstreamingcontext调用receiverStream
方法,传入自定义的数据源的实例化对象。 参考示例:
java
package src.main.streaming;
import org.apache.spark.SparkConf;
import org.apache.spark.storage.StorageLevel;
import org.apache.spark.streaming.Durations;
import org.apache.spark.streaming.api.java.JavaReceiverInputDStream;
import org.apache.spark.streaming.api.java.JavaStreamingContext;
import org.apache.spark.streaming.receiver.Receiver;
import java.util.Random;
// 自定义数据源
public class streamingDemo2 {
public static void main(String[] args) throws InterruptedException {
SparkConf sparkAppConf = new SparkConf().setMaster("local[2]").setAppName("sparkStreamingDemo");
JavaStreamingContext javaStreamingContext = new JavaStreamingContext(sparkAppConf, Durations.seconds(4));
JavaReceiverInputDStream<String> messageDS = javaStreamingContext.receiverStream(new CustomerReceiver(StorageLevel.MEMORY_ONLY()));
messageDS.print();
javaStreamingContext.start();
javaStreamingContext.awaitTermination();
}
/*
自定义接收器,继承自Receiver,重写onstart、onstop方法
*/ public static class CustomerReceiver extends Receiver<String>{
private boolean flag = true;
public CustomerReceiver(StorageLevel storageLevel) {
super(storageLevel);
}
@Override
public void onStart() {
new Thread(new Runnable() {
@Override public void run() {
while (flag){
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("收到的信息->");
int i = new Random().nextInt(10);
StringBuilder append = stringBuilder.append(i);
store(stringBuilder.toString());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}).start();
}
@Override
public void onStop() {
flag = false;
} }
}
运行结果:
rust
-------------------------------------------
Time: 1710080772000 ms
-------------------------------------------
收到的信息->0
收到的信息->1
收到的信息->6
收到的信息->3
收到的信息->8
收到的信息->8
收到的信息->7
收到的信息->1
kafka数据源
ReceiverAPI:需要一个专门的 Executor 去接收数据,然后发送给其他的 Executor 做计算。存在的问题:接收数据的 Executor 和计算的 Executor 速度会有所不同,特别在接收数据的 Executor速度大于计算的 Executor 速度,会导致计算数据的节点内存溢出。 早期版本中提供此方式,spark3.0版本不适用。 DirectAPI:是由计算的 Executor 来主动消费 Kafka 的数据,速度由自身控制。
xml
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming-kafka-0-10_2.12</artifactId>
<version>3.3.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.12.7</version>
<scope>provided</scope>
</dependency>
如果遇到异常可能是由于com.fasterxml.jackson.core.util.JacksonFeature
这个库在当前环境存在多个版本,配置一个比较高的版本就可以了。
Kafka的配置信息
java
import org.apache.spark.streaming.api.java.*;
import org.apache.spark.streaming.kafka010.*;
Map<String, Object> kafkaConfig = new HashMap<String, Object>();
kafkaConfig.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "172.20.143.219:9092");
kafkaConfig.put(ConsumerConfig.GROUP_ID_CONFIG, "SPARKTEXT");
kafkaConfig.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
kafkaConfig.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
kafkaConfig.put(ConsumerConfig.AUTO_OFFSET_RESET_DOC, "latest");
kafkaConfig.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false); // 禁止自动,提交作为Kafka生产者的时候建议开启,提升数据一致性
List<String> topic = Arrays.asList("test"); // topic列表
通过kafkaUtils创建DStream:
java
JavaInputDStream<ConsumerRecord<String, String>> kafkaStream = KafkaUtils.createDirectStream(
javaStreamingContext, // spark streaming context实例
LocationStrategies.PreferConsistent(),
ConsumerStrategies.<String, String>Subscribe(
topic, // 需要传入collection对象
kafkaConfig // 需要传入map对象
)
);
完整代码:
java
package src.main.streaming;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.function.FlatMapFunction;
import org.apache.spark.streaming.Durations;
import org.apache.spark.streaming.api.java.*;
import org.apache.spark.streaming.kafka010.*;
import scala.Tuple2;
import java.util.*;
// 消费Kafka数据源数据
public class streamingDemo3kafka {
public static void main(String[] args) throws InterruptedException {
SparkConf sparkAppConf = new SparkConf().setMaster("local[2]").setAppName("sparkStreamingDemo");
JavaStreamingContext javaStreamingContext = new JavaStreamingContext(sparkAppConf, Durations.seconds(4));
Map<String, Object> kafkaConfig = new HashMap<String, Object>();
kafkaConfig.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "172.20.143.219:9092");
kafkaConfig.put(ConsumerConfig.GROUP_ID_CONFIG, "SPARKTEXT");
kafkaConfig.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
kafkaConfig.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
kafkaConfig.put(ConsumerConfig.AUTO_OFFSET_RESET_DOC, "latest");
kafkaConfig.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false); // 禁止自动,提交作为Kafka生产者的时候建议开启,提升数据一致性
List<String> topic = Arrays.asList("test"); // topic列表
JavaInputDStream<ConsumerRecord<String, String>> kafkaStream = KafkaUtils.createDirectStream(
javaStreamingContext, // spark streaming context实例
LocationStrategies.PreferConsistent(),
ConsumerStrategies.<String, String>Subscribe(
topic, // 需要传入collection对象
kafkaConfig // 需要传入map对象
)
);
JavaDStream<String> mapDS = kafkaStream.flatMap(new FlatMapFunction<ConsumerRecord<String, String>, String>() {
@Override
public Iterator<String> call(ConsumerRecord<String, String> value) throws Exception {
String value1 = value.value();
List<String> stringList = Arrays.asList(value1.split(" "));
return stringList.iterator();
}
});
JavaPairDStream<String, Integer> map2DS = mapDS.mapToPair(x -> new Tuple2<>(x, 1));
JavaPairDStream<String, Integer> result = map2DS.reduceByKey((x, y) -> x + y);
result.print();
javaStreamingContext.start();
javaStreamingContext.awaitTermination();
}
}
运行结果:
markdown
-------------------------------------------
Time: 1710091096000 ms
-------------------------------------------
(flink,1)
(hello,2)
(world,1)
-------------------------------------------
Time: 1710091100000 ms
-------------------------------------------
(python,1)
(hello,1)
DStream转换
DStream 上的操作与 RDD 的类似,分为 Transformations(转换)和 Output Operations(输出)两种,就是这里的DStream的转换就可以类比RDD的算子。 在DStream转换中,大体可分为无状态转换操作和有状态转换操作两种!
无状态转换
无状态转化操作就是把简单的RDD转化操作应用到每个批次上,也就是转化DStream中的每一个RDD。部分无状态转化操作列在了下表中。
需要记住的是,尽管这些函数看起来像作用在整个流上一样,但事实上每个DStream
在内部是由许多RDD
(批次)组成,且无状态转化操作是分别应用到每个RDD上的。例如,reduceByKey()
会化简每个时间区间中的数据,但不会化简不同区间之间的数据。 举个例子,在之前的wordcount程序中,我们只会统计几秒内接收到的数据的单词个数,而不会累加。如下:
markdown
这里用到了map->flatmap->reduceByKey,在单个dstream内进行聚合
-------------------------------------------
Time: 1710091096000 ms
-------------------------------------------
(flink,1)
(hello,2)
(world,1)
-------------------------------------------
Time: 1710091100000 ms
-------------------------------------------
(python,1)
(hello,1)
无状态转化操作也能在多个DStream
间整合数据,不过也是在各个时间区间内。例如,键值对DStream
拥有和RDD
一样的与连接相关的转化操作,也就是cogroup()
、join()
、leftOuterJoin()
等。我们可以在DStream
上使用这些操作,这样就对每个批次分别执行了对应的RDD操作。 我们还可以像在常规的 Spark 中一样使用 DStream
的union()
操作将它和另一个DStream
的内容合并起来,也可以使用StreamingContext.union()
来合并多个流。
Transform
transform
原语允许 DStream
上执行任意的RDD-to-RDD
函数。可以用来执行一些 RDD 操作,即使这些操作并没有在 SparkStreaming 中暴露出来,该函数每一批次调度一次,其实也就是对DStream中的RDD应用转换。
java
package src.main.streaming;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.function.Function;
import org.apache.spark.streaming.Durations;
import org.apache.spark.streaming.api.java.*;
public class streamingDemo4transform {
public static void main(String[] args) throws InterruptedException {
SparkConf sparkAppConf = new SparkConf().setMaster("local[2]").setAppName("sparkStreamingDemo");
JavaStreamingContext javaStreamingContext = new JavaStreamingContext(sparkAppConf, Durations.seconds(4));
JavaReceiverInputDStream<String> lines = javaStreamingContext.socketTextStream("localhost", 9999);
JavaDStream<String> transform = lines.transform(new Function<JavaRDD<String>, JavaRDD<String>>() {
@Override
public JavaRDD<String> call(JavaRDD<String> stringJavaRDD) throws Exception {
//! driver端执行,周期性执行,每份DStream都会执行一遍
JavaRDD<String> map = stringJavaRDD.map(new Function<String, String>() {
@Override
public String call(String s) throws Exception {
//! executor端执行,DStream中每行数据都会执行
return "data->" + s;
}
});
return map;
}
});
transform.print();
javaStreamingContext.start();
javaStreamingContext.awaitTermination();
}
}
join
两个流之间的 join 需要两个流的批次大小一致,这样才能做到同时触发计算。计算过程就是对当前批次的两个流中各自的 RDD 进行 join,与两个 RDD 的 join 效果相同。 按照下面思路构造两个socket字符串流
java
JavaReceiverInputDStream<String> lines = javaStreamingContext.socketTextStream("192.168.141.177", 9999);
JavaDStream<String> mapDS = lines.flatMap(new FlatMapFunction<String, String>() {
@Override
public Iterator<String> call(String s) throws Exception {
return Arrays.asList(s.split(" ")).iterator();
}
});
JavaPairDStream<String, Integer> mapKeyDS = mapDS.mapToPair(x -> new Tuple2<>(x, 1));
JavaPairDStream<String, Integer> reduceDS = mapKeyDS.reduceByKey((x, y) -> x + y);
这里需要转换成KV类型,然后按照相同的key把两个DS的数据做join操作
java
JavaPairDStream<String, Tuple2<Integer, Integer>> joinrdd = reduceDS.join(reduceDS2);
完整代码:
java
package src.main.streaming;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.function.FlatMapFunction;
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;
public class streamingDemo5join {
public static void main(String[] args) throws InterruptedException {
SparkConf sparkAppConf = new SparkConf().setMaster("local[*]").setAppName("sparkStreamingDemo");
JavaStreamingContext javaStreamingContext = new JavaStreamingContext(sparkAppConf, Durations.seconds(3));
JavaReceiverInputDStream<String> lines = javaStreamingContext.socketTextStream("192.168.141.177", 9999);
JavaDStream<String> mapDS = lines.flatMap(new FlatMapFunction<String, String>() {
@Override
public Iterator<String> call(String s) throws Exception {
return Arrays.asList(s.split(" ")).iterator();
}
});
JavaPairDStream<String, Integer> mapKeyDS = mapDS.mapToPair(x -> new Tuple2<>(x, 1));
JavaPairDStream<String, Integer> reduceDS = mapKeyDS.reduceByKey((x, y) -> x + y);
JavaReceiverInputDStream<String> lines2 = javaStreamingContext.socketTextStream("192.168.141.177", 9998);
JavaDStream<String> mapDS2 = lines2.flatMap(new FlatMapFunction<String, String>() {
@Override
public Iterator<String> call(String s) throws Exception {
return Arrays.asList(s.split(" ")).iterator();
}
});
JavaPairDStream<String, Integer> mapKeyDS2 = mapDS2.mapToPair(x -> new Tuple2<>(x, 1));
JavaPairDStream<String, Integer> reduceDS2 = mapKeyDS2.reduceByKey((x, y) -> x + y);
JavaPairDStream<String, Tuple2<Integer, Integer>> joinrdd = reduceDS.join(reduceDS2);
joinrdd.print();
javaStreamingContext.start();
javaStreamingContext.awaitTermination();
}
}
输出:
markdown
-------------------------------------------
Time: 1710137454000 ms
-------------------------------------------
(a,(3,3))
join操作会将两个DS相同时间内的数据按照相同key进行join输出
kafka数据源的DS的join
java
package src.main.streaming;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.function.FlatMapFunction;
import org.apache.spark.streaming.Durations;
import org.apache.spark.streaming.api.java.JavaDStream;
import org.apache.spark.streaming.api.java.JavaInputDStream;
import org.apache.spark.streaming.api.java.JavaPairDStream;
import org.apache.spark.streaming.api.java.JavaStreamingContext;
import org.apache.spark.streaming.kafka010.ConsumerStrategies;
import org.apache.spark.streaming.kafka010.KafkaUtils;
import org.apache.spark.streaming.kafka010.LocationStrategies;
import scala.Tuple2;
import java.util.*;
// 消费Kafka数据源数据
public class streamingDemo3kafka2 {
public static void main(String[] args) throws InterruptedException {
SparkConf sparkAppConf = new SparkConf().setMaster("local[2]").setAppName("sparkStreamingDemo");
JavaStreamingContext javaStreamingContext = new JavaStreamingContext(sparkAppConf, Durations.seconds(10));
Map<String, Object> kafkaConfig = new HashMap<String, Object>();
kafkaConfig.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.141.177:9092");
kafkaConfig.put(ConsumerConfig.GROUP_ID_CONFIG, "SPARKTEXT");
kafkaConfig.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
kafkaConfig.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
kafkaConfig.put(ConsumerConfig.AUTO_OFFSET_RESET_DOC, "earliest");
kafkaConfig.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false); // 禁止自动,提交作为Kafka生产者的时候建议开启,提升数据一致性
List<String> topic = Arrays.asList("test"); // topic列表
List<String> topic2 = Arrays.asList("gmall_db"); // topic列表
JavaInputDStream<ConsumerRecord<String, String>> kafkaStream = KafkaUtils.createDirectStream(
javaStreamingContext, // spark streaming context实例
LocationStrategies.PreferConsistent(),
ConsumerStrategies.<String, String>Subscribe(
topic, // 需要传入collection对象
kafkaConfig // 需要传入map对象
)
);
JavaInputDStream<ConsumerRecord<String, String>> kafkaStream2 = KafkaUtils.createDirectStream(
javaStreamingContext,
LocationStrategies.PreferConsistent(),
ConsumerStrategies.<String, String>Subscribe(
topic2,
kafkaConfig
)
);
JavaPairDStream<String, Integer> result1 = parseKafkaValue(kafkaStream);
JavaPairDStream<String, Integer> result2 = parseKafkaValue(kafkaStream2);
JavaPairDStream<String, Tuple2<Integer, Integer>> joined = result1.join(result2);
joined.print();
javaStreamingContext.start();
javaStreamingContext.awaitTermination();
}
public static JavaPairDStream<String, Integer> parseKafkaValue(JavaInputDStream<ConsumerRecord<String, String>> dStream){
JavaDStream<String> mapDS = dStream.flatMap(new FlatMapFunction<ConsumerRecord<String, String>, String>() {
@Override
public Iterator<String> call(ConsumerRecord<String, String> value) throws Exception {
return Arrays.asList(value.value().split(" ")).iterator();
}
});
JavaPairDStream<String, Integer> mapds2 = mapDS.mapToPair(x -> new Tuple2<>(x, 1));
return mapds2.reduceByKey((x, y) -> x+y);
}
}
执行结果:
erlang
-------------------------------------------
Time: 1710230520000 ms
-------------------------------------------
(CdEL61a,(1,2))
(8gMKjsVL,(1,3))
(zoyDrDMx1,(1,1))
(IAz,(2,2))
(5LM,(2,2))
(3hMCeLne,(3,2))
(7Na2b0sZb,(2,1))
(s0SwJk9,(1,3))
-------------------------------------------
Time: 1710230530000 ms
-------------------------------------------
(68f36kaFu,(1,1))
(8gMKjsVL,(2,1))
(zoyDrDMx1,(2,1))
(IAz,(1,2))
(qNcy,(3,3))
(7Na2b0sZb,(3,4))
-------------------------------------------
Time: 1710230540000 ms
-------------------------------------------
(68f36kaFu,(1,1))
(CdEL61a,(2,2))
(zoyDrDMx1,(5,3))
(5LM,(2,4))
(s0SwJk9,(3,2))
有状态转换操作
UpdateStateByKey
UpdateStateByKey 原语用于记录历史记录,有时,我们需要在 DStream 中跨批次维护状态(例如流计算中累加 wordcount)。针对这种情况, updateStateByKey()为我们提供了对一个状态变量的访问,用于键值对形式的 DStream。给定一个由(键,事件)对构成的 DStream,并传递一个指
定如何根据新的事件更新每个键对应状态的函数,它可以构建出一个新的 DStream,其内部数据为(键,状态) 对。
updateStateByKey() 的结果会是一个新的 DStream,其内部的 RDD 序列是由每个时间区间对应的(键,状态)对组成的。
updateStateByKey 操作使得我们可以在用新信息进行更新时保持任意的状态。为使用这个功能,需要做下面两步:
- 定义状态,状态可以是一个任意的数据类型
- 定义状态更新函数,用此函数阐明如何使用之前的状态和来自输入流的新值对状态进行更新
使用updateStateByKey需要对检查点目录进行配置,因为是使用检查点保存状态
java
package src.main.streaming;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.Optional;
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.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;
import java.util.List;
public class streamingDemo5 {
public static void main(String[] args) throws InterruptedException {
SparkConf sparkAppConf = new SparkConf().setMaster("local[2]").setAppName("sparkStreamingDemo");
JavaStreamingContext javaStreamingContext = new JavaStreamingContext(sparkAppConf, Durations.seconds(3));
javaStreamingContext.checkpoint("/home/saberbin/logs/checkpoints/sparkdemo");
JavaReceiverInputDStream<String> lines = javaStreamingContext.socketTextStream("192.168.141.177", 9999);
JavaDStream<String> mapDS = lines.flatMap(new FlatMapFunction<String, String>() {
@Override
public Iterator<String> call(String s) throws Exception {
return Arrays.asList(s.split(" ")).iterator();
}
});
JavaPairDStream<String, Integer> mapKeyDS = mapDS.mapToPair(new PairFunction<String, String, Integer>() {
@Override
public Tuple2<String, Integer> call(String s) throws Exception {
return new Tuple2<>(s, 1);
}
});
JavaPairDStream<String, Integer> reduceDS = mapKeyDS.reduceByKey(new Function2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer integer, Integer integer2) throws Exception {
return integer + integer2;
}
});
// JavaPairDStream<String, Integer> result = reduceDS.updateStateByKey(new Function2<List<Integer>, Optional<Integer>, Optional<Integer>>() {
// @Override
// public Optional<Integer> call(List<Integer> integers, Optional<Integer> optional) throws Exception {
// Integer value = optional.orElse(0);
// for (Integer i : integers) {
// value += i;
// }
// return Optional.of(value);
// }
// });
JavaPairDStream<String, Object> result = reduceDS.updateStateByKey((values, state) -> {
Integer sum = (Integer) state.orElse(0);
for (Integer value : values) {
sum += value;
}
return Optional.of(sum);
});
result.print();
javaStreamingContext.start();
javaStreamingContext.awaitTermination();
}
}
执行结果:
markdown
-------------------------------------------
Time: 1710243672000 ms
-------------------------------------------
(hello,1)
(world,1)
-------------------------------------------
Time: 1710243675000 ms
-------------------------------------------
(hello,2)
(world,2)
-------------------------------------------
Time: 1710243678000 ms
-------------------------------------------
(hello,3)
(world,2)
(py,1)
-------------------------------------------
Time: 1710243681000 ms
-------------------------------------------
(flink,1)
(hello,4)
(world,2)
(py,1)
-------------------------------------------
Time: 1710243684000 ms
-------------------------------------------
(flink,2)
(hello,4)
(world,2)
(app,1)
(py,1)
-------------------------------------------
Time: 1710243687000 ms
-------------------------------------------
(tom,1)
(flink,2)
(hello,5)
(world,2)
(app,1)
(py,1)
-------------------------------------------
Time: 1710243690000 ms
-------------------------------------------
(tom,1)
(flink,2)
(hello,5)
(world,2)
(app,1)
(py,1)
程序会持续将状态缓存起来,而且是按key进行汇总,可以看到本地目录页缓存了checkpoint数据。
需要注意的是,因为设置了checkpoint目录,当前程序在Windows系统下执行会出现未设置Hadoop目录的异常(因为本地Windows没有配置Hadoop,所以不清楚设置了Windows的Hadoop目录是否可以正常执行);Linux系统下可以正常设置本地目录。
WindowOperations
Window Operations 可以设置窗口的大小和滑动窗口的间隔来动态的获取当前 Steaming 的允许状态。 所有基于窗口的操作都需要两个参数,分别为窗口时长以及滑动步长。
- 窗口时长:计算内容的时间范围
- 滑动步长:隔多久触发一次计算 这两者的时间都必须为采集周期大小的整数倍。
java
package src.main.streaming;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.Optional;
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.streaming.Durations;
import org.apache.spark.streaming.Seconds;
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;
public class streamingDemo6 {
public static void main(String[] args) throws InterruptedException {
SparkConf sparkAppConf = new SparkConf().setMaster("local[2]").setAppName("sparkStreamingDemo");
JavaStreamingContext javaStreamingContext = new JavaStreamingContext(sparkAppConf, Durations.seconds(3));
javaStreamingContext.checkpoint("/home/saberbin/logs/checkpoints/sparkdemo");
JavaReceiverInputDStream<String> lines = javaStreamingContext.socketTextStream("192.168.141.177", 9999);
JavaDStream<String> mapDS = lines.flatMap(new FlatMapFunction<String, String>() {
@Override
public Iterator<String> call(String s) throws Exception {
return Arrays.asList(s.split(" ")).iterator();
}
});
JavaPairDStream<String, Integer> mapKeyDS = mapDS.mapToPair(new PairFunction<String, String, Integer>() {
@Override
public Tuple2<String, Integer> call(String s) throws Exception {
return new Tuple2<>(s, 1);
}
});
JavaPairDStream<String, Integer> winoutDS = mapKeyDS.reduceByKeyAndWindow(new Function2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer integer, Integer integer2) throws Exception {
return integer + integer2;
}
}, Durations.seconds(12), Durations.seconds(6));
winoutDS.print();
javaStreamingContext.start();
javaStreamingContext.awaitTermination();
}
}
执行结果:
erlang
-------------------------------------------
Time: 1710245214000 ms
-------------------------------------------
(hello,1)
(world,1)
-------------------------------------------
Time: 1710245220000 ms
-------------------------------------------
(oa,1)
(b,1)
(hello,1)
(world,1)
(s,1)
(a,3)
(hell,1)
(c,2)
-------------------------------------------
Time: 1710245226000 ms
-------------------------------------------
(oa,1)
(b,1)
(hello,2)
(s,1)
(a,3)
(hell,1)
(py,1)
(c,2)
-------------------------------------------
Time: 1710245232000 ms
-------------------------------------------
(hello,4)
(java,1)
(a,2)
(py,1)
(c,1)
-------------------------------------------
Time: 1710245238000 ms
-------------------------------------------
(hello,2)
(java,1)
(a,2)
(c,1)
-------------------------------------------
Time: 1710245244000 ms
-------------------------------------------
window的其他方法:
(1) window(windowLength, slideInterval)
: 基于对源 DStream 窗化的批次进行计算返回一个新的 Dstream;
(2) countByWindow(windowLength, slideInterval)
: 返回一个滑动窗口计数流中的元素个数;
(3) reduceByWindow(func, windowLength, slideInterval)
: 通过使用自定义函数整合滑动区间流元素来创建一个新的单元素流;
(4) reduceByKeyAndWindow(func, windowLength, slideInterval, [numTasks])
: 当在一个(K,V)对的 DStream 上调用此函数,会返回一个新(K,V)对的 DStream,此处通过对滑动窗口中批次数据使用 reduce 函数来整合每个 key 的 value 值。
(5) reduceByKeyAndWindow(func, invFunc, windowLength, slideInterval, [numTasks])
: 这个函数是上述函数的变化版本,每个窗口的 reduce 值都是通过用前一个窗的 reduce 值来递增计算。通过 reduce 进入到滑动窗口数据并"反向 reduce"离开窗口的旧数据来实现这个操作。一个例子是随着窗口滑动对 keys 的"加""减"计数。通过前边介绍可以想到,这个函数只适用于"可逆的 reduce 函数",也就是这些 reduce 函数有相应的"反 reduce"函数(以参数 invFunc 形式传入)。如前述函数, reduce 任务的数量通过可选参数来配置。
java
JavaPairDStream<String, Integer> winoutDS = mapKeyDS.reduceByKeyAndWindow(
(x, y) -> x + y,
(x, y) -> x - y,
Durations.seconds(27),
Durations.seconds(9)
);
/*
-------------------------------------------
Time: 1710246045000 ms
-------------------------------------------
(b,1)
(a,7)
-------------------------------------------
Time: 1710246054000 ms
-------------------------------------------
(b,7)
(,2)
(a,11)
(ab,1)
(ba,1)
-------------------------------------------
Time: 1710246063000 ms
-------------------------------------------
(b,7)
(,2)
(a,12)
(ab,1)
(ba,1)
-------------------------------------------
Time: 1710246072000 ms
-------------------------------------------
(b,6)
(,2)
(a,5)
(ab,1)
(ba,1)
-------------------------------------------
Time: 1710246081000 ms
-------------------------------------------
(b,0)
(,0)
(a,1)
(ab,0)
(ba,0)
*/
countByWindow()和 countByValueAndWindow()作为对数据进行计数操作的简写。
countByWindow()
返回一个表示每个窗口中元素个数的 DStream countByValueAndWindow()
返回的 DStream 则包含窗口中每个值的个数。
DS输出
输出操作指定了对流数据经转化操作得到的数据所要执行的操作(例如把结果推入外部数据库或输出到屏幕上)。与 RDD 中的惰性求值类似,如果一个 DStream 及其派生出的 DStream 都没有被执行输出操作,那么这些 DStream 就都不会被求值。如果 StreamingContext 中没有设定输出操作,整个 context 就都不会启动。 print()
:在运行流程序的驱动结点上打印 DStream 中每一批次数据的最开始 10 个元素。这用于开发和调试。在 Python API 中,同样的操作叫 print()。
saveAsTextFiles(prefix, [suffix])
:以 text 文件形式存储这个 DStream 的内容。每一批次的存储文件名基于参数中的 prefix 和 suffix。prefix-Time_IN_MS[.suffix]
。
saveAsObjectFiles(prefix, [suffix])
:以 Java 对象序列化的方式将 Stream 中的数据保存为 SequenceFiles
。每一批次的存储文件名基于参数中的为prefix-TIME_IN_MS[.suffix]
。Python中目前不可用。
saveAsHadoopFiles(prefix, [suffix])
:将 Stream 中的数据保存为 Hadoop files。每一批次的存储文件名基于参数中的为prefix-TIME_IN_MS[.suffix]
。 Python API 中目前不可用。 foreachRDD(func):这是最通用的输出操作,即将函数 func 用于产生于 stream 的每一个RDD。其中参数传入的函数 func 应该实现将每一个 RDD 中数据推送到外部系统,如将RDD 存入文件或者通过网络将其写入数据库。 通用的输出操作 foreachRDD(),它用来对 DStream 中的 RDD 运行任意计算。这和 transform()有些类似,都可以让我们访问任意 RDD。在 foreachRDD()中,可以重用我们在 Spark 中实现的所有行动操作。比如,常见的用例之一是把数据写到诸如 MySQL 的外部数据库中。
优雅的关闭
流式任务需要 7*24
小时执行,但是有时涉及到升级代码需要主动停止程序,但是分布式程序,没办法做到一个个进程去杀死,所有配置优雅的关闭就显得至关重要了。 需要设置sparkstreaming的优雅关闭配置为true
java
sparkConf.set("spark.streaming.stopGracefullyOnShutdown", "true")
使用外部系统
这里可以是外部的文件系统,或者是NoSQL、MySQL等。思路都是一样的,都是检测外部系统的值作为标志位从而判断程序是否应该停止。
外部文件系统
java
package src.main.streaming;
import org.apache.spark.SparkConf;
import org.apache.spark.streaming.Durations;
import org.apache.spark.streaming.StreamingContextState;
import org.apache.spark.streaming.api.java.JavaReceiverInputDStream;
import org.apache.spark.streaming.api.java.JavaStreamingContext;
public class streamingDemo7 {
public static void main(String[] args) throws InterruptedException {
SparkConf sparkAppConf = new SparkConf().setMaster("local[2]").setAppName("sparkStreamingDemo");
sparkAppConf.set("spark.streaming.stopGracefullyOnShutdown", "true"); // 开启优雅关闭
JavaStreamingContext javaStreamingContext = new JavaStreamingContext(sparkAppConf, Durations.seconds(3));
JavaReceiverInputDStream<String> lines = javaStreamingContext.socketTextStream("localhost", 9999);
lines.print();
javaStreamingContext.start();
// 创建线程在第三方系统中增加程序的关闭状态
new Thread(
new Runnable() {
@Override
public void run() {
while (true){
Boolean stopState = false;
/* TODO
这里写检测第三方系统的逻辑并更新stopState的状态
*/ // 根据状态判断是否需要停止程序
if (stopState){
StreamingContextState scState = javaStreamingContext.getState();
if (scState==StreamingContextState.ACTIVE){
// 当前程序处理活动状态才需要进行关闭操作
javaStreamingContext.stop(true, true);
System.exit(0);
}
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
).start();
javaStreamingContext.awaitTermination();
}
}
redis
可以参考这篇内容:blog.csdn.net/qq_31975963...
在driver端开启一个jetty或者http服务
需要在driver启动一个socket线程,或者http服务,这里推荐使用http服务,因为socket有点偏底层处理起来稍微复杂点,如果使用http服务,我们可以直接用内嵌的jetty,对外暴露一个http接口,spark ui页面用的也是内嵌的jetty提供服务,所以我不需要在pom里面引入额外的依赖,在关闭的时候,找到驱动所在ip,就可以直接通过curl或者浏览器就直接关闭流程序。
找到驱动程序所在的ip,可以在程序启动的log中看到,也可以在spark master ui的页面上找到。这种方式不依赖任何外部的存储系统,仅仅部署的时候需要一个额外的端口号用来暴露http服务。
github: github.com/qindonglian...
恢复数据
在停止程序之后重新启动需要恢复数据,以便从原来的地方开始处理。
java
package src.main.streaming;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.Optional;
import org.apache.spark.api.java.function.FlatMapFunction;
import org.apache.spark.api.java.function.Function0;
import org.apache.spark.api.java.function.Function2;
import org.apache.spark.api.java.function.PairFunction;
import org.apache.spark.streaming.Durations;
import org.apache.spark.streaming.StreamingContext;
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;
public class streamingDemo8 {
public static void main(String[] args) throws InterruptedException {
String checkpointDir = "/home/saberbin/logs/checkpoints/sparkdemo";
// 此方法的第二个参数也可以是匿名函数,如果需要实现需要继承Function0接口并重写call方法
JavaStreamingContext javaStreamingContext = JavaStreamingContext.getOrCreate(checkpointDir, new SparkStreamingContext());
javaStreamingContext.checkpoint(checkpointDir);
javaStreamingContext.start();
javaStreamingContext.awaitTermination();
}
public static class SparkStreamingContext implements Function0<JavaStreamingContext> {
@Override
public JavaStreamingContext call() throws Exception {
// 这里需要将实际的处理逻辑也一并包裹在此处,否则在实例化streaming的时候会报错。因为getOrCreate方法返回的context是无法创建DStream的
SparkConf sparkAppConf = new SparkConf().setMaster("local[2]").setAppName("sparkStreamingDemo");
JavaStreamingContext javaStreamingContext = new JavaStreamingContext(sparkAppConf, Durations.seconds(3));
JavaReceiverInputDStream<String> lines = javaStreamingContext.socketTextStream("192.168.141.177", 9999);
JavaDStream<String> mapDS = lines.flatMap(new FlatMapFunction<String, String>() {
@Override public Iterator<String> call(String s) throws Exception {
return Arrays.asList(s.split(" ")).iterator();
}
});
JavaPairDStream<String, Integer> mapKeyDS = mapDS.mapToPair(new PairFunction<String, String, Integer>() {
@Override public Tuple2<String, Integer> call(String s) throws Exception {
return new Tuple2<>(s, 1);
}
});
JavaPairDStream<String, Integer> reduceDS = mapKeyDS.reduceByKey((x, y) -> x+y);
JavaPairDStream<String, Object> result = reduceDS.updateStateByKey((values, state) -> {
Integer sum = (Integer) state.orElse(0);
for (Integer value : values) {
sum += value;
}
return Optional.of(sum);
});
result.print();
return javaStreamingContext;
} }
}
这里需要注意的就是getOrCreate
方法的第二个参数必须要包括实际的处理逻辑,因为该方法返回的context对象是无法创建DStream的。(版本不同可能会有争议)(不如flink)
说法来源:blog.csdn.net/weixin_4217...