1、sparkStreaming概述
1.1 什么是sparkStreaming
- Spark Streaming makes it easy to build scalable fault-tolerant streaming applications.
- sparkStreaming是一个可以非常容易的构建可扩展、具有容错机制的流式应用程序
- 它就是一个实时处理的程序,数据源源不断的来,然后它就进行实时不断的处理。
1.2 sparkStreaming特性
-
1、易用性
-
可以像开发离线批处理一样去编写实时处理的代码程序
-
可以使用多种不同的语言进行代码开发
-
java
-
scala
-
python
-
-
-
2、容错性
-
sparkStreaming可以实现恰好一次语义
- 数据被处理且只被处理一次
- 避免了数据丢失和数据的重复处理
- 数据被处理且只被处理一次
-
可以实现在没有额外代码的情况下来恢复一些丢失的工作和状态
-
-
3、可以融合到spark生态系统
- sparkStreaming流式处理可以与批处理和交互式查询进行结合使用
2、sparkStreaming原理
2.1 sparkStreaming计算原理
Spark Streaming 是基于spark的流式批处理引擎,其基本原理是把输入数据以某一时间间隔批量的处理,当批处理间隔缩短到秒级时,便可以用于处理实时数据流。
2.2 sparkStreaming计算流程
sparkStreaming是某一个时间间隔的批处理,在时间维度上就划分成了很多job,每一个job都有大量的Dstream,
对Dstream做大量的transformation转换操作,其本质是作用在它内部的rdd上。
也就是说Dstream内部是封装了rdd,rdd内部又有很多个分区,分区里面才是真正的数据。
2.3 sparkStreaming容错性
(1)Dstream中内部是封装了rdd,rdd自身是具有容错机制,就是通过lineage血统来实现某些rdd的分区数据丢失之后,然后进行重新计算恢复得到。
(2)同时对于网络数据的处理,sparkStreaming在接受到数据之后它会把网络中的数据保留多份到其他机器,保证数据源端的安全性。
sc.textFile("/words.txt").flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).collect
rdd1 ----------------->rdd2--------->rdd3------------->rdd4
某个rdd的数据丢失之后恢复逻辑:血统+数据源
2.4 sparkStreaming实时性
storm是来一条数据就处理一条,实时性是比较高
sparkStreaming是以某一时间间隔的批量处理,它的实时性就比较低,延迟就比较高
后期再实际公司中具体使用哪一种框架,需要结合自身的一些业务场景
比如说公司的领导允许数据出现一定的延迟,对数据的实时性要求不是特别高,这个时候可以优先考虑sparkStreaming;
如果对数据的实时性非常高,就考虑storm。
3、DStream介绍
3.1 什么是DStream
Discretized Stream是Spark Streaming的基础抽象,代表持续性的数据流和经过各种Spark算子操作后的结果数据流。在内部实现上,DStream是一系列连续的RDD来表示。每个RDD含有一段时间间隔内的数据.
3.2 DStream上的操作分类
- transformation(转换)
- 可以把一个DStream转换生成一个新的Dstream,它也是延迟加载,不会立即触发任务的运行
- 类似于rdd中transformation操作
- 可以把一个DStream转换生成一个新的Dstream,它也是延迟加载,不会立即触发任务的运行
- outputOperation (输出)
- 它会触发任务的真正运行
- 类似于rdd中action操作
- 它会触发任务的真正运行
4、DStream操作实战
-
添加依赖
<dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-streaming_2.11</artifactId> <version>2.1.3</version> </dependency>
4.1 利用sparkStreaming接受socket数据实现单词统计
- 1、代码开发
package cn.itcast.socket
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.streaming.{Seconds, StreamingContext}
//todo:利用sparkStreaming接受socket数据实现单词统计
object SparkStreamingSocket {
def main(args: Array[String]): Unit = {
//1、创建SparkConf
val sparkConf: SparkConf = new SparkConf().setAppName("SparkStreamingSocket").setMaster("local[2]")
//2、创建SparkContext
val sc = new SparkContext(sparkConf)
sc.setLogLevel("warn")
//3、创建StreamingContext 需要一个SparkContext对象,还有一个批处理时间间隔 表示每隔5s处理上一个5s的数据
val ssc = new StreamingContext(sc,Seconds(5))
//4、接受socket数据
val socketTextStream: ReceiverInputDStream[String] = ssc.socketTextStream("node1",9999)
//5、切分每一行数据
val words: DStream[String] = socketTextStream.flatMap(_.split(" "))
//6、每个单词计为1
val wordAndOne: DStream[(String, Int)] = words.map((_,1))
//7、相同单词出现的1累加
val result: DStream[(String, Int)] = wordAndOne.reduceByKey(_+_)
//8、打印输出
result.print()
//9、开启流式计算
ssc.start()
ssc.awaitTermination()
}
}
4.2 利用sparkStreaming接受socket数据实现所有批次单词统计的结果累加
- 1、代码开发
- updateStateByKey
- 可以按照key去更新状态,key—>单词 状态—>次数
- updateStateByKey
package cn.itcast.socket
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
//todo:利用sparkStreaming接受socket数据实现所有批次单词统计的结果累加
object SparkStreamingSocketTotal {
//currentValues:当前批次相同的单词出现的所有的1 (hadoop,1)(hadoop,1)(hadoop,1)----->List(1,1,1)
//historyValues: 在之前所有批次中相同单词出现的总次数 Option类型可以表示有值(Some)或者是没有值(None)
def updateFunc(currentValues:Seq[Int],historyValues:Option[Int]):Option[Int] = {
val newValue: Int = currentValues.sum + historyValues.getOrElse(0)
Some(newValue)
}
def main(args: Array[String]): Unit = {
//1、创建SparkConf
val sparkConf: SparkConf = new SparkConf().setAppName("SparkStreamingSocketTotal").setMaster("local[2]")
//2、创建SparkContext
val sc = new SparkContext(sparkConf)
sc.setLogLevel("warn")
//3、创建StreamingContext 需要一个SparkContext对象,还有一个批处理时间间隔 表示每隔5s处理上一个5s的数据
val ssc = new StreamingContext(sc,Seconds(5))
//设置ckeckpoint目录,用于保存每一个单词在之前的所有批次中出现的总次数
ssc.checkpoint("./socket")
//4、接受socket数据
val socketTextStream: ReceiverInputDStream[String] = ssc.socketTextStream("node1",9999)
//5、切分每一行数据
val words: DStream[String] = socketTextStream.flatMap(_.split(" "))
//6、每个单词计为1
val wordAndOne: DStream[(String, Int)] = words.map((_,1))
//7、相同单词出现的1累加
val result: DStream[(String, Int)] = wordAndOne.updateStateByKey(updateFunc)
//8、打印输出
result.print()
//9、开启流式计算
ssc.start()
ssc.awaitTermination()
}
}
4.3 利用sparkStreaming接受socket数据实现单词统计–使用开窗函数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gba23OsM-1572241319579)(reduceByKeyAndWindow使用介绍.png)]
- 1、代码开发
- reduceByKeyAndWindow
package cn.itcast.socket
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
//todo:利用sparkStreaming接受socket数据实现单词统计---使用开窗函数
object SparkStreamingSocketWindow {
def main(args: Array[String]): Unit = {
//1、创建SparkConf
val sparkConf: SparkConf = new SparkConf().setAppName("SparkStreamingSocketWindow").setMaster("local[2]")
//2、创建SparkContext
val sc = new SparkContext(sparkConf)
sc.setLogLevel("warn")
//3、创建StreamingContext 需要一个SparkContext对象,还有一个批处理时间间隔 表示每隔5s处理上一个5s的数据
val ssc = new StreamingContext(sc,Seconds(5))
//4、接受socket数据
val socketTextStream: ReceiverInputDStream[String] = ssc.socketTextStream("node1",9999)
//5、切分每一行数据
val words: DStream[String] = socketTextStream.flatMap(_.split(" "))
//6、每个单词计为1
val wordAndOne: DStream[(String, Int)] = words.map((_,1))
//7、相同单词出现的1累加
//reduceFunc: (V, V) => V, 就是一个函数
//windowDuration: Duration, 窗口的长度
//slideDuration: Duration 滑动窗口的时间间隔,它表示每隔多久计算一次
val result: DStream[(String, Int)] = wordAndOne.reduceByKeyAndWindow((x:Int,y:Int)=>x+y,Seconds(10),Seconds(10))
//8、打印输出
result.print()
//9、开启流式计算
ssc.start()
ssc.awaitTermination()
}
}
4.4 利用sparkStreaming接受socket数据实现一定时间内的热门词汇
-
1、代码开发
-
transform
- 实现把一个Dstream转换生成一个新的Dstream,内部需要一个函数,函数的输入参数就是前面Dstream中的rdd,函数返回值是一个新的RDD
-
foreachRDD
- 它是一个outputOperation,会触发任务的运行,需要一个函数,函数的输入参数还是前面Dstream中的rdd,最后函数的返回值是Unit表示没有
package cn.itcast.socket
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
//todo:利用sparkStreaming接受socket数据实现一定时间内热门词汇统计
object SparkStreamingSocketWindowHotWord {
def main(args: Array[String]): Unit = {
//1、创建SparkConf
val sparkConf: SparkConf = new SparkConf().setAppName("SparkStreamingSocketWindowHotWord").setMaster("local[2]")
//2、创建SparkContext
val sc = new SparkContext(sparkConf)
sc.setLogLevel("warn")
//3、创建StreamingContext 需要一个SparkContext对象,还有一个批处理时间间隔 表示每隔5s处理上一个5s的数据
val ssc = new StreamingContext(sc,Seconds(5))
//4、接受socket数据
val socketTextStream: ReceiverInputDStream[String] = ssc.socketTextStream("node1",9999)
//5、切分每一行数据
val words: DStream[String] = socketTextStream.flatMap(_.split(" "))
//6、每个单词计为1
val wordAndOne: DStream[(String, Int)] = words.map((_,1))
//7、相同单词出现的1累加
//reduceFunc: (V, V) => V, 就是一个函数
//windowDuration: Duration, 窗口的长度
//slideDuration: Duration 滑动窗口的时间间隔,它表示每隔多久计算一次
val result: DStream[(String, Int)] = wordAndOne.reduceByKeyAndWindow((x:Int,y:Int)=>x+y,Seconds(10),Seconds(10))
//8、按照单词出现的次数降序
val finalResult: DStream[(String, Int)] = result.transform(rdd => {
//可以使用rdd中排序的方法去操作
val sortRDD: RDD[(String, Int)] = rdd.sortBy(_._2, false)
//取出出现次数最多的前3位
val top3: Array[(String, Int)] = sortRDD.take(3)
//打印
println("===============top3 start===============")
top3.foreach(println)
println("===============top3 end=================")
sortRDD
})
//8、打印输出
finalResult.print()
//9、开启流式计算
ssc.start()
ssc.awaitTermination()
}
}
5、sparkStreaming整合flume
5.1 poll拉模式(优先考虑)
-
1、需要把spark-streaming-flume-sink_2.11-2.1.3.jar拷贝到flume/lib
-
2、还需要把flume中自带的scala依赖由2.10改成2.11
-
3、flume配置文件
- flume-poll-spark.conf
#source a1.sources.r1.channels = c1 a1.sources.r1.type = spooldir a1.sources.r1.spoolDir = /root/data a1.sources.r1.fileHeader = true #channel a1.channels.c1.type =memory a1.channels.c1.capacity = 20000 a1.channels.c1.transactionCapacity=5000 #sinks a1.sinks.k1.channel = c1 a1.sinks.k1.type = org.apache.spark.streaming.flume.sink.SparkSink a1.sinks.k1.hostname=node1 a1.sinks.k1.port = 8888 a1.sinks.k1.batchSize= 2000
-
4、可以启动flume
bin/flume-ng agent -n a1 -c conf -f conf/flume-poll-spark.conf -Dflume.root.logger=info,console
-
5、sparkStreaming代码开发
-
引入依赖
<dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-streaming-flume_2.11</artifactId> <version>2.1.3</version> </dependency>
-
代码开发
package cn.itcast.flume import java.net.InetSocketAddress import org.apache.spark.storage.StorageLevel import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream} import org.apache.spark.streaming.{Seconds, StreamingContext} import org.apache.spark.{SparkConf, SparkContext} import org.apache.spark.streaming.flume.{FlumeUtils, SparkFlumeEvent} //todo:sparkStreaming整合flume--------poll拉模式 object SparkStreamingPollFlume { def main(args: Array[String]): Unit = { //1、创建SparkConf val sparkConf: SparkConf = new SparkConf().setAppName("SparkStreamingPollFlume").setMaster("local[2]") //2、创建SparkContext val sc = new SparkContext(sparkConf) sc.setLogLevel("warn") //3、创建StreamingContext val ssc = new StreamingContext(sc,Seconds(5)) //4、通过poll拉模式获取flume数据 val pollingStream: ReceiverInputDStream[SparkFlumeEvent] = FlumeUtils.createPollingStream(ssc,"node1",8888) //拉取多个flume收集到的数据 // val address=List(new InetSocketAddress("node1",8888),new InetSocketAddress("node2",8888),new InetSocketAddress("node3",8888)) // val totalFlume: ReceiverInputDStream[SparkFlumeEvent] = FlumeUtils.createPollingStream(ssc,address,StorageLevel.MEMORY_AND_DISK_SER_2) //flume中数据传输的最小单元:event event中有什么? headers body // event:{"headers":xxxxx,"body":xxxxxxx} //5、获取flume中的真实数据 new String(Array[byte]) val data: DStream[String] = pollingStream.map(x=>new String(x.event.getBody.array())) //6、切分每一行获取所有的单词 val words: DStream[String] = data.flatMap(_.split(" ")) //7、每个单词计为1 val wordAndOne: DStream[(String, Int)] = words.map((_,1)) //8、相同单词出现的1累加 val result: DStream[(String, Int)] = wordAndOne.reduceByKey(_+_) //9、打印 result.print() //10 开启流式计算 ssc.start() ssc.awaitTermination() } }
-
5.2 push推模式
-
1、修改flume配置
- vim flume-push-spark.conf
#push mode a1.sources = r1 a1.sinks = k1 a1.channels = c1 #source a1.sources.r1.channels = c1 a1.sources.r1.type = spooldir a1.sources.r1.spoolDir = /root/data a1.sources.r1.fileHeader = true #channel a1.channels.c1.type =memory a1.channels.c1.capacity = 20000 a1.channels.c1.transactionCapacity=5000 #sinks a1.sinks.k1.channel = c1 a1.sinks.k1.type = avro a1.sinks.k1.hostname=192.168.160.34 a1.sinks.k1.port = 8888 a1.sinks.k1.batchSize= 2000
-
2、代码开发
package cn.itcast.flume import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream} import org.apache.spark.{SparkConf, SparkContext} import org.apache.spark.streaming.{Seconds, StreamingContext} import org.apache.spark.streaming.flume.{FlumeUtils, SparkFlumeEvent} //todo:sparkStreaming整合flume--------push推模式 object SparkStreamingPushFlume { def main(args: Array[String]): Unit = { //1、创建SparkConf val sparkConf: SparkConf = new SparkConf().setAppName("SparkStreamingPushFlume").setMaster("local[2]") //2、创建SparkContext val sc = new SparkContext(sparkConf) sc.setLogLevel("warn") //3、创建StreamingContext val ssc = new StreamingContext(sc,Seconds(5)) //4、通过push推模式 接受flume的数据,这里的hostname和port端口是sparkStreaming应用程序所在服务器地址和端口 val stream: ReceiverInputDStream[SparkFlumeEvent] = FlumeUtils.createStream(ssc,"192.168.160.34",8888) //5、获取flume中的真实数据 new String(Array[byte]) val data: DStream[String] = stream.map(x=>new String(x.event.getBody.array())) //6、切分每一行获取所有的单词 val words: DStream[String] = data.flatMap(_.split(" ")) //7、每个单词计为1 val wordAndOne: DStream[(String, Int)] = words.map((_,1)) //8、相同单词出现的1累加 val result: DStream[(String, Int)] = wordAndOne.reduceByKey(_+_) //9、打印 result.print() //10 开启流式计算 ssc.start() ssc.awaitTermination() } }
6、sparkStreaming整合kafka
6.1、KafkaUtils.creataStream
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i9kmH0VZ-1572241319581)(sparkStreaming整合kafka–基于receiver接收器.png)]
它是基于receiver接收器去拉取数据,默认数据会丢失,开启WAL日志把接受到的数据同步写入到hdfs上保证数据源的安全性,后期某些rdd的部分分区数据丢失之后,可以通过血统+原始数据重新计算恢复得到。
可以保证数据不丢失,但是它保证不了数据被处理只被处理一次,会出现数据的重复处理。
-
添加依赖
<dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-streaming-kafka-0-8_2.11</artifactId> <version>2.1.3</version> </dependency>
-
代码开发
package cn.itcast.kafka import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream} import org.apache.spark.{SparkConf, SparkContext} import org.apache.spark.streaming.{Seconds, StreamingContext} import org.apache.spark.streaming.kafka.KafkaUtils import scala.collection.immutable //todo:sparkStreaming整合kafka-----------kafka高级api(消息的偏移量保存zk,基于receiver接受器接受数据) object SparkStreamingKafkaReceiver { def main(args: Array[String]): Unit = { //1、创建SparkConf val sparkConf: SparkConf = new SparkConf() .setAppName("SparkStreamingKafkaReceiver") .setMaster("local[4]") //开启WAL日志,把接受到的数据写入到HDFS上,保证数据源端的安全性 .set("spark.streaming.receiver.writeAheadLog.enable","true") //2、创建SparkContext val sc = new SparkContext(sparkConf) sc.setLogLevel("warn") //3、创建StreamingContext val ssc = new StreamingContext(sc,Seconds(5)) //设置一个checkpoint目录,用于存储接受到的数据,保证数据的安全 ssc.checkpoint("./spark-recever") //4、指定zk地址 val zkQuorum="node1:2181,node2:2181,node3:2181" //5、消费者组id val groupId="spark-receiver" //6、指定topic信息 key:表示topic的名称,value:表示一个recevier接收器需要使用1个线程去拉取数据 val topics=Map("heima" ->1) /** ssc: StreamingContext, zkQuorum: String, groupId: String, topics: Map[String, Int], storageLevel: StorageLevel = StorageLevel.MEMORY_AND_DISK_SER_2 */ //7、通过receiver接收器接受kafka的topic数据 //(String, String)------> 第一个String表示消息的key, 第二个String就是消息内容 //默认使用了一个receiver接收器去接受数据,数据接受效率比较慢,后期可以使用多个receiver接收器接受数据 //开启3个receiver接受数据,提升数据接收的效率 val totalReceiverSeq: immutable.IndexedSeq[ReceiverInputDStream[(String, String)]] = (1 to 3).map(x => { val kafkaStream: ReceiverInputDStream[(String, String)] = KafkaUtils.createStream(ssc, zkQuorum, groupId, topics) kafkaStream }) //通过调用union方法把所有recevier接收器得到的每一个Dstream进行合并生成一个Dstream val totalDstream: DStream[(String, String)] = ssc.union(totalReceiverSeq) //8、获取topic的数据 val data: DStream[String] = totalDstream.map(x=>x._2) //9、切分每一行获取所有的单词 val words: DStream[String] = data.flatMap(_.split(" ")) //10、每个单词计为1 val wordAndOne: DStream[(String, Int)] = words.map((_,1)) //11、相同单词出现的1累加 val result: DStream[(String, Int)] = wordAndOne.reduceByKey(_+_) //12、打印 result.print() //13 开启流式计算 ssc.start() ssc.awaitTermination() } }
6.2 KafkaUtils.createDirectStream(优先考虑)
-
可以实现数据被处理且只被处理一次
就是通过数据处理和更新偏移量这2个操作在同一事务。 偏移量的保存可以保存在任何外部存储介质中,比如 文件、mysql、zk、redis
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6slDbTq5-1572241319583)(1.png)]
-
1、代码开发
package cn.itcast.kafka import kafka.serializer.StringDecoder import org.apache.spark.streaming.dstream.{DStream, InputDStream} import org.apache.spark.{SparkConf, SparkContext} import org.apache.spark.streaming.{Seconds, StreamingContext} import org.apache.spark.streaming.kafka.KafkaUtils //todo:sparkStreaming整合kafka-------使用低级api(消息的偏移量不在由zk保存,客户端自己去维护) object SparkStreamingDirectKafka { def main(args: Array[String]): Unit = { //1、创建SparkConf val sparkConf: SparkConf = new SparkConf() .setAppName("SparkStreamingDirectKafka") .setMaster("local[4]") //2、创建SparkContext val sc = new SparkContext(sparkConf) sc.setLogLevel("warn") //3、创建StreamingContext val ssc = new StreamingContext(sc,Seconds(5)) //设置checkpoint目录------> 保存消息消费的偏移量的 ssc.checkpoint("./spark-direct") //4、指定kafka集群参数 val kafkaParams=Map("bootstrap.servers"->"node1:9092,node2:9092,node3:9092","group.id" ->"spark-direct") //5、指定消费的topic名称 val topics=Set("heima") val dstream: InputDStream[(String, String)] = KafkaUtils.createDirectStream[String,String,StringDecoder,StringDecoder](ssc,kafkaParams,topics) //6、获取topic的内容 val data: DStream[String] = dstream.map(x=>x._2) //7、切分每一行获取所有的单词 val words: DStream[String] = data.flatMap(_.split(" ")) //8、每个单词计为1 val wordAndOne: DStream[(String, Int)] = words.map((_,1)) //9、相同单词出现的1累加 val result: DStream[(String, Int)] = wordAndOne.reduceByKey(_+_) //10、打印 result.print() //11 开启流式计算 ssc.start() ssc.awaitTermination() } }
-
2、程序逻辑梳理
这个时候并没有使用receiver接收器去接受数据,也没有把接受到的数据写入到HDFS上,相对于第一套api代码开发起来比较简洁。 要想保证数据不丢失和数据被处理且只被处理一次: (1)数据处理 (2)保存偏移量 如果可以确保这2个操作在同一事务中。
-
3、打成jar包提交到集群中运行
在实际开发中,先进行本地测试,本地测试只是看一下程序的处理逻辑对不对,如果有问题就及时修改,如果没有问题就把该成打成jar包提交到集群去运行。 spark-submit --master spark://node1:7077 --class cn.itcast.kafka.SparkStreamingDirectKafka --executor-memory 1g --total-executor-cores 8 original-spark_class15-1.0-SNAPSHOT.jar --executor-memory 1g --total-executor-cores 8 这个时候就需要给当前这个任务准备资源,这个资源到底给多少合理? 资源给多了就浪费,给少了跑的慢.. 我也不知道到底给定多少资源是合理的,但是我们可以找到一个比较合理的状态。 比如说我们在代码中的设置逻辑:每隔5s就处理上一个5s的数据。 由于不知道数据量,可能会出现一个批次的数据的处理时间是1分钟。 第一个5s的批次数据: -------------------------------------> 1分钟 第二个5s的批次数据: -------------------------------------> 1分钟 第三个5s的批次数据: -------------------------------------> 1分钟 第四个5s的批次数据: -------------------------------------> 1分钟 ....... 上面这种情况下就会出现大量的job在等待,也就出现了数据的积压,延迟是非常高。 最理想的状态:就是在5s之内就把5s的数据处理完成。 这个时候可以访问spark的web管理界面: master主机名;8080 去观察每一个批次的数据处理时间 在给定资源的时候,可以进行几组资源参数进行测试: (1)--executor-memory 5g --total-executor-cores 10 (2)--executor-memory 5g --total-executor-cores 20 (3)--executor-memory 6g --total-executor-cores 20 (4)--executor-memory 6g --total-executor-cores 30 最后找到一个最理想的参数,然后提交到集群去运行。