Spark实时流分为两种:
1)Spark streaming:类似Spark
2)Spark Struct Streamming:类似Spark SQL,处理结构化数据
SparkStreaming从Kafka中读取数据有两种方式:
1)Receiver:Executor中的Kafka Receiver不断从kafka中读取数据,为了保证数据处理失败能够重试,同时将数据写入WAL。写入成功后,Receiver会向Kafka中的zookeeper发送消息(推),更新offset。
因为zookeeper中的offset与Kafka中可能不一致,所以可能导致消息重复消费(通过调优可以解决)
Receiver方式中,kafka和Spark中的partition是没关系的

2)DirectStream:使用DirectStream,每个批次的Spark任务提交前会从kafka中读取offset,决定该Spark任务读取的offset范围,然后直接由Spark任务从Kafka中读取(拉)。
使用DirectStream,需要设定checkpoint文件路径,用于保存每个任务处理的offset范围,方便任务异常时重试。同时,保证了数据肯定被处理,且处理一次。
因为省略的WAL存储区域,并且使用Zero Copy的方式,Spark能够更快的读取数据,并且节省空间。
Kafka中的partition与Spark任务中的partition一一对应,如果要提高Spark并行度,需要repartition

参考链接:[https://juejin.cn/post/6844903950353694733](https://juejin.cn/post/6844903950353694733)
实例代码:
<!-- sparkstreaming + kafka begin -->
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming_2.11</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<version>2.2.2.RELEASE</version>
</dependency><!-- 创建Kafka 生产者、消费者等 -->
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka_2.11</artifactId>
<version>1.1.0</version>
</dependency><!-- 使用ZkUtils创建、删除topic 0.8.0版本有坑!-->
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming-kafka-0-10_2.11</artifactId>
<version>2.4.0</version> <!-- 2.2.0有坑,会在kafka启动时报java.lang.AbstractMethodError -->
</dependency>
<!-- sparkstreaming + kafka end -->
/**
* 消费者
* <p>
* 消费者使用consumer API预先将Message fetch到buffer中,可通过配置spark.streaming.kafka.consumer.cache.enabled关闭,不过
* 不建议,需要解决bug described in SPARK-19185
*
* @throws InterruptedException
*/
public void SparkStreamingKafkaTest() throws InterruptedException {
// 一个SpringBoot项目中,只允许有一个SparkConf
SparkConf conf = new SparkConf()
// .setMaster("local[*]")
.setAppName("crawler kafka consumer");
JavaSparkContext javaSparkContext = new JavaSparkContext(conf);
// 一个SpringBoot项目中,可以有多个JavaStreamingContext,不过需要关闭上一个后再开始用下一个
JavaStreamingContext jssc = new JavaStreamingContext(javaSparkContext, Durations.seconds(5));
Map<String, Object> kafkaParams = new HashMap<>();
kafkaParams.put("bootstrap.servers", servers);
kafkaParams.put("key.deserializer", StringDeserializer.class);
kafkaParams.put("value.deserializer", StringDeserializer.class);
kafkaParams.put("group.id", "use_a_separate_group_id_for_each_stream");
// kafkaParams.put("fetch.min.bytes", 10);
// kafkaParams.put("heartbeat.interval.ms", 10);
kafkaParams.put("auto.offset.reset", "latest");
kafkaParams.put("enable.auto.commit", false);
// TODO 指定精确的topic名称列表 // Collection<String> topics = Arrays.asList("topicA", "topicB", "hello");
// JavaInputDStream<ConsumerRecord<String, String>> messages = KafkaUtils.createDirectStream(
// jssc,
// LocationStrategies.PreferConsistent(),
// ConsumerStrategies.Subscribe(topics, kafkaParams));
// TODO 指定正则表达式,匹配一类topic JavaInputDStream<ConsumerRecord<String, String>> messages = KafkaUtils.createDirectStream(
jssc,
LocationStrategies.PreferConsistent(), // 使用该LocationStrategy,使得partition均为的分配到executor
ConsumerStrategies.SubscribePattern(Pattern.compile("hell.*"), kafkaParams));
JavaDStream<String> lines = messages.map(ConsumerRecord::value);
System.out.println(lines);
jssc.start(); // Start the computation
jssc.awaitTermination(); // Wait for the computation to terminate
}
**问题一:requirement failed: No output operations registered, so nothing to execute**
问题分析:因为没有指定output的动作,如存储Cassandra、存储文件、打印等操作
问题解决:最简单的,调用JavaDStream的print()方法打印;或者存储数据库、文件
**问题二:不报错,只是程序中不断打印Attempt to heartbeat failed since group is rebalancing**
问题分析:程序中,定义了两个KafkaUtils.createDirectStream()
问题解决:删除其中一个,看来SparkStreaming不支持同时定义多个DirectStream
**问题三:如何提高并行度**
reparition,实践发现定义两个DirectStream不可以
**问题四:SparkStream向Kafka写数据(未实践)**
参考连接:[https://allegro.tech/2015/08/spark-kafka-integration.html](https://allegro.tech/2015/08/spark-kafka-integration.html)
class KafkaSink(createProducer: () => KafkaProducer[String, String]) extends Serializable {
lazy val producer = createProducer()
def send(topic: String, value: String): Unit = producer.send(new ProducerRecord(topic, value))
}
object KafkaSink {
def apply(config: Map[String, Object]): KafkaSink = {
val f = () => {
val producer = new KafkaProducer[String, String](config)
// 增加shutdown后的回调函数
sys.addShutdownHook {
producer.close()
}
producer
}
new KafkaSink(f)
}
}
val kafkaSink = sparkContext.broadcast(KafkaSink(conf))
dstream.foreachRDD { rdd =>
rdd.foreach { message =>
kafkaSink.value.send(message)
}
}
**问题五:Sparkstreaming kafka 跨时段任务**
索引:sparkstreaming跨时段处理、sparkstreaming reduce(其实不是reduce操作,只是第一印象是reduce,所以为了方便搜索,这里加一个索引)
!!!真香系列之参考链接:[https://cloud.tencent.com/developer/article/1198450](https://cloud.tencent.com/developer/article/1198450)!!!
目的:
SparkStreaming一般是取某一时段内数据,处理后进行存储。但在实际开发中,可能出现需要跨多时段进行统计的需求,SparkStreaming中提供了这种方式
实现方式:
SparkStreaming中,提供了updateStateByKey和mapWithState两种方式,其中updateStateByKey的方式会随着统计的数据量的增加越来越慢,所以建议使用mapWithState的方式
val initialRDD = ssc.sparkContext.parallelize(List[(String, Int)]())
// 自定义mappingFunction,累加单词出现的次数并更新状态
// word表示统计的key,count表示新增数量,State存储当前word的数目状态
val mappingFunc = (word: String, count: Option[Int], state: State[Int]) => {
val sum = count.getOrElse(0) + state.getOption.getOrElse(0)
val output = (word, sum)
state.update(sum)
output
}
//调用mapWithState进行管理流数据的状态
kafkaStream.map(r => (r._2,1)).mapWithState(StateSpec.function(mappingFunc).initialState(initialRDD)).print()
**问题:为什么mapWithState比updateStateWithKey性能要高?**
问题解决:因为updateStateWithKey在每批次处理时,都会与当前统计的集合做co-group操作(个人理解是join操作),所以会随着系统中统计的集合的增大而变慢
而mapWithState则只会将当前统计集合中与新增集合中相关的数据进行处理