spark-05:sparkStreaming

1.工作原理

spark处理的单元RDD

一个是流式数据,动态,一个是RDD,相当静态


sparkSteaming把流式数据,按规定的时间间隔就分成很多小块,每个小块数据就定下来了,每个小块就有开始的地方,和结束的地方。相当就变成一个“静态”的数据,这样的好处就可以继续应用spark的RDD相关的算子,并行计算。

2.容错

流式数据和传统RDD有不同特性

1)  RDD容错,血缘lineage

2)  流式数据RDD非常多,海量。

不同,在加工数据块时,先把它复制一个,放在内存中。也就是说形成一个副本。这个没有其他作用,它就是为防止小块在计算中出现错误。如果出错,把副本拿过来,重新部署task。

3.处理方式

Streaming中把流式数据源数据,按△t固定时间间隔把数据进行切割。切割成一个小块一个小块,对每个小块把它就封装成DStream对象,拿去进行业务处理(数据加工)。

多个小块业务处理时异步还是同步的?是串行还是并发?

只能串行!!!

上一个小块如果业务没有处理完成,下一个小块就等待。

前提:业务的处理速度(毫秒)远远大于流动切割速度(0.5~2s)

4.storm和sparkStreaming比较

storm亚秒级别 0.0.1,streaming(1s)

storm真实时,streaming假实时

衡量一个产品

1)  性能,storm快于streaming

2)  吞吐率,单位时间内处理数据量,storm基于事件event,来一条数据就处理一条数据;但是streaming模型没有这样处理,堆积,transformations和actions,遇到transformations命令时,懒执行,当遇到actions命令时,反向推导,正向执行。批量来执行。一堆数据一次处理。storm吞吐量远远小于streaming

3)  落地,把流式处理的数据写磁盘。

storm在写磁盘时也是批量,把加工数据先放入buffer,等buffer快满时,触发写磁盘。

streaming一样。streaming落地处理比较顺畅。

目前storm使用要比sparkStreaming广泛(2018.5.5)


数据源,数据落地


5.sparkStreaming输入

监控网络(监听socket)

netcat模拟网络传输

[root@hadoop01 software]# nc -lk 9999
adf
开启sparkStreaming
import org.apache.spark.storage.StorageLevel
import org.apache.spark.streaming._

val ssc = new StreamingContext(sc, Seconds(2))
ssc.checkpoint("/root/checkpoint")	//设置检查点

// 通过Socket获取数据,该处需要提供Socket的主机名和端口号,数据保存在内存和硬盘中。9999端口为NC开启的端口。
val lines = ssc.socketTextStream("192.168.1.106",9999, StorageLevel.MEMORY_AND_DISK_SER)
val words = lines.flatMap(_.split("\\s+"))
val wordCounts = words.map((_,1)).reduceByKey(_+_).print

ssc.start()
ssc.awaitTermination()	//计算完毕退出
结果
-------------------------------------------
Time: 1525532304000 ms
-------------------------------------------
(adf,1)

监控文件夹

当目录下文件有变动,就它当做新流入的数据进行处理。

ssc.textFileStream从文本文件获取流,检查一个文件夹,看是否有新的文件进入。

scala> import org.apache.spark.streaming._
import org.apache.spark.streaming._

// 创建Streaming的上下文,包括Spark的配置和时间间隔,这里时间为间隔1秒
scala> val ssc = new StreamingContext(sc, Seconds(1))

// 指定监控的目录,在这里为/root/ss(如果关联了hdfs,此处为hdfs的路径,如果是本地文件系统需要写为:file:///root/ss)
scala> val lines = ssc.textFileStream("/root/ss")
// 对指定文件夹变化的数据进行单词统计并且打印
scala> val words = lines.flatMap(_.split("\\s+"))
scala> val wordCounts = words.map((_,1)).reduceByKey(_+_)
scala> wordCounts.print()

scala> ssc.start()					// 启动Streaming
scala> ssc.awaitTermination()		// 计算完毕退出
-------------------------------------------
Time: 1525537330000 ms
-------------------------------------------
(scala,1)
(spark,1)
(hive,1)
(phoenix,1)
(hadoop,1)
(flume,1)
(zebra,1)
(storm,1)
(kafka,1)
(hbase,1)

1)  新文件,在时间间隔中开始处理业务,按算法加工流式数据。直接处理完成展示,过了这个时间间隔,之前的结果数据就不在了(因为流式数据量,存它没有意义,所有处理完成就丢掉)

