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:正式环境中一定禁用
所以一般修改源码:在大型集群中禁止使用这样的命令