文章目录
RDD创建
从文件系统中加载数据创建RDD:
-
Spark采用textFile() 方法来从文件系统中加载数据创建RDD
-
方法把文件的URI作为参数,这个URI可以是:
1) 本地文件系统的地址 2) 或者是分布式文件系统HDFS的地址 3) 或者是Amazon S3的地址等
通过并行集合(数组)创建RDD:
可以调用SparkContext的parallelize方法,在Driver中一个已经存在的集合(数组)上创建。
val array = Array(1,2,3,4,5)
val rdd = sc.parallelize(array)
RDD操作
转换操作
- 对于RDD而言,每一个转换操作都会产生不同的RDD,供一个操作使用。
- 转换到的RDD是惰性求值的。不发生真正的计算。
基本的转化操作
函数名 | 目的 |
---|---|
map() | 将函数应用于RDD中的每个元素,将返回值构成新的RDD |
flatmap() | 将函数应用于RDD中的每个元素,将返回的迭代器的内容构成新的RDD,通常用来切分单词 |
filter() | 返回一个通过传给filter()的函数的元素组成RDD |
distinct() | 去重 |
sample(withReplacement, fraction, [seed] | 对RDD采样,以及是否替换 |
union() | 生成一个包含两个RDD中所有元素的RDD |
intersection() | 求两个RDD共同的元素的RDD |
subtract() | 移除一个RDD中的内容 |
cartesian() | 于另一个RDD的笛卡尔积 |
groupByKey | 应用于(K, b)键值对的数据集时,返回一个新的(K, Iterable)形式的数据集 |
reduceByKey(func) | 应用于(K, V)键值对的数据集时,返回一个新的(K, V)形式的数据集,其中的每个值是将每个Key传递到函数func中进行聚合 |
行动操作
真正触发计算的地方。
函数名 | 目的 |
---|---|
collect() | 返回RDD中的所有元素 |
count() | RDD中的元素个数 |
countByValue() | 各元素在RDD中出现的次数 |
take(num) | 从RDD中返回num个元素 |
top(num) | 从RDD中返回最前面的num个元素 |
takeOrdered(num) (ordering) | 从RDD中按照提供的顺序返回最前面的num个元素 |
takeSample(withRepalcement, num, [seed]) | 从RDD中返回任意一些元素 |
reduce(func) | 并行整合RDD中所有的整数 |
fold(zero)(func) | 和reduce()一样,但是需要提供初始值 |
aggreagte(zeroValue)(seqOp, comOp) | 和Reduce()相似,但是通常返回不同类型的函数 |
foreach(func) | 对RDD中的每个元素使用给定的函数 |
注意点:
- collect() 要求所有数据都必须能一同放入单台机器的内存中。
- take(n) 尝试只访问尽量少的分区,因此会得到一个不均衡的集合,返回元素的顺序与预期不一致。
- top() 可以通过提供自己的比较函数,来提取前几个元素
- foreach() 不将任何结果返回到驱动程序中,把数据发送到一个网络服务器中或数据库中。
持久化
spark中,RDD采用惰性求值得机制,每次遇到行动操作,都会从头开始执行计算,每次调用行动操作,都会触发一次从头开始的计算,这对于迭代操作而言,代价是很大的,迭代计算经常需要多次重复使用同一组数据。
可以通过持久化(缓存)机制避免这种重复计算的开销。
可以使用persist()方法对一个RDD标记为持久化。
persist()的圆括号内中包含的是持久化级别参数。
- persist(MEMORY_ONLY):表示将RDD作为反序列化的对象存储于JVM中,如果内存不足,就要按照LRU原则替换缓存中的内存。
- persist(MEMORY_AND_DISK)表示将RDD作为反序列化的对象存储在JVM中,如果内存不足,超出的分区将会被存放在硬盘上。
- 可以使用unpersist()方法手动地把持久化的RDD从缓存中移除。
- 一般而言,使用cache()方法时,会调用persist(MEMORY_ONLY)
分区
分区原则:
RDD分区的一个分区原则是使得分区的个数尽量等于集群中的CPU核心数目。
默认分区数目:
对于不同的Spark部署模式而言,都可以通过设置spark.default.parallelism这个参数的值,来配置默认的分区数目。
手动设置分区:
- 创建RDD时:在调用textFile和parallelize方法时候手动指定分区个数,sc.textFile(path, partitionNum)
- 通过转换操作得到RDD时:直接调用repatition方法即可(RDD数据量在经过转换操作后变小,无需过多的分区)
打印元素:
- 单机模式下打印输出到屏幕上:rdd.foreach(println)或rdd.map(println)
- 为了能够把所有worker节点上的打印输出信息也显示到Driver Program中,可以使用collect()方法:rdd.collect().foreach(println)。但是如果数据量过大,可能会导致内存溢出,采用rdd.take(100).foreach(println)。
Pair RDD的创建
从文件中加载
使用map()函数创建:
val lines = sc.textFile("file:///usr/local/spark/mycode/pairrdd/word.txt")
val pairRDD = lines.flatMap(line => line.split(" ").map(word => (word.1))
pairRDD.foreach(println)
通过并行集合(数组)创建RDD
val list = List("Hadoop","Spark","Hive")
val rdd = sc.parallelize(list)
val pairRDD = rdd.map(word => (word,1))
pairRDD.foreach(println)
常用的键值对
RDD转换操作
reduceByKey
reduceByKey(func)的功能是,使用func函数合并具有相同键的值
scala> pairRdd.collect().foreach(println)
(Hadoop,1)
(Spark,1)
(Hive,1)
(Spark,1)
scala> pairRdd.reduceByKey((a,b) => a+b).collect().foreach(println)
(Hive,1)
(Spark,2)
(Hadoop,1)
gourpByKey
groupByKey()按key值分组,生成一个值的列表。
scala> pairRDD.groupByKey()
res1: org.apache.spark.rdd.RDD[(String, Iterable[Int])] = ShuffledRDD[2] at groupByKey at <console>:31
reduceByKey和groupByKey的区别
scala> val words = Array("one", "two", "two", "three", "three", "three")
words: Array[String] = Array(one, two, two, three, three, three)
scala> val wordPairRdd = sc.parallelize(words).map(word => (word, 1))
wordPairRdd: org.apache.spark.rdd.RDD[(String, Int)] = MapPartitionsRDD[4] at map at <console>:26
scala> val wordCountsWithReduce = wordPairRdd.reduceByKey(_+_)
wordCountsWithReduce: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[5] at reduceByKey at <console>:28 ^
scala> val wordCountsWithGroup = wordPairRdd.groupByKey().map(t => (t._1, t._2.sum))
wordCountsWithGroup: org.apache.spark.rdd.RDD[(String, Int)] = MapPartitionsRDD[7] at map at <console>:28
因为groupByKey在传输前不进行合并操作,所以通信开销比reduceByKey大。
keys、values和sortByKey
keys只会把pair RDD中的key返回形成一个新的RDD。
values只会把pair RDD中的value返回形成一个新的RDD。
sortByKey()的功能是返回一个根据键排序的RDD,默认为升序排序,参数为false时,降序
sortBy()根据所给参数决定根据键或值排序。
scala> val d1 = sc.parallelize(Array(("c", 8), ("b",25), ("c", 17), ("a", 42), ("b", 4),("d", 9),("e", 17), ("c", 2), ("f", 29), ("g", 21), ("b", 9)))
d1: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[15] at parallelize at <console>:24
scala> d1.reduceByKey(_+_).sortByKey(false).collect
res3: Array[(String, Int)] = Array((g,21), (f,29), (e,17), (d,9), (c,27), (b,38), (a,42))
scala> val d2 = sc.parallelize(Array(("c", 8), ("b",25), ("c", 17), ("a", 42), ("b", 4),("d", 9),("e", 17), ("c", 2), ("f", 29), ("g", 21), ("b", 9)))
d2: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[8] at parallelize at <console>:24
scala> d2.reduceByKey(_+_).sortBy(_._2, false).collect
res2: Array[(String, Int)] = Array((a,42), (b,38), (f,29), (c,27), (g,21), (e,17), (d,9))
mapValues和join
mapValues(func)
对键值对RDD中的每个value都应用一个函数
join
表示内连接。对于给定的两个输入数据集,只有在两个数据集中都存在key才会被输出。最终得到一个(K, (V1, V2))类型的数据集
共享变量
Spark中的两个重要抽象是RDD和共享变量。
当Spark在集群的多个不同节点的多个任务上运行一个函数时,它会把函数中涉及到的每个变量在每个任务上都生成一个副本。
但是,有时候,需要在多个任务之间共享变量,或者在任务Task和任务控制节点Driver Program之间共享变量。
为了满足这种需求,Spark提供了两种类型的变量:广播变量和累加器。
广播变量用来把变量在所有节点的内存之间进行共享。
累加器则支持在不同节点之间进行累加计算。
广播变量:
- 在每个机器上缓存一个只读的变量。
- Spark的“行动”操作会跨越多个阶段,对每个阶段内的所有任务所需要的的公共数据,Spark都会自动进行广播。
//创建广播变量
val.broadcastVar = sc.broadcast(Array(1,2,3))
broadcastVar.value //获取广播值
累加器:
- 累加器是仅仅被相关操作累加的变量,通常可以用来实现计数器和求和。
- 一个数值型的累加器,可以通过调用SparkContext.longAccumulator()或者SparkContext.doubleAccumulator()来创建运行在集群中的任务,就可以使用add方法来把数值累加到累加器上,但是,这些任务只能做累加操作,不能读取累加器的值只有任务控制节点可以使用value方法来读累加器的值。
val accum = sc.longAccumulator("my accumulate")
val list = sc.parallelize(Array(1,2,3,4,5)).foreach(x => accum.add(x))
accum.value