Hadoop可以完成项目的功能实现,spark是hadoop的功能优化实现,spark使用的内存基于内存进行计算,一个jar包中有很多任务,特点是:迭代式计算(后一个job依赖前一个job记过)和交互式数据挖掘(shell)。
spark被看成是一整套的大数据处理的通用处理引擎,是一套大数据的处理方案一个大的软件栈,在各个方面都可以基于此进行实现
spark中的角色:
集群中的角色:
master是集群中的管理节点,worker是在正式工作的节点。master是集群中的资源管理,worker是管理自己的资源的。
程序运行中的角色:
每一个spark项目由一个驱动器程序执行其中的main函数,驱动器程序的工作包括:
将程序分成若干任务,对执行器进行跟踪和调度,创建sparkContext(可以看成是和集群的一个连接,在shell中是一个sc对象,编程时候通过conf对象来生成);
执行器是运行在工作节点的进程,负责真正的作业工作并返回相应的结果,RDD是缓存在执行器进程中的。
driver和executor是对于当前这个程序而言的,程序执行完成那么这对角色也没有了,master和worker则是集群的机器角色。
驱动器程序和执行器程序之间通过集群资源调度管理器(内存和核心),常见的模式有Local只有本身自己一台机器,local-cluster伪分布相似的一台机器多个进程,standalone(master和workers存在于这种模式下的真正的集群模式),yarn,Mesos(专业的集群管理者)等
spark项目的提交方式:
spark-submit的总的格式是 :
spark-submit [options] <app jar | python file > [app options]
options的一些参数:
–class: 你的应用的启动类 (如 org.apache.spark.examples.SparkPi)
–master: 集群的master URL (如 spark://192.168.9.102:7077)
–deploy-mode: 是否发布你的驱动到worker节点(cluster) 或者作为一个本地客户端 (client) (default: client)*
–name 应用的显示名,会在网页中显示
–jars需要上传并放在CLASSPATH中的第三方应用jar,少量的jar可以这么做
–files 需要放到应用工作目录中的文件的列表,分发到各个节点的数据文件
–conf: 任意的Spark配置属性, 格式key=value. 如果值包含空格,可以加引号“key=value”. 缺省的Spark配置 application-jar: 打包好的应用jar,包含依赖. 这个URL在集群中全局可见。 比如hdfs:// 共享存储系统, 如果是 file:// path, 那么所有的节点的path都包含同样的jar.
application-arguments: 传给main()方法的参数
例如:
bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--master spark://hadoop102:7077 \
--executor-memory 1G \
--total-executor-cores 2 \
examples/jars/spark-examples_2.11-2.1.1.jar 100
master参数的例子:
-
local 本地单线程的跑
-
local[K] 本地k个线程跑
-
spark://HOST:PORT 独立集群standalone中的master
-
mesos://HOST:PORT连接到指定的Mesos集群
-
yarn-client 作为客户端连接到YARN cluster,集群位置由HADOOP_CONF_DIR指定
-
yarn-cluster:以cluster形式连接到YARN cluster,集群位置由HADOOP_CONF_DIR指定,两者差别在driver的运行 位置在哪里。client在提交的机器上,cluster在集群中的一台上。后面的yarn上的可以分开写,master写yarn,deploy-mode写client或者cluster
在运行spark-shell中不指定master的是local[* ],是以本地的模式在运行的。同常测试集群和生产集群两种,端口4040是对当前运行(注意只是当前,历史的查看需要重新配置)application的job状况查看,8080查看集群中的情况,7077是集群提交的端口。
一般spark程序的工作过程:
通过外部数据产生RDD,通过filter这样的转化操作产生新的RDD,通过行动操作触发spark优化后的并行计算,通过persist()实现持久化保存中间的需要进行重用的结果。
RDD操作:行动操作(不产生新的RDD)和转化操作(返回产生的新RDD),filter这个操作虽然产生了新的RDD但是在对旧的RDD来说,在后面的程序中依然可以使用,
创建RDD:通过sc.parallelize产生;通过从外部数据读取(sc.textFile等)
RDD的惰性求值:产生的新的RDD,只有在进行行动操作的时候才会被真正的计算。
由于转化操作的存在,使得RDD之间存在着类似派生的关系,spark使用的系谱图来记录这些RDD之间的关系。常用来进行恢复数据等操作使用
一些重要的RDD操作
普通RDD操作:
- 单个RDD的操作:
take(10) 获取指定数目的元素rdd;
filter(x=>x>10) 针对各个元素过滤掉返回值是false的元素;
map(x=>(x,1)) 针对各个元素进行操作;
flatMap(x=>x.split(" ")) 针对各个元素,单个元素可能返回值不止一个,接受所有的返回值作为一个RDD中的元素;
count() 对元素进行计数;
first() 返回第一个元素;
collect() 整个的元素返回驱动器, 消耗巨大;
sample() 对rdd进行取样,结果非确定的;
distinct() 使集合中元素唯一;
reduce((x,y)=>x+y) 对所有的元素进行归约,比如对RDD中所有元素求和;
fold(sum)((x,y)=>x+y),所有元素的归约,但是是存在初始值的;
aggregate((0, 0))( (acc, value) => (acc. _1 + value, acc._2 + 1), (acc1, acc2) => (acc1._1 + acc2._1, acc1._2 + acc2._2)) val avg = result._1 / result._2.toDouble 分别是初始值(0,0),(acc,value)本地节点运行是遇到value如何操作,(acc1,acc2)在多个累加器之间合并操作;
foreach(x=>x+1) 可以在不返回驱动器的情况下操作每一个元素;
- 集合操作:
union(other) 取两个RDD的并集
substract(other) 差集,在前者而不在后者的元素
intersection(other)取两个RDD的交集
cartesian(other) 两个RDD 的笛卡尔积
- RDD的持久化
多次重复计算同一个RDD的消耗可以通过持久化的操作减少多次重复计算,持久化有不同的级别,
用法:rdd.persist(StorageLevel.DISK_ONLY)
,持久化是在将数据存在执行器进程的缓存中的,也可以使用unpersist()解除持久化操作
pairRDD操作:
pairRDD支持所有的普通的RDD的操作,只是在书写时候可以使用
val r3=r2.map(x=>x._1+x._2) val r4=r2.filter{case (x,y) => x>1}
两种。
键值对的RDD可以从普通的RDD转化过来,val pairRdd=r1.map(x=>(x,1))
单个pairRDD 的操作:
reduceByKey((x,y)=>x+y) 对相同key的元素操作
groupBykey() 按照key进行分组
combineByKey() 合并具有相同的key
mapValues() 对所有的values进行相同的操作
flatMapValues()
keys() 获取所有的keys
values() 获取所有的values
sortByKey() 根据key进行排序
lookup(key) 返回给定的键对相应的所有的值
collectAsMap() 结果以映射表的形式返回
countBykey() 对每个键分别计数
针对两个pairRDD的转化操作
subtractBykey() 在key上的差集操作
join() 根据key进行内连接
rightOuterJoin
leftOuterJoin
cogroup 将相同的key进行分组
聚合操作:
reduceByKey((x,y)=>x+y)
combine求key对应的均值val result = input.combineByKey(
(v) => (v, 1),//新key作v的转化
(acc: (Int, Int), v) => (acc._1 + v, acc._2 + 1),//已经存在的key对于其v的处理
(acc1: (Int, Int), acc2: (Int, Int)) => (acc1._1 + acc2._1, acc1._2 + acc2._2) ).map{ case (key, value) => (key, value._1 / value._2.toFloat) }//多个分区之间的合并
数据分区
为了减少数据通信的代价,对于多次访问多次扫描基于Key的多次操作的RDD可以考虑进行分区,首先所有的pairRDD都可以针对Key进行分组,spark所有的共同一组的key(通过某种函数获得相同的结果的key是一组的)的数据出现在同一个分区中。数据分区时候需要充分的考虑后续使用的时候重复计算的部分,比如相对较大的表和较小的表在进行连接时候是通过计算key的哈希值来进行匹配的,此时就可以将大一点的表进行分区,创建新的RDD并持久化,这样在后续的哈希计算过程中,就不需要重新计算了,获取新的joined的RDD时候大大减少了计算量。
注意在实现过程中的持久化的位置,不然每次划分之后都重新计算没分区的意义就不存在了。
partitionBy()这个方法需要使用的版本尽量不要特别旧。
使用RDD.partitioner属性来获取分区的信息,返回的结果是一个scala.Option对象,这个对象中的isDefined()检查是否有值,get()方法获取其中的值。
从分区中获益的操作:
一些常见的操作用到分区的信息:
sortByKey 使用范围分区
groupByKey 使用哈希分区
join 使用哈希分区
cogroup
groupWith
leftOuterJoin
groupByKey
reduceByKey
combineByKey
lookup
二元操作中至少一个RDD是不用做数据混洗的,通常选择那个数据操作量耗时严重的作为不发生数据混洗的那个。