2)  覆盖文件,不会进行处理。因为覆盖的文件内容一致,所有不理由。

秒传:qq、360、百度云都有这个功能

原理:hash、md5、CRC(eclipse中有该码,下载好后验证一下,自己下载的是否是源文件,防止下载的资源是被植入木马的程序)

3)  修改文件,不会进来处理

4)  删除文件,拷入刚才文件

监控文件夹只对新文件进行处理,修改文件,覆盖文件都不处理。

默认这种方式,它的数据只在△t内进行累计,超过这个△t时间后,数据就不会在继续累加。

历史结果累计updateStateByKey

上面的方法称为无状态,而要记录中间结果就称为有状态的。它通过检查点来实现。

ssc.chekcpoint("目录路径")

设置了检查点,历史的RDD结果就会存入到指定的目录,如果未设置检查点,旧的历史的结果就被抛弃掉了。

updateStateByKey接收一个函数作为参数

Seq代表当前RDD的值的数组,Option代表上次累计的结果

格式:(seq,Option) => {Some(本次的计算结果)}

Option代表可有可无的值,没有值就是None有值就是Some,getOrElse("默认值")

重构代码:

import org.apache.spark.streaming._
val ssc = new StreamingContext(sc, Seconds(1))
ssc.checkpoint("/root/checkpoint")

val lines = ssc.textFileStream("file:///root/ss")
val words = lines.flatMap(_.split("\\s+"))
val wordCounts = words.map(x=>(x,1)).updateStateByKey{
  (seq, op:Option[Int]) => { Some(seq.sum + op.getOrElse(0)) }
}
wordCounts.print()

ssc.start()
ssc.awaitTermination()

将之前的reduceByKey换成updateStateByKey。可以看到每次执行都会记录下最后一次的结果,有新的变化就在这个结果上累加。

查看检查点文件夹:

-rw-r--r--. 1 root root 4625 May 29 21:45 checkpoint-1464583530000
-rw-r--r--. 1 root root 4625 May 29 21:45 checkpoint-1464583535000
-rw-r--r--. 1 root root 4635 May 29 21:45 checkpoint-1464583535000.bk
-rw-r--r--. 1 root root 4628 May 29 21:45 checkpoint-1464583540000
-rw-r--r--. 1 root root 4638 May 29 21:45 checkpoint-1464583540000.bk
-rw-r--r--. 1 root root 4628 May 29 21:45 checkpoint-1464583545000
-rw-r--r--. 1 root root 4638 May 29 21:45 checkpoint-1464583545000.bk
-rw-r--r--. 1 root root 4630 May 29 21:45 checkpoint-1464583550000
-rw-r--r--. 1 root root 4640 May 29 21:45 checkpoint-1464583550000.bk
-rw-r--r--. 1 root root 4641 May 29 21:45 checkpoint-1464583555000
drwxr-xr-x. 2 root root 4096 May 29 21:45 receivedBlockMetadata

时间段结果累计reduceByKeyAndWindow

窗口DStream

l  window(windowLength, slideInterval)

l  countByWindow(windowLength, slideInterval)

l  reduceByWindow(func, windowLength, slideInterval)

l  reduceByKeyAndWindow(func, windowLength, slideInterval, [numTasks])

l  reduceByKeyAndWindow(func, invFunc, windowLength, slideInterval,[numTasks])

l  countByValueAndWindow(windowLength, slideInterval, [numTasks])

如果需要更灵活记录累计值,可以使用窗口相关函数,窗口函数有两个重要参数:WindowLength和SlidingInterval,它们的含义参考下图说明:


处理时间:绿色框就代表每10秒处理一次。

窗口长度:WindowLength如每5个绿色窗口作为一个阶段。

滑动间隔:SlidingInterval如每隔3个进行一次统计。它必须是绿色框处理时间的倍数。

import org.apache.spark.storage.StorageLevel
import org.apache.spark.streaming._

val ssc = new StreamingContext(sc, Seconds(2))
ssc.checkpoint("/root/checkpoint")

// 通过Socket获取数据,该处需要提供NC.Socket的主机名和端口号,数据保存在内存和硬盘中
val lines = ssc.socketTextStream("192.168.1.106",9999, StorageLevel.MEMORY_AND_DISK_SER)
val words = lines.flatMap(_.split("\\s+"))
words.map((_,1)).reduceByKey(_+_).print
words.map((_,1)).reduceByKeyAndWindow( (x:Int,y:Int)=>x+y, Seconds(6), Seconds(2) ).print

ssc.start()
ssc.awaitTermination()

6.sparkStreaming的输出

 控制台输出print

输入案例中都使用控制台输出

文本文件输出saveAsTextFiles

import org.apache.spark.streaming._
val ssc = new StreamingContext(sc, Seconds(10))
val lines = ssc.textFileStream("file:///root/ss")
val words = lines.flatMap(_.split("\\s+"))
val wordCounts = words.map((_,1)).reduceByKey(_+_).saveAsTextFiles("file:///root/output/out")
ssc.start()

dfs输出

import org.apache.spark.streaming._
val ssc = new StreamingContext(sc, Seconds(10))
val lines = ssc.textFileStream("file:///root/ss")
val words = lines.flatMap(_.split("\\s+"))
val wordCounts = words.map((_,1)).reduceByKey(_+_).saveAsTextFiles("hdfs:///spark/sparkStreaming")
ssc.start()

7.链接kafka

streaming和kafka整合注意版本匹配:

kafka_2.11-0.10.0.0           2.11 scala版本

kafka_2.10-0.10.0.1            2.11 scala版本

上传jar包

kafka_2.10-0.8.2.1.jar,kafka-clients-0.8.2.1.jar,metrics-core-2.2.0.jar,spark-streaming-kafka_2.10-1.5.0.jar,zkclient-0.3.jar

进入spark-shell执行:

bin/spark-shell --master=local[4] --jars=kafka_2.10-0.8.2.1.jar,kafka-clients-0.8.2.1.jar,metrics-core-2.2.0.jar,spark-streaming-kafka_2.10-1.5.0.jar,zkclient-0.3.jar

import org.apache.spark._
import org.apache.spark.streaming._
import org.apache.spark.streaming.kafka._

val ssc = new StreamingContext(sc, Seconds(10))
val ds = KafkaUtils.createStream(ssc, "hadoop01:2181", "group1", Map("topic1"->1))
// Kafka的x不是一个值,而是一个tuple x._2 才是wordcount中的key
ds.map{x => (x._2,1)}.reduceByKey {_ + _}.print

ssc.start
ssc.awaitTermination()
结果:
-------------------------------------------
Time: 1525542380000 ms
-------------------------------------------
(a,1)

8.spark优化

1)  在java编程中推荐使用包装类型(比如分数如果使用包装类型在没有分值的时候可以定为null),在scala中推荐使用基本类型,因为长度短,网络传输时快。包装类型是基本类型多40byte。

2)  hashmap(数组+链表、因子0.75)

有数据要存放到hashmap中,把这个数据的hashcode值,把数据放入这个地方。如果这个地方已经有数据,产生链表来存放。把新的数据作为链表的第一个元素,新的数据挂接到数组上,旧的元素挂接到新的元素后面。(有冲突元素)

查询快,数组,(数组内存连续存储)链表查询就慢,先比较数组,如果有重复元素,对象比较。

因子0.75,扩容有缺点,缺点:hashcode%n,数据偏移

spark检索快,spark没有直接使用java提供hashmap,自己研发了新版本,二次探测法新的hashmap

1)  消除链表,利用空挡,新的hashmap中数据比较充满

2)  声明数组时根据业务尽量声明的大一些

a)   二次探测法

hashcode运算速度快,if对象比较效率低

二次探测法消除链表,它以固定算法

查询效率高,

spark blockmanage块处理

自动发现,

如果两个节点是在同一个机器,直接调用

如果两个节点是在两个机器,会创建socket

启动任务时,分配资源

1)  spark会根据情况,安排资源。默认是所有的资源(8core,默认就开启8个分区)

2)  可以自己定义分区数,sc.makeRDD(List(1,2,3,4,5),2)

如果把所有的资源分配给这个任务,但是任务用不了那么多。

repatition,重新设置分区数量,增加分区数量,可以减少

数据倾斜来源

hash算法,导致数据倾斜,数据key导致

解决数据倾斜,修改key,给key前面加上很长的随机数

redis缓存预热,自己来实现热点数据缓存:写一个程序将热点数据存放到redis中。

大集群环境中禁止部分命令

spark中:collect命令是将各个节点的数据收集到driver端,如果节点巨多那么driver端会卡死。

redis中keys *:这个命令在大集群中几乎没用,即使你执行了,海量数据也看不过来,还会影响现有的业务运行。

redis中flush all:正式环境中一定禁用

所以一般修改源码:在大型集群中禁止使用这样的命令

猜你喜欢

转载自blog.csdn.net/tansuoliming/article/details/80208295