Spark整理(6)
一,SparkStreaming简介
SparkStreaming是流式处理框架,是Spark API的扩展,支持可扩展,高吞吐量,容错的实时数据流处理,实时数据的来源可以是:Kafka,Flume,Twitter,ZeroMQ,或者TCP sockets,并且可以使用高级功能的复杂算子来处理流数据。例如:map,reduce,join,window.最终,处理后的数据可以存放在文件系统,数据库等。
二,SparkStreaming和Storm的区别
- Storm是纯实时的流式处理框架,SparkStreaming是准实时的处理框架(微批处理)。正是因为如此,SparkStreaming的吞吐要比Storm要高。
- Storm的事务机制要比SparkStreaming的要完善。
- Storm支持动态资源调度,(SparkStreaming在 1.2之后也支持)。
- SparkStreaming擅长复杂的业务处理,Storm不擅长复杂的业务处理,擅长简单的汇总型计算。
三,SparkStreaming工作流程
3.1 工作流程图
这里数据源拿TCP socket举例
注意:
- receiver task是 7*24小时一直执行,一直接收数据,将一段时间内接收到的数据保存到batch中。假设bacthinterval为5s,那么会将接收到的数据每隔5秒封装到一个bacth中,batch没有分布式的计算特性,这一个bacth的数据又被封装到一个RDD中,RDD最终封装到一个DStream中。
例如:假设batchinterval为5秒时,每隔5秒通过SparkStreaming将得到的一个DStream,在第6秒的时候计算这5秒的数据,假设执行任务的时间是3秒,那么第6-9秒一边在接收数据,一边在计算任务,9-10只是在计算数据。然后在第11秒的时候重复上面的操作。
- 如果Job执行的时间大于bacthinterval会有什么样的问题?
如果接收过来的数据设置的级别是仅内存,接收来的数据会越堆积越多,最后可能会导致OOM,如果设置StorgeLevel包含disk,则内存存放不下的数据会溢写到disk,加大延迟。
3.2 SparkStreaming代码
package com.shsxt.spark.stream;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.function.*;
import org.apache.spark.streaming.Durations;
import org.apache.spark.streaming.Time;
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;
public class Demo {
public static void main(String args[]){
SparkConf conf = new SparkConf();
conf.setMaster("local[2]").setAppName("wc");
JavaStreamingContext jsc = new JavaStreamingContext(conf, Durations.seconds(5));
JavaReceiverInputDStream<String> lineDStream = jsc.socketTextStream("node03", 1010);
JavaDStream<String> wordDStream = lineDStream.flatMap(new FlatMapFunction<String, String>() {
@Override
public Iterable<String> call(String s) throws Exception {
System.out.println(s+"================");
return Arrays.asList(s.split(" "));
}
});
JavaPairDStream<String, Integer> pairDStream = wordDStream.mapToPair(new PairFunction<String, String, Integer>() {
@Override
public Tuple2<String, Integer> call(String s) throws Exception {
return new Tuple2<>(s, 1);
}
});
JavaPairDStream<String, Integer> reduceByKeyDStream = pairDStream.reduceByKey(new Function2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer v1, Integer v2) throws Exception {
return v1 + v2;
}
});
// reduceByKeyDStream.foreachRDD(new VoidFunction<JavaPairRDD<String, Integer>>() {
// @Override
// public void call(JavaPairRDD<String, Integer> pairRDD) throws Exception {
//
// System.out.println("***************************");
//
// pairRDD.filter(new Function<Tuple2<String, Integer>, Boolean>() {
// @Override
// public Boolean call(Tuple2<String, Integer> v1) throws Exception {
//
// System.out.println("***********************");
//
// return true;
// }
// }).count();
// }
// });
reduceByKeyDStream.print();
jsc.start();
jsc.awaitTermination();
}
}
注意事项:
-
启动socket server 服务器 : nc -lk 1010(先安装nc服务,命令:yum install -y nc)
-
receiver模式下接收数据,local的模拟线程必须大于等于2,一个线程用来receiver用来接收数据,另一个线程用来执行job。
-
Durations 时间设置就是我们能接收的延迟度。这个需要根据集群的资源情况以及任务的执行情况来调节。
-
创建JavaStreamingContext有两种方式(SparkConf 和 SparkContext)
-
所有的代码逻辑完成后要有一个 output operation算子。
-
JavaStreamingContext.start() Streaming框架启动后不能在添加业务逻辑。
-
JavaStreamingContext.stop() 无参的stop方法将SparkContext一同关闭。stop(false),不会关闭SparkContext
-
JavaStreamingContext.stop() 停止之后不能再调用 start.
四,SparkStreaming算子操作
4.1 foreachRDD
output operation算子,必须对抽取出来的RDD执行action类算子,代码才能执行。
package com.shsxt.spark.stream;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.function.VoidFunction;
import org.apache.spark.streaming.Durations;
import org.apache.spark.streaming.api.java.JavaReceiverInputDStream;
import org.apache.spark.streaming.api.java.JavaStreamingContext;
public class Operate_ForeachRDD {
public static void main(String args[]){
SparkConf conf = new SparkConf();
conf.setMaster("local[2]").setAppName("foreachRDD");
JavaStreamingContext jsc = new JavaStreamingContext(conf, Durations.seconds(5));
JavaReceiverInputDStream<String> socketDStream = jsc.socketTextStream("node03", 1010);
// socketDStream.print();
socketDStream.foreachRDD(new VoidFunction<JavaRDD<String>>() {
@Override
public void call(JavaRDD<String> stringJavaRDD) throws Exception {
stringJavaRDD.foreach(new VoidFunction<String>() {
@Override
public void call(String s) throws Exception {
System.out.println(s);
}
});
}
});
jsc.start();
jsc.awaitTermination();
jsc.stop();
}
}
4.2 transform
transform类算子
可以通过transform算子,对DStream做RDD到RDD的任意操作。
package com.shsxt.spark.stream;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.function.Function;
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.ArrayList;
import java.util.Arrays;
/**
* 过滤黑名单
* transform操作
* DStream可以通过transform做RDD到RDD的任意操作
*/
public class TransformOperator {
public static void main(String args[]){
SparkConf conf = new SparkConf();
conf.setMaster("local[2]").setAppName("transformOperator");
JavaStreamingContext jsc = new JavaStreamingContext(conf, Durations.seconds(5));
//模拟黑名单数据
ArrayList<String> blackList = new ArrayList<>();
blackList.add("zhangsan");
//广播黑名单
Broadcast<ArrayList<String>> nameBroad = jsc.sparkContext().broadcast(blackList);
//接收socket数据源
JavaReceiverInputDStream<String> linesDStream = jsc.socketTextStream("node03", 1010);
JavaPairDStream<String, String> pairNameRDD = linesDStream.mapToPair(new PairFunction<String, String, String>() {
@Override
public Tuple2<String, String> call(String s) throws Exception {
return new Tuple2<String, String>(s.split(" ")[1], s);
}
});
JavaDStream<String> transformRDD = pairNameRDD.transform(new Function<JavaPairRDD<String, String>, JavaRDD<String>>() {
@Override
public JavaRDD<String> call(JavaPairRDD<String, String> nameRDD) throws Exception {
JavaPairRDD<String, String> filter = nameRDD.filter(new Function<Tuple2<String, String>, Boolean>() {
@Override
public Boolean call(Tuple2<String, String> v1) throws Exception {
return !blackList.contains(v1._1);
}
});
return filter.map(new Function<Tuple2<String, String>, String>() {
@Override
public String call(Tuple2<String, String> v1) throws Exception {
return v1._2;
}
});
}
});
transformRDD.print();
// pairNameRDD.transform(new Function<JavaPairRDD<String, String>, JavaRDD<String>>() {
//
// /**
// * linesDStrea: 1 zhangsan
// * 2 lisi
// *pairNameRDD: zhangsan , 1 zhangsan
// * lisi , 2 lisi
// *nameRDD zhangsan , 1 zhangsan
// * lisi , 2 lisi
// */
//
// @Override
// public JavaRDD<String> call(JavaPairRDD<String, String> nameRDD) throws Exception {
//
// JavaPairRDD<String, String> rdd = nameRDD.filter(new Function<Tuple2<String, String>, Boolean>() {
// @Override
// public Boolean call(Tuple2<String, String> tuple2) throws Exception {
// return !nameBroad.value().contains(tuple2._1);
// }
// });
//
// rdd.foreach(new VoidFunction<Tuple2<String, String>>() {
// @Override
// public void call(Tuple2<String, String> tuple2) throws Exception {
// System.out.println(tuple2);
// }
// });
//
//
// return null;
// }
// });
jsc.start();
jsc.awaitTermination();
jsc.stop();
}
}
4.3 updateStateByKey
transform类算子
updateStateByKey作用:
- 为SparkStreaming中每一个Key维护一份state状态,state类型可以是任意类型的,可以是一个自定义的对象,更新函数也可以是自定义的
- 通过更新函数对该key的状态不断更新,对于每个新的batch而言,SparkStreaming会在使用uodateStateByKey的时候为已经存在的key进行state的状态更新。
因为SparkStreaming是一批一批处理,通过updateStateBykey算子我们可以对数据进行批与批之间的汇总操作。
- 使用到updateStateByKey要开启checkpoint机制和功能
- 多久会将内存中的数据写入到磁盘一份?
如果batchinterval设置的时间小于10秒,那么10秒写入磁盘一份,如果batchinterval设置的时间大于10秒,那么则以batchinterval为间隔写入磁盘。
package com.shsxt.spark.stream;
import com.google.common.base.Optional;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
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.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.List;
public class UpdateStateByKey {
public static void main(String args[]){
SparkConf conf = new SparkConf();
conf.setMaster("local[2]").setAppName("updateStateByKey");
JavaStreamingContext jsc = new JavaStreamingContext(conf, Durations.seconds(5));
/**
* 设置checkpoint目录
* 多久会将内存中的数据(每一个key所对应的状态)写入到磁盘上一份呢?
* 如果你的batch interval小于 10秒,那么每隔10秒会将内存中的数据写入到磁盘中
* 如果你的batch interval大于10秒,那么就以bacth interval为间隔时间写入磁盘
*/
jsc.sparkContext().setCheckpointDir("./checkpoint");
JavaReceiverInputDStream<String> jscDStream = jsc.socketTextStream("node03", 1010);
JavaDStream<String> wordRDD = jscDStream.flatMap(new FlatMapFunction<String, String>() {
@Override
public Iterable<String> call(String s) throws Exception {
return Arrays.asList(s.split(" "));
}
});
JavaPairDStream<String, Integer> mapPairRDD = wordRDD.mapToPair(new PairFunction<String, String, Integer>() {
@Override
public Tuple2<String, Integer> call(String s) throws Exception {
return new Tuple2<>(s, 1);
}
});
JavaPairDStream<String, Integer> updateStateByKeyRDD = mapPairRDD.updateStateByKey(new Function2<List<Integer>, Optional<Integer>, Optional<Integer>>() {
/**
* values :经过分组最后 这个Key所对应的value {1,1,1....}
* state 这个key是本次之前的状态
* @param values
* @param state
* @return
* @throws Exception
*/
@Override
public Optional<Integer> call(List<Integer> values, Optional<Integer> state) throws Exception {
Integer updateValue = 0;
if (state.isPresent()) {
updateValue = state.get();
}
System.out.println(updateValue + " ====================");
for (Integer value : values) {
updateValue += value;
}
return Optional.of(updateValue);
}
});
updateStateByKeyRDD.print();
updateStateByKeyRDD.foreachRDD(new VoidFunction<JavaPairRDD<String, Integer>>() {
@Override
public void call(JavaPairRDD<String, Integer> pairRDD) throws Exception {
System.out.println(pairRDD);
pairRDD.collect();
}
});
jsc.start();
jsc.awaitTermination();
jsc.stop();
}
}
4.4 窗口操作
- 窗口操作理解图
如果每隔5秒1个batch,上图中的窗口长度为15s,窗口滑动间隔为10s.
-
窗口长度和滑动间隔必须是batchinterval的整数倍。如果不是整数被会检测报错
-
优化窗口操作示意图
优化后的窗口操作要保存状态所以要设置chechpoint路径,没有优化的窗口函数可以不设置checkpoint的路径。
package com.shsxt.spark.stream;
import org.apache.spark.SparkConf;
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.api.java.*;
import scala.Tuple2;
import java.util.Arrays;
/**
* 基于滑动创建偶的热点搜索词实时统计
*/
public class WindowOperator {
public static void main(String args[]){
SparkConf conf = new SparkConf();
conf.setMaster("local[2]").setAppName("windowHotWord");
JavaStreamingContext jsc = JavaStreamingContext.getOrCreate("./checkPoint", new Function0<JavaStreamingContext>() {
@Override
public JavaStreamingContext call() throws Exception {
System.out.println("新的Driver启动........................");
return new JavaStreamingContext(conf, Durations.seconds(5));
}
});
/**
* 没有优化的窗口函数可以不设置checkpoint目录
* 优化的窗口函数必须设置checkpoint目录
*/
jsc.sparkContext().setCheckpointDir("./checkPoint");
jsc.checkpoint("./checkPoint");
JavaReceiverInputDStream<String> socketDStream = jsc.socketTextStream("node03", 1010);
JavaDStream<String> linesDStream = socketDStream.flatMap(new FlatMapFunction<String, String>() {
@Override
public Iterable<String> call(String s) throws Exception {
return Arrays.asList(s.split(" "));
}
});
JavaPairDStream<String, Integer> wordPairDStream = linesDStream.mapToPair(new PairFunction<String, String, Integer>() {
@Override
public Tuple2<String, Integer> call(String s) throws Exception {
return new Tuple2<>(s, 1);
}
});
// JavaPairDStream<String, Integer> searchWordCountDStream = wordPairDStream.reduceByKeyAndWindow(new Function2<Integer, Integer, Integer>() {
// @Override
// public Integer call(Integer v1, Integer v2) throws Exception {
// return v1 + v2;
// }
// }, Durations.seconds(15), Durations.seconds(5));
//
// searchWordCountDStream.print();
/**
* window窗口操作优化
*/
JavaPairDStream<String, Integer> resultWordCountDStream = wordPairDStream.reduceByKeyAndWindow(new Function2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer v1, Integer v2) throws Exception {
return v1 + v2;
}
}, new Function2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer v1, Integer v2) throws Exception {
return v1 - v2;
}
}, Durations.seconds(15), Durations.seconds(5));
resultWordCountDStream.print();
jsc.start();
jsc.awaitTermination();
jsc.stop();
}
}
五,Driver HA(Standalone或者Mesos)
因为SparkStreaming是7*24小时运行的,Driver只是一个简单的进程,有可能挂掉,Driver挂掉了,那么Executor也就挂掉了(这里指的是集群环境下的情况)。Yarn平台cluster模式下提交任务,ApplicationMaster相当于Driver,如果挂掉会自动启动AM.这里所说的DriverHA针对的是SparkStreaming和Mesos资源调度的情况下。实现Driver的高可用的两个步骤:
1.提交任务层面,在提交任务的时候加上选项 --supervise ,当Driver挂掉的时候会自动重启Driver.
2.代码层面:使用JavaStreamingContext.getOrCreate(checkpoint路径,JavaStreamingContextFactory)
Driver中元数据包括:
- 创建应用程序的配置信息
- DStream的操作逻辑
- Job中没有完成的批次数据,也就是job的执行进度。
代码:
SparkConf conf = new SparkConf();
conf.setMaster("local[2]").setAppName("windowHotWord");
//创建前先去checkpoint文件中查询,不存在数据就新建一个Driver
JavaStreamingContext jsc = JavaStreamingContext.getOrCreate("./checkPoint", new Function0<JavaStreamingContext>() {
@Override
public JavaStreamingContext call() throws Exception {
System.out.println("新的Driver启动........................");
return new JavaStreamingContext(conf, Durations.seconds(5));
}
});
六,SparkStreaming+Kafka
6.1 receiver模式
-
receiver原理图:
-
receiver模式理解
在SparkStreaming程序运行起来后,Executor中会有receiver tasks接收kafka 推送过来的消息。数据会被持久化,默认持久化的级别是 Memory_AND_DISK_ser_2,这个级别也可以修改。reveiver task会对接收过来数据进行存储和备份,这个过程会有节点之间的数据传输。备份完成后,会去zookeeper中更新消费偏移量,然后向Driver中的receiver tracker汇报数据的位置。最后Driver根据数据本地化将task分发到不同节点上执行。
-
receiver模式存在的问题
当Driver进程挂掉后,Driver下的executor都会被杀掉,当更新完zookeeper消费偏移量的时候,Driver如果挂掉了,就会存在找不到数据的问题,相当于丢失数据。
- 如何解决数据丢失的问题?
开启WAL(write and log) 预写日志机制,在接收过来数据备份到其他节点的时候,同时备份到HDFS上一份(我们需要将接收来的数据的持久化级别降级到 Memory_AND_DISK_ser),这样就能保证数据的安全性。不过,写HDFS比较消耗性能,要在备份完数据之后才能进行更新偏移量和汇报位置等,这样会增加任务的执行时间,增加了延迟度。
- receiver模式的并行度设置
receiver的并行度是由 spark.streaming.blockInterval来决定的,默认为200ms,假设batchinterval为5s,那么每隔5秒blockInterval会产生一个block,对应的就是RDD中的一个partition,这样5秒产生的这个DStream中的RDD的partition为25个,并行度就是25。如果向提高并行度可以降低该参数的值,但是最好不要低于50ms.
- 优化后的重复消费问题
存在重复消费的场景,如果在kafka发送完之后,receiver task 接收到数据后,做完备份数据之后,在向zookeeper更新偏移量的过程中,Driver程序挂掉(这部分数据是未丢失),重新启动后,会先去执行备份数据任务
同时由于偏移量未提交,又重复读取并计算了一次相同数据。
- 代码
package com.shsxt.spark.stream;
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.JavaPairReceiverInputDStream;
import org.apache.spark.streaming.api.java.JavaStreamingContext;
import org.apache.spark.streaming.kafka.KafkaUtils;
import scala.Tuple2;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
public class SparkStreamingOnKafkaReceiver {
public static void main(String args[]){
SparkConf conf = new SparkConf();
conf.setMaster("local[2]").setAppName("receiver");
//开启WAL预写日志
// conf.set("spark.streaming.receiver.writeAheadLog.enable","true");
JavaStreamingContext jsc = new JavaStreamingContext(conf, Durations.seconds(5));
jsc.checkpoint("./receiverData");
Map<String,Integer> topicConsumerConcurrency = new HashMap<String,Integer>();
/**
* 设置读取的topic和接收数据的线程数
*/
topicConsumerConcurrency.put("sk1",1);
topicConsumerConcurrency.put("sk2",1);
JavaPairReceiverInputDStream<String,String> topicDStream = KafkaUtils.createStream(
jsc,
"node01:2181,node02:2181,node03:2181",
"receiverTopic",
topicConsumerConcurrency);
// (null,wcb)
topicDStream.print();
System.out.println("==============================");
JavaDStream<String> flatMapRDD = topicDStream.flatMap(new FlatMapFunction<Tuple2<String, String>, String>() {
@Override
public Iterable<String> call(Tuple2<String, String> tuple2) throws Exception {
return Arrays.asList(tuple2._2);
}
});
JavaPairDStream<String, Integer> mapPairRDD = flatMapRDD.mapToPair(new PairFunction<String, String, Integer>() {
@Override
public Tuple2<String, Integer> call(String s) throws Exception {
return new Tuple2<>(s, 1);
}
});
JavaPairDStream<String, Integer> reduceByKeyRDD = mapPairRDD.reduceByKey(new Function2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer v1, Integer v2) throws Exception {
return v1 + v2;
}
});
reduceByKeyRDD.print();
jsc.start();
jsc.awaitTermination();
jsc.stop();
}
}
kafkaProducer.java
package com.shsxt.spark.test;
import kafka.javaapi.producer.Producer;
import kafka.producer.KeyedMessage;
import kafka.producer.ProducerConfig;
import kafka.serializer.StringEncoder;
import java.util.Properties;
/**
*
*/
public class KafkaProducer extends Thread {
private String topic; //发送给Kafka的数据
private Producer<Integer,String> producerForKafka;
public KafkaProducer(String topic){
this.topic = topic;
Properties props = new Properties();
props.put("metadata.broker.list","node01:9092,node02:9092,node03:9092");
props.put("serializer.class", StringEncoder.class.getName());
props.put("acks",1);
producerForKafka = new Producer<Integer, String>(new ProducerConfig(props));
}
@Override
public void run() {
int count = 0;
while (true){
// count++;
String value = "wcb";
KeyedMessage<Integer, String> message = new KeyedMessage<>(topic, value);
producerForKafka.send(message);
System.out.println(value+" ----------------------->");
//hash partition 当有key时,默认通过key取hash后,对partition_number取余数
//producerForKafka.send(new KeyedMessage<>(topic,5,value));
// if (0==count%2){
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
}
}
public static void main(String args[]){
new KafkaProducer("sk1").start();
new KafkaProducer("sk2").start();
}
}
6.2 direct模式
-
direct模式理解
SparkStreaming+kafka的direct 模式就是将Kafka看做存储数据的一方,然后主动的去拉取数据。消费者的偏移量不采用zookeeper管理,而是SparkStreaming内部对消息者偏移量自动来维护,默认消费的偏移量是存储在内存中,当然如果设置了checkpoint目录,那么消费者偏移量也会保存在checkpoint目录下,消费者的偏移量也可以使用zookeeper来管理。
-
direct模式并行度设置
Direct模式的并行度是由读取的kafka中topic的partition数决定的。
- direct模式是 exactly-once
理解: 有且只有一次(完美)的消费Kafka中的消息
- 代码
package com.shsxt.spark.stream;
import kafka.serializer.StringDecoder;
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.JavaPairInputDStream;
import org.apache.spark.streaming.api.java.JavaStreamingContext;
import org.apache.spark.streaming.kafka.KafkaUtils;
import scala.Tuple2;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
/**
* 并行度:
* 1.与kafka topic的 partition个输有关 与之对应的是 DStream中RDD的分区数 也就决定了Task的并行度
* 2.从kafka中读来的数据封装一个DStream里面,可以对这个DStream重分区 repartitions()
*/
public class SparkStreamingOnKafkaDirect {
public static void main(String args[]){
SparkConf conf = new SparkConf();
conf.setMaster("local[2]").setAppName("direct");
// conf.set("spark.streaming.backpressure.enabled","true");
// conf.set("spark.streaming.kafka.maxRatePerPartition","100");
/**
* 优雅的退出会先把当前未执行完的Job执行完毕,然后会关闭接收数据源的通道,然后退出程序。
*/
//设置优雅的退出 sparkStreaming程序 除了设置参数以外 还得敲 kill -15 driverId
//conf.set("spark.streaming.stopGracefullyOnShutdown","true");
JavaStreamingContext jsc = new JavaStreamingContext(conf, Durations.seconds(10));
//可以不设置checkpoint 不设置不保存offset,offset默认在内存中有一份,如果设置了那么在checkpoint目录下也有一份,一般设置在hdfs路径
jsc.checkpoint("./kafkaDirect");
Map<String,String> kafkaParameters = new HashMap<String,String>();
// kafkaParameters.put("auto.offset.reset","smallset");
kafkaParameters.put("metadata.broker.list","node01:9092,node02:9092,node03:9092");
HashSet<String> topicSet = new HashSet<>();
topicSet.add("sk1");
topicSet.add("sk2");
JavaPairInputDStream<String, String> directDStrean = KafkaUtils.createDirectStream(jsc, String.class, String.class,
StringDecoder.class, StringDecoder.class, kafkaParameters, topicSet);
JavaDStream<String> flatMapDStream = directDStrean.flatMap(new FlatMapFunction<Tuple2<String, String>, String>() {
@Override
public Iterable<String> call(Tuple2<String, String> tuple2) throws Exception {
return Arrays.asList(tuple2._2);
}
});
JavaPairDStream<String, Integer> pairRDD = flatMapDStream.mapToPair(new PairFunction<String, String, Integer>() {
@Override
public Tuple2<String, Integer> call(String s) throws Exception {
return new Tuple2<>(s, 1);
}
});
JavaPairDStream<String, Integer> reduce = pairRDD.reduceByKey(new Function2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer v1, Integer v2) throws Exception {
return v1 + v2;
}
});
reduce.print();
jsc.start();
jsc.awaitTermination();
jsc.stop();
}
}
6.3 相关配置
- 预写日志(针对 receiver模式)
spark.streaming.receiver,writeAheadLog.enable ##默认false没有开启
- blockInterval(针对receiver模式)
spark.streaming.blockInterval ##默认200ms 调节sparkStreaming任务并行度
- 反压机制
spark.streaming.backpressure.enabled ##默认false
- 接收数据的最大速率(每个接收器 )
spark.streaming.receiver.maxRate ##没有默认值
- 从kafka每个分区读取数据的最大速率
spark.streaming.kafka.maxRatePerPartition ##没有默认值
- 优雅关闭SparkStreaming程序
spark.streaming.stopGracefullyOnShutdown ##默认值 false ; 如果为true,在JVM关闭时优先关闭而不是立即关闭
想要优雅关闭,除了代码中配置该参数,还需在客户端输入命令:
kill -15 DriverId
SparkConf conf = new SparkConf();
conf.setMaster("local[2]").setAppName("direct");
/**
* 优雅的退出会先把当前未执行完的Job执行完毕,然后会关闭接收数据源的通道,然后退出程序。
*/
//设置优雅的退出 sparkStreaming程序 除了设置参数以外 还得敲 kill -15 driverId
conf.set("spark.streaming.stopGracefullyOnShutdown","true");