1)需求:循环创建几个RDD,将RDD放入队列。通过SparkStream创建DStream,计算WordCount。
2)代码实现
import org.apache.spark.SparkConf
import org.apache.spark.rdd.RDD
import org.apache.spark.streaming.dstream.{DStream, InputDStream}
import org.apache.spark.streaming.{Seconds, StreamingContext}
import scala.collection.mutable
/**
* @author zjfstart
* @create 2020-05-21-14:55
*
* 通过RDD队列方式创建DStream
* 需求:循环创建几个RDD,将RDD放入队列。通过SparkStream创建Dstream,计算WordCount
*/
object Test02_DStreamingCreate_RDDQueue {
def main(args: Array[String]): Unit = {
// 创建配置文件的对象
val conf: SparkConf = new SparkConf().setAppName("Test02_DStreamingCreate_RDDQueue").setMaster("local[*]")
// 创建Spark Streaming上下文环境对象
// 采集周期为3秒钟
val ssc = new StreamingContext(conf, Seconds(3))
// 创建一个队列,里面放的是RDD
val rddQueue: mutable.Queue[RDD[Int]] = new mutable.Queue[RDD[Int]]
// 从队列中采集数据,获取DStream
// oneAtATime默认情况下是true,表示在一个采集周期内采集一个RDD
val queueDS: InputDStream[Int] = ssc.queueStream(rddQueue,true)
// 处理采集到的数据
val resDS: DStream[(Int, Int)] = queueDS.map((_, 1)).reduceByKey(_ + _)
// 打印输出结果
resDS.print()
// 启动采集器
ssc.start()
// 循环创建RDD,并将创建的RDD放入到队列中
for (i <- 1 to 5) {
rddQueue.enqueue(ssc.sparkContext.makeRDD(6 to 10)) // 把创建好的RDD放入队列中
Thread.sleep(2000) // 睡两秒钟
}
// 等待采集之后终止程序
ssc.awaitTermination()
}
}
3)输出结果
-------------------------------------------
Time: 1590049257000 ms
-------------------------------------------
(6,1)
(7,1)
(8,1)
(9,1)
(10,1)
-------------------------------------------
Time: 1590049260000 ms
-------------------------------------------
(6,1)
(7,1)
(8,1)
(9,1)
(10,1)
-------------------------------------------
Time: 1590049263000 ms
-------------------------------------------
(6,1)
(7,1)
(8,1)
(9,1)
(10,1)
-------------------------------------------
Time: 1590049266000 ms
-------------------------------------------
(6,1)
(7,1)
(8,1)
(9,1)
(10,1)
-------------------------------------------
Time: 1590049269000 ms
-------------------------------------------
(6,1)
(7,1)
(8,1)
(9,1)
(10,1)
-------------------------------------------
Time: 1590049272000 ms
-------------------------------------------
-------------------------------------------
1)需求:自定义数据源,实现监控某个端口号,获取该端口号内容。
2)代码实现
import java.io.{BufferedReader, InputStreamReader}
import java.net.{ConnectException, Socket}
import java.nio.charset.StandardCharsets
import org.apache.spark.SparkConf
import org.apache.spark.storage.StorageLevel
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.receiver.Receiver
import org.apache.spark.streaming.{Seconds, StreamingContext}
/**
* @author zjfstart
* @create 2020-05-21-14:56
*
* 通过自定义数据源方式创建DStream
* 模拟从指定的网络端口获取数据
*/
object Test03_CustomReceiver {
def main(args: Array[String]): Unit = {
// 创建配置文件的对象
val conf: SparkConf = new SparkConf().setAppName("Test03_CustomReceiver").setMaster("local[*]")
// 创建Spark Streaming上下文的环境对象
val ssc = new StreamingContext(conf, Seconds(3))
// 通过自定义数据源的方式来创建DStream
val myDS: ReceiverInputDStream[String] = ssc.receiverStream(new MyRecevicer("hadoop102", 9999))
// 对获取到的数据进行扁平化处理
val flatMapDS: DStream[String] = myDS.flatMap(_.split(" "))
// 对数据进行结构上的转换
val mapDS: DStream[(String, Int)] = flatMapDS.map((_, 1))
// 对上述的数据进行聚合处理
val reduceDS: DStream[(String, Int)] = mapDS.reduceByKey(_ + _)
// 输出结果 注意:调用的是 DS的 print 函数
reduceDS.print()
// 启动采集器
ssc.start()
// 默认情况下,上下文对象不能关闭
// scc.stop()
// 等待采集器结束,终止上下文环境对象
ssc.awaitTermination()
}
}
// 自定义数据源 // 模仿SocketReceiver
// Receiver[T] 泛型表示的是读取数据的类型
class MyRecevicer(host: String, port: Int) extends Receiver[String](StorageLevel.MEMORY_ONLY) {
// 定义一个socket
private var socket: Socket = _
// 真正的处理接收数据的逻辑
// 自定义数据源的入口
def receive() {
try {
// 创建socket连接
socket = new Socket(host, port)
// 根据连接对象获取输入流
val reader = new BufferedReader(new InputStreamReader(socket.getInputStream, StandardCharsets.UTF_8))
// 定义一个变量,用于接收读取到的一行数据
var input: String = null
while((input = reader.readLine()) != null) {
store(input) // 拿到数据给框架进行保存
}
} catch {
case e: ConnectException =>
restart(s"Error connecting to $host:$port", e)
return
} finally {
onStop()
}
}
// 开启线程(固定的写法)
override def onStart(): Unit = {
new Thread("Socket Receiver") {
setDaemon(true)
override def run() { receive() }
}.start()
}
// 关闭socket(固定的写法)
override def onStop(): Unit = {
synchronized {
if (socket != null) {
socket.close()
socket = null
}
}
}
}
<dependency>
<groupId>org.apache.sparkgroupId>
<artifactId>spark-streaming-kafka-0-8_2.11artifactId>
<version>2.1.1version>
dependency>
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.kafka.KafkaUtils
import org.apache.spark.streaming.{Seconds, StreamingContext}
/**
* @author zjfstart
* @create 2020-05-21-14:57
* * 通过ReceiverAPI连接Kafka数据源,获取数据
*/
object Test04_ReceiverAPI {
def main(args: Array[String]): Unit = {
// 创建配置文件的对象
val conf: SparkConf = new SparkConf().setAppName("Test04_ReceiverAPI").setMaster("local[*]")
// 创建SparkStreaming上下文的环境对象
val ssc = new StreamingContext(conf, Seconds(3))
// 连接Kafka,创建DStream
val kafkaDStream: ReceiverInputDStream[(String, String)] = KafkaUtils.createStream(
ssc,
// 这里面需要注意的是:要和之前配置Kafka的server-properties的配置文件中配置的zookeeper连接地址一致
"hadoop102:2181,hadoop103:2181,hadoop104:2181/kafka", // zookeeper集群地址
"test0105", // 消费者组
Map("bigdata-0105" -> 2) // 主题 -> 分区数
)
// 获取Kafka中的消息,我们只需要v的部分:消息体
val lineDS: DStream[String] = kafkaDStream.map(_._2)
// 扁平化
val flatMapDS: DStream[String] = lineDS.flatMap(_.split(" "))
// 结构转换 进行计数
val mapDS: DStream[(String, Int)] = flatMapDS.map((_, 1))
// 聚合
val reduceDS: DStream[(String, Int)] = mapDS.reduceByKey(_ + _)
// 打印输出
reduceDS.print()
// 开启任务
ssc.start()
// 等待
ssc.awaitTermination()
}
}
[atguigu@hadoop102 kafka-2.4.1]$ bin/kafka-topics.sh --list --bootstrap-server hadoop102:9092
[atguigu@hadoop102 kafka-2.4.1]$ bin/kafka-topics.sh --create --bootstrap-server hadoop102:9092 --topic bigdata-0105 --partitions 2 --replication-factor 2
[atguigu@hadoop102 kafka-2.4.1]$ bin/kafka-console-producer.sh --broker-list hadoop102:9092 --topic bigdata-0105
1)需求:通过SparkStreaming从Kafka读取数据,并将读取过来的数据做简单计算,最终打印到控制台。
2)导入依赖
<dependency>
<groupId>org.apache.sparkgroupId>
<artifactId>spark-streaming-kafka-0-8_2.11artifactId>
<version>2.1.1version>
dependency>
3)代码编写(自动维护offset1)
import kafka.serializer.StringDecoder
import org.apache.kafka.clients.consumer.ConsumerConfig
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.{DStream, InputDStream}
import org.apache.spark.streaming.kafka.KafkaUtils
import org.apache.spark.streaming.{Seconds, StreamingContext}
/**
* @author zjfstart
* @create 2020-05-21-15:32
*
* 通过DirectAPI连接Kafka数据源,获取数据
* 自动的维护偏移量,偏移量维护在checkpiont中
* 目前我们这个版本,只是指定的检查点,只会将offset放到检查点中,但是并没有从检查点中取,会存在消息丢失
*/
object Test05_DirectAPI_Auto01 {
def main(args: Array[String]): Unit = {
// 创建配置文件的对象
val conf: SparkConf = new SparkConf().setAppName("Test05_DirectAPI_Auto01").setMaster("local[*]")
// 创建Spark Streaming的上下文环境对象
val ssc: StreamingContext = new StreamingContext(conf, Seconds(3))
// 设置检查点目录
ssc.checkpoint("D:\\MyWork\\IdeaProjects\\spark0105_exer\\src\\main\\checkPoint")
// 准备Kafka参数
val kafkaParams: Map[String, String] = Map[String, String](
ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG -> "hadoop102:9092,hadoop103:9092,hadoop104:9092",
ConsumerConfig.GROUP_ID_CONFIG -> "bigdata"
)
// 创建DStream
val kafkaDStream: InputDStream[(String, String)] = KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder](
ssc,
kafkaParams,
Set("bigdata-0105")
)
// 获取Kafka中的消息,我们只需要v的部分:消息体
val lineDS: DStream[String] = kafkaDStream.map(_._2)
// 扁平化
val flatMapDS: DStream[String] = lineDS.flatMap(_.split(" "))
// 结构转换 进行计数
val mapDS: DStream[(String, Int)] = flatMapDS.map((_, 1))
// 聚合
val reduceDS: DStream[(String, Int)] = mapDS.reduceByKey(_ + _)
// 打印输出
reduceDS.print()
// 开启任务
ssc.start()
// 等待
ssc.awaitTermination()
}
}
4)代码编写(自动维护offset2)
import kafka.serializer.StringDecoder
import org.apache.kafka.clients.consumer.ConsumerConfig
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.{DStream, InputDStream}
import org.apache.spark.streaming.kafka.KafkaUtils
import org.apache.spark.streaming.{Seconds, StreamingContext}
/**
* @author zjfstart
* @create 2020-05-21-16:07
*
* 通过DirectAPI连接Kafka数据源,获取数据
* 自动的维护偏移量,偏移量维护在checkpiont中
* 优点:修改StreamingContext对象的获取方式,先从检查点获取,如果检查点没有,通过函数创建。会保证数据不丢失
* 缺点:
* 1.小文件过多
* 2.在checkpoint中,只记录最后offset的时间戳,再次启动程序的时候,会从这个时间到当前时间,把所有周期都执行一次
*/
object Test05_DirectAPI_Auto02 {
def main(args: Array[String]): Unit = {
// 创建Streaming上下文的对象
val ssc: StreamingContext = StreamingContext.getActiveOrCreate("D:\\MyWork\\IdeaProjects\\spark0105_exer\\src\\main\\checkPoint", () => getStreamingContext)
ssc.start()
ssc.awaitTermination()
}
def getStreamingContext(): StreamingContext = {
// 创建配置文件的对象
val conf: SparkConf = new SparkConf().setAppName("Test05_DirectAPI_Auto02").setMaster("local[*]")
// 创建Spark Streaming上下文环境对象
val ssc = new StreamingContext(conf, Seconds(3))
// 设置检查点目录
ssc.checkpoint("D:\\MyWork\\IdeaProjects\\spark0105_exer\\src\\main\\checkPoint")
// 准备Kafka参数
val kafkaParams: Map[String, String] = Map[String, String](
ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG -> "hadoop102:9092,hadoop103:9092,hadoop104:9092",
ConsumerConfig.GROUP_ID_CONFIG -> "bigdata"
)
// 创建DStream
val kafkaDStream: InputDStream[(String, String)] = KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder](
ssc,
kafkaParams,
Set("bigdata-0105")
)
// 获取Kafka中的消息,我们只需要v的部分
val lineDS: DStream[String] = kafkaDStream.map(_._2)
//扁平化
val flatMapDS: DStream[String] = lineDS.flatMap(_.split(" "))
//结构转换 进行计数
val mapDS: DStream[(String, Int)] = flatMapDS.map((_,1))
//聚合
val reduceDS: DStream[(String, Int)] = mapDS.reduceByKey(_+_)
//打印输出
reduceDS.print
ssc
}
}
5)代码编写(手动维护offset)
import kafka.common.TopicAndPartition
import kafka.message.MessageAndMetadata
import kafka.serializer.StringDecoder
import org.apache.kafka.clients.consumer.ConsumerConfig
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.InputDStream
import org.apache.spark.streaming.kafka.{HasOffsetRanges, KafkaUtils, OffsetRange}
import org.apache.spark.streaming.{Seconds, StreamingContext}
/**
* @author zjfstart
* @create 2020-05-21-16:27
*
* 通过DirectAPI连接Kafka数据源,获取数据
* 手动维护offset
*/
object Test07_DirectAPI_Handler {
def main(args: Array[String]): Unit = {
// 创建配置文件的对象
val conf: SparkConf = new SparkConf().setAppName("Test07_DirectAPI_Handler").setMaster("local[*]")
// 创建SparkStreaming上下文环境对象
val ssc: StreamingContext = new StreamingContext(conf, Seconds(3))
// 准备Kafka的参数
val kafkaParams: Map[String, String] = Map[String, String](
ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG -> "hadoop102:9092,hadoop103:9092,hadoop104:9092",
ConsumerConfig.GROUP_ID_CONFIG -> "bigdata"
)
// 获取上一次消费的位置(偏移量)
// 实际项目中,为了保证数据精准一致性,我们对数据进行消费处理之后,将偏移量保存在有事务的存储中,如MySQL
var fromOffsets:Map[TopicAndPartition,Long] = Map[TopicAndPartition,Long](
TopicAndPartition("bigdata-0105",0)->10L,
TopicAndPartition("bigdata-0105",1)->10L
)
// 从指定的offset读取数据进行消费
val kafkaDStream: InputDStream[String] = KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder, String](
ssc,
kafkaParams,
fromOffsets,
(m: MessageAndMetadata[String, String]) => m.message()
)
//消费完毕之后,对偏移量offset进行更新
var offsetRanges: Array[OffsetRange] = Array.empty[OffsetRange] // 定义一个集合,用来存放偏移量的范围
kafkaDStream.transform{
rdd=>{
offsetRanges = rdd.asInstanceOf[HasOffsetRanges].offsetRanges
rdd
}
}.foreachRDD{
rdd=>{
for (o <- offsetRanges) {
println(s"${o.topic} ${o.partition} ${o.fromOffset} ${o.untilOffset}")
}
}
}
ssc.start()
ssc.awaitTermination()
}
}
<dependency>
<groupId>org.apache.sparkgroupId>
<artifactId>spark-core_2.11artifactId>
<version>2.1.1version>
dependency>
<dependency>
<groupId>org.apache.sparkgroupId>
<artifactId>spark-streaming_2.11artifactId>
<version>2.1.1version>
dependency>
<dependency>
<groupId>org.apache.sparkgroupId>
<artifactId>spark-streaming-kafka-0-10_2.11artifactId>
<version>2.1.1version>
dependency>
import org.apache.kafka.clients.consumer.{ConsumerConfig, ConsumerRecord}
import org.apache.kafka.common.serialization.StringDeserializer
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.InputDStream
import org.apache.spark.streaming.kafka010.{ConsumerStrategies, KafkaUtils, LocationStrategies}
import org.apache.spark.streaming.{Seconds, StreamingContext}
/**
* @author zjfstart
* @create 2020-05-22-9:26
* * 通过DirectAPI 0-10 消费Kafka数据
* 消费的offset保存在__consumer_offsets主题中
*/
object SparkStreaming01_DirectAPI_0_10 {
def main(args: Array[String]): Unit = {
// 创建配置文件的对象
val conf: SparkConf = new SparkConf().setAppName("SparkStreaming01_DirectAPI_0_10").setMaster("local[*]")
// 创建SparkStreaming上下文的环境对象
val ssc: StreamingContext = new StreamingContext(conf, Seconds(3))
// 声明Kafka相关的连接参数
val kafkaParams: Map[String, Object] = Map[String, Object](
ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG -> "hadoop102:9092, hadoop103:9092, hadoop104:9092",
ConsumerConfig.GROUP_ID_CONFIG -> "BigData",
// 反序列化方式:两种写法
ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG -> "org.apache.kafka.common.serialization.StringDeserializer",
ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG -> classOf[StringDeserializer] // 获取类的全限定名
)
// 通过读取Kafka数据,创建DStream
val kafkaDStream0_10: InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream[String, String](
ssc,
// 位置策略,指定计算的Executor
LocationStrategies.PreferConsistent,
// 消费策略 参数:消费的主题,Kafka连接参数
ConsumerStrategies.Subscribe[String, String](Set("bigdata-0105"), kafkaParams)
)
// WordCount计算
kafkaDStream0_10.map(_.value())
.flatMap(_.split(" "))
.map((_, 1))
.reduceByKey(_+_)
.print()
ssc.start()
ssc.awaitTermination()
}
}
[atguigu@hadoop102 kafka-2.4.1]$ bin/kafka-console-producer.sh --broker-list hadoop102:9092 --topic bigdata-0105
>aa aa bb bb cc cc
>zhang zhang zhang
启动编写好的程序,会消费到Kafka中生产出的消息;
其中,停止掉程序,然后继续在Kafka上生产消息,再次启动程序,仍然能消费到历史消息,这是因为0-10版本的offset(偏移量)是存放在Kafka内置的一个主题中,主题的名字叫做__consumer_offset。
0-8 ReceiverAPI
1)专门的Executor读取数据,速度不统一;
2)跨机器传输数据,WAL;
3)Executor读取数据通过多个线程的方式,想要增加并行度,则需要多个流union;
4)offset存储在Zookeeper中。
0-8 DirectAPI
1)Executor读取数据并计算;
2)增加Executor个数来增加消费的并行度
3)offset存储:
(1)CheckPoint(getActiveOrCreate方式创建StreamingContext);
(2)手动维护(有事务的存储系统);
(3)获取offset必须在第一个调用的算子中:offsetRanges = rdd.asInstanceOf[HashOffsetRanges].offsetRanges。
0-10 DirectAPI
1)Executor读取数据并计算
2)增加Executor个数来增加消费的并行度
3) offset存储
(1)__consumer_offsets系统主题中
(2)手动维护(有事务的存储系统)