RDD编程

简介: RDD编程

一、RDD编程基础

(一)RDD创建

       Spark采用textFile()方法来从文件系统中加载数据创建RDD 该方法把文件的URI作为参数,这个URI可以是: 本地文件系统的地址、或者是分布式文件系统HDFS的地址或者是Amazon S3的地址等等。

1、从文件系统中加载数据创建RDD

"file:///home/zhc/mycode/word.txt"文件内容如下:

Hadoop is good

Spark is better

Spark is fast

(1)从本地文件系统中加载数据创建RDD

>>> lines = sc.textFile("file:///home/zhc/mycode/word.txt")
>>> lines.foreach(print)
Hadoop is good
Spark is better
Spark is fast

(2)从分布式文件系统HDFS中加载数据

>>> lines = sc.textFile("hdfs://localhost:9000/user/zhc/word.txt")
>>> lines = sc.textFile("/user/zhc/word.txt")
>>> lines = sc.textFile("word.txt")

三条语句是完全等价的,可以使用其中任意一种方式。

>>> lines.foreach(print)
Hadoop is good
Spark is better
Spark is fast

2. 通过并行集合(列表)创建RDD

       可以调用SparkContext的parallelize方法,在Driver中一个已经存在的集合(列表)上创建,从而实现并行化处理。

>>> array = [1,2,3,4,5]
>>> rdd = sc.parallelize(array)
>>> rdd.foreach(print)
1
2
3
4
5

(二)RDD操作

1、转换操作

       对于RDD而言,每一次转换操作都会产生不同的RDD,供给下一个“转换”使用 转换得到的RDD是惰性求值的,也就是说,整个转换过程只是记录了转换的轨迹,并不会发生真正的计算,只有遇到行动操作时,才会发生真正的计算,开始从血缘关系源头开始,进行物理的转换操作。

常用的RDD转换操作API

操作 含义
filter(func) 筛选出满足函数func的元素,并返回一个新的数据集
map(func) 将每个元素传递到函数func中,并将结果返回为一个新的数据集
flatMap(func) 与map()相似,但每个输入元素都可以映射到0或多个输出结果
groupByKey() 应用于(K,V)键值对的数据集时,返回一个新的(K, Iterable)形式的数据集
reduceByKey(func) 应用于(K,V)键值对的数据集时,返回一个新的(K, V)形式的数据集,其中每个值是将每个key传递到函数func中进行聚合后的结果

(1)filter(func)

filter(func)会筛选出满足函数func的元素,并返回一个新的数据集。

>>> lines = sc.textFile("file:///home/zhc/mycode/word.txt")
>>> linesWithSpark = lines.filter(lambda line: "Spark" in line)
>>> linesWithSpark.foreach(print)
Spark is better
Spark is fast

(2)map(func)

map(func)操作将每个元素传递到函数func中,并将结果返回为一个新的数据集。

>>> data = [1,2,3,4,5]
>>> rdd1 = sc.parallelize(data)
>>> rdd2 = rdd1.map(lambda x:x+10)
>>> rdd2.foreach(print)
11
13
12
14
15

另外一个实例:

>>> lines = sc.textFile("file:///home/zhc/mycode/word.txt")
>>> words = lines.map(lambda line:line.split(" "))
>>> words.foreach(print)
['Hadoop', 'is', 'good']
['Spark', 'is', 'fast']
['Spark', 'is', 'better']


(3)flatMap(func)

>>> lines = sc.textFile("file:///home/zhc/mycode/word.txt")
>>> words = lines.flatMap(lambda line:line.split(" "))


(4)groupByKey()

groupByKey()应用于(K,V)键值对的数据集时,返回一个新的(K, Iterable)形式的数据集。

>>> words = sc.parallelize([("Hadoop",1),("is",1),("good",1), \
... ("Spark",1),("is",1),("fast",1),("Spark",1),("is",1),("better",1)])
>>> words1 = words.groupByKey()
>>> words1.foreach(print)
('Hadoop', <pyspark.resultiterable.ResultIterable object at 0x7fab13a8d160>)
('fast', <pyspark.resultiterable.ResultIterable object at 0x7fab13a8d0b8>)
('better', <pyspark.resultiterable.ResultIterable object at 0x7fab13a8d160>)
('is', <pyspark.resultiterable.ResultIterable object at 0x7fab13a8d160>)
('good', <pyspark.resultiterable.ResultIterable object at 0x7fab13a8d0b8>)
('Spark', <pyspark.resultiterable.ResultIterable object at 0x7fab13a8d160>)

(5)reduceByKey(func)

reduceByKey(func)应用于(K,V)键值对的数据集时,返回一个新的(K, V)形式的数据集,其中的每个值是将每个key传递到函数func中进行聚合后得到的结果。

>>> words = sc.parallelize([("Hadoop",1),("is",1),("good",1),("Spark",1), \
... ("is",1),("fast",1),("Spark",1),("is",1),("better",1)])
>>> words1 = words.reduceByKey(lambda a,b:a+b)
>>> words1.foreach(print)
('good', 1)
('Hadoop', 1)
('better', 1)
('Spark', 2)
('fast', 1)
('is', 3)

rdd.reduceByKey(lambda a,b:a+b)

<“spark”,<1,1,1>>

2、行动操作

     行动操作是真正触发计算的地方。Spark程序执行到行动操作时,才会执行真正的计算,从文件中加载数据,完成一次又一次转换操作,最终,完成行动操作得到结果。

常用的RDD行动操作API

操作 含义
count() 返回数据集中的元素个数
collect() 以数组的形式返回数据集中的所有元素
first() 返回数据集中的第一个元素
take(n) 以数组的形式返回数据集中的前n个元素
reduce(func) 通过函数func(输入两个参数并返回一个值)聚合数据集中的元素
foreach(func) 将数据集中的每个元素传递到函数func中运行

以下是通过一个实例来介绍上表中的各个行动操作,这里同时给出了在pyspark环境中执行的代码及其结果。

>>> rdd = sc.parallelize([1,2,3,4,5])
>>> rdd.count()
5
>>> rdd.first()
1
>>> rdd.take(3)
[1, 2, 3]
>>> rdd.reduce(lambda a,b:a+b)
15
>>> rdd.collect()
[1, 2, 3, 4, 5]
>>> rdd.foreach(lambda elem:print(elem))
1
2
3
4
5
3、惰性机制

       所谓的“惰性机制”是指,整个转换过程只是记录了转换的轨迹,并不会发生真正的计算,只有遇到行动操作时,才会触发“从头到尾”的真正的计算。这里给出一段简单的语句来解释Spark的惰性机制。

>>> lines = sc.textFile("file:///home/zhc/mycode/word.txt")
>>> lineLengths = lines.map(lambda s:len(s))
>>> totalLength = lineLengths.reduce(lambda a,b:a+b)
>>> print(totalLength)
42

(三)持久化

       在Spark中,RDD采用惰性求值的机制,每次遇到行动操作,都会从头开始执行计算。每次调用行动操作,都会触发一次从头开始的计算。这对于迭代计算而言,代价是很大的,迭代计算经常需要多次重复使用同一组数据。下面就是多次计算同一个RDD的例子:

>>> list = ["Hadoop","Spark","Hive"]
>>> rdd = sc.parallelize(list)
>>> print(rdd.count())         #行动操作,触发一次真正从头到尾的计算
3
>>> print(','.join(rdd.collect()))          #行动操作,触发一次真正从头到尾的计算
Hadoop,Spark,Hive

     可以通过持久化(缓存)机制避免这种重复计算的开销。具体方法是使用persist()方法对一个RDD标记为持久化,之所以说“标记为持久化”,是因为出现persist()语句的地方,并不会马上计算生成RDD并把它持久化,而是要等到遇到第一个行动操作触发真正计算以后,才会把计算结果进行持久化。持久化后的RDD将会被保留在计算节点的内存中被后面的行动操作重复使用。

persist()的圆括号中包含的是持久化级别参数:

persist(MEMORY_ONLY):表示将RDD作为反序列化的对象存储于JVM中,如果内存不足,就要按照LRU原则替换缓存中的内容。

persist(MEMORY_AND_DISK)表示将RDD作为反序列化的对象存储在JVM中,如果内存不足,超出的分区将会被存放在硬盘上。

 一般而言,使用cache()方法时,会调用persist(MEMORY_ONLY)。针对上面的实例,增加持久化语句以后的执行过程如下:

>>> list = ["Hadoop","Spark","Hive"]
>>> rdd = sc.parallelize(list)
>>> rdd.cache()  #会调用persist(MEMORY_ONLY),但是,语句执行到这里,并不会缓存rdd,因为这时rdd还没有被计算生成
>>> print(rdd.count()) #第一次行动操作,触发一次真正从头到尾的计算,这时上面的rdd.cache()才会被执行,把这个rdd放到缓存中
3
>>> print(','.join(rdd.collect())) #第二次行动操作,不需要触发从头到尾的计算,只需要重复使用上面缓存中的rdd
Hadoop,Spark,Hive

    持久化RDD会占用内存空间,当不需要一个RDD时,可以使用unpersist()方法手动地把持久化的RDD从缓存中移除,释放内存空间。

(四)分区

    RDD是弹性分布式数据集,通常RDD很大,会被分成很多个分区,分别保存在不同的节点上。

1、分区的作用

(1)增加并行度

(2)减少通信开销

UserData(UserId,UserInfo)

Events(UserID,LinkInfo)

UserData 和Events 表进行连接操作,获得(UserID,UserInfo,LinkInfo)

未分区时对UserData和Events两个表进行连接操作:

     实际上,由于userData这个RDD要比 events大很多,所以,可以选择对userData进行区。 比如,可以采用哈希分区方法,把userData这个RDD分区成m个分区,这些分区分布在节点u1、u2……um上。 对userData进行分区以后,在执行连接操作时,就不会产生上图的数据混洗情况。

采用分区以后对UserData和Events两个表进行连接操作:

       由于已经对userData根据哈希值进行了分区,因此,在执行连接操作时,不需要再把userData 中的每个元素进行哈希求值以后再分发到其他节点上,只需要对events 这个RDD的每个元素求哈希值(采用与userData相同的哈希函数)。然后,根据哈希值把每个events 中的RDD元素分发到对应的节点u1、u2……um上面。整个过程中,只有events发生了数据混洗,产生了网络通信,而userData 的数据都是在本地引用,不会产生网络传输开销。由此可以看出,Spark通过数据分区,可以大大降低一些特定类型的操作(比如join()、leftOuterJoin()、groupByKey()、reduceByKey()等)的网络传输开销。

2、RDD分区原则

       RDD分区的一个原则是使得分区的个数尽量等于集群中的CPU核心(core)数目。对于不同的Spark部署模式而言(本地模式、Standalone模式、YARN模式、Mesos模式),都可以通过设置spark.default.parallelism这个参数的值,来配置默认的分区数目,一般而言:

*Local模式:默认为本地机器的CPU数目,若设置了local[N],则默认为N。

*Standalone或YARN:在“集群中所有CPU核心数目总和”和“2”二者中取较大值作为默认值。

*Apache Mesos:默认的分区数为8。

3、设置分区的个数

(1)创建RDD时手动指定分区个数

在调用textFile()和parallelize()方法的时候手动指定分区个数即可,语法格式如下: sc.textFile(path, partitionNum)

其中,path参数用于指定要加载的文件的地址,partitionNum参数用于指定分区个数。下面是一个分区实例。

>>> list = [1,2,3,4,5]
>>> rdd = sc.parallelize(list,2)     #设置两个分区

对于paralelize0而言,如果没有在方法中指定分区数,则默认为spark.default.parallelism。对于textFile()而言,如果没有在方法中指定分区数,则默认为min(defaultParallelism,2),其中,defaultParallelism对应的就是spark.default.parallelism。如果是从HDFS 中读取文件,则分区数为文件分片数(比如,128MB/片)。

(2)使用reparititon方法重新设置分区个数

通过转换操作得到新 RDD 时,直接调用 repartition 方法即可。例如:

>>> data = sc.parallelize([1,2,3,4,5],2)
>>> len(data.glom().collect())       #显示data这个RDD的分区数量
2
>>> rdd = data.repartition(1)        #对data这个RDD进行重新分区
>>> len(rdd.glom().collect())        #显示rdd这个RDD的分区数量
1

4.自定义分区方法

 Spark提供了自带的HashPartitioner(哈希分区)与RangePartitioner(区域分区),能够满足大多数应用场景的需求。与此同时,Spark也支持自定义分区方式,即通过提供一个自定义的分区函数来控制RDD的分区方式,从而利用领域知识进一步减少通信开销。

实例:根据key值的最后一位数字,写到不同的文件

例如:10写入到part-00000,11写入到part-00001 . . . 19写入到part-00009。

打开一个Linux终端,使用vim编辑器创建一个代码文件“/home/zhc/mycode/TestPartitioner.py”,输入以下代码:

from pyspark import SparkConf, SparkContext
 
def MyPartitioner(key):
    print("MyPartitioner is running")
    print('The key is %d' % key)
    return key%10
 
def main():
    print("The main function is running")
    conf = SparkConf().setMaster("local").setAppName("MyApp")
    sc = SparkContext(conf = conf)
    data=sc.parallelize(range(10),5)
    data.map(lambda x:(x,1)) \
        .partitionBy(10,MyPartitioner) \
        .map(lambda x:x[0]) \
        .saveAsTextFile("file:///usr/local/spark/mycode/rdd/partitioner")
 
if __name__ == '__main__':
  main()
[root@bigdata mycode]# vi TestPartitioner.py
[root@bigdata mycode]# python3 TestPartitioner.py 

或者通过spark-submit提交文件:

[root@bigdata mycode]# spark-submit TestPartitioner.py

运行结果:

       运行结束后可以看到,在本地文件系统的“file://home/zhc/mycode/partitioner"目录下面,会生成 part-00000、part-00001、part-00002……part-00009和_SUCCESS等文件。其中,part-00000 文件中包含了数字0,part-00001文件中包含了数字1, part-00002文件中包含了数字2。

(五)一个综合实例

       假设有一个本地文件word.txt,里面包含了很多行文本,每行文本由多个单词构成,单词之间用空格分隔。可以使用如下语句进行词频统计(即统计每个单词出现的次数):

>>> lines = sc. \
... textFile("file:///home/zhc/mycode/word.txt")
>>> wordCount = lines.flatMap(lambda line:line.split(" ")). \
... map(lambda word:(word,1)).reduceByKey(lambda a,b:a+b)
>>> print(wordCount.collect())
[('good', 1), ('Spark', 2), ('is', 3), ('better', 1), ('Hadoop', 1), ('fast', 1)]

词频统计执行示意图:

       在实际应用中,单词文件可能非常大,会被保存到分布式文件系统HDFS中,Spark和Hadoop会统一部署在一个集群上。

二、键值对RDD

       键值对RDD(Pair RDD)是指每个 RDD 元素都是(key,value)键值对类型,是一种常见的RDD类型,可以应用于很多应用场景。

(一)键值对RDD的创建

1、第一种创建方式:从文件中加载生成RDD

       在Linux系统本地文件新建“/home/zhc/mycode/pairrdd/word.txt”,里面包含如下内容:

I love Hadoop

Hadoop is good

Spark is fast

    首先使用textFile()方法从文件中加载数据,使用map()函数转换得到相应的键值对RDD。

>>> lines = sc.textFile("file:///home/zhc/mycode/pairrdd/word.txt")
>>> pairRDD = lines.flatMap(lambda line:line.split(" ")).map(lambda word:(word,1))
>>> pairRDD.foreach(print)
('Spark', 1)
('I', 1)
('is', 1)
('fast', 1)
('love', 1)
('hadoop', 1)
('Hadoop', 1)
('is', 1)
('good', 1)

  在上述语句中,map(lambda word:(word,1))函数的作用是,取出RDD中的每个元素,也就是每个单词,赋值给word,然后,把word转换成(word,1)的键值对形式。

2、第二种创建方式:通过并行集合(列表)创建RDD

   下面代码从一个列表创建一个键值对RDD:      

>>> list = ["Hadoop","Spark","Hive","Spark"]
>>> rdd = sc.parallelize(list)
>>> pairRDD = rdd.map(lambda word:(word,1))
>>> pairRDD.foreach(print)
(Hadoop,1)
(Spark,1)
(Hive,1)
(Spark,1)

(二)常用键值对转换操作

       常用键值对转换操作包括reduceByKey(func)、groupByKey()、keys、values、sortByKey()、sortBy()、mapValues(func)、join()和combineByKey等。

1、reduceByKey(func)

       reduceByKey(func)的功能是,使用func函数合并具有相同键的值。

(Hadoop,1)

(Spark,1)

(Hive,1)

(Spark,1)

>>> pairRDD = sc.parallelize([("Hadoop",1),("Spark",1),("Hive",1),("Spark",1)])
>>> pairRDD.reduceByKey(lambda a,b:a+b).foreach(print)
('Spark', 2)
('Hive', 1)
('Hadoop', 1)

2、groupByKey()

     groupByKey()的功能是,对具有相同键的值进行分组。比如,对四个键值对("spark",1)、("spark",2)、("hadoop",3)和("hadoop",5),采用groupByKey()后得到的结果是:("spark",(1,2))和("hadoop",(3,5))。

(spark,1)

(spark,2)

(hadoop,3)

(hadoop,5)

>>> list = [("spark",1),("spark",2),("hadoop",3),("hadoop",5)]
>>> pairRDD = sc.parallelize(list)
>>> pairRDD.groupByKey()
PythonRDD[27] at RDD at PythonRDD.scala:48
>>> pairRDD.groupByKey().foreach(print)
('hadoop', <pyspark.resultiterable.ResultIterable object at 0x7f2c1093ecf8>)
('spark', <pyspark.resultiterable.ResultIterable object at 0x7f2c1093ecf8>)

reduceByKey和groupByKey的区别

       reduceByKey用于对每个key对应的多个value进行merge操作,最重要的是它能够在本地先进行merge操作,并且merge操作可以通过函数自定义。

       groupByKey也是对每个key进行操作,但只生成一个sequence,groupByKey本身不能自定义函数,需要先用groupByKey生成RDD,然后才能对此RDD通过map进行自定义函数操作。

>>> words = ["one", "two", "two", "three", "three", "three"]
>>> wordPairsRDD = sc.parallelize(words).map(lambda word:(word, 1))
>>> wordCountsWithReduce = wordPairsRDD.reduceByKey(lambda a,b:a+b)
>>> wordCountsWithReduce.foreach(print)
('one', 1)
('two', 2)
('three', 3)
>>> wordCountsWithGroup = wordPairsRDD.groupByKey(). \
... map(lambda t:(t[0],sum(t[1])))
>>> wordCountsWithGroup.foreach(print)
('two', 2)
('three', 3)
('one', 1)

      上面得到的wordCountsWithReduce和wordCountsWithGroup是完全一样的,但是,它们的内部运算过程是不同的。

3、keys

       keys只会把Pair RDD中的key返回形成一个新的RDD。

(Hadoop,1)

(Spark,1)

(Hive,1)

(Spark,1)

>>> list = [("Hadoop",1),("Spark",1),("Hive",1),("Spark",1)]
>>> pairRDD = sc.parallelize(list)
>>> pairRDD.keys().foreach(print)
Hadoop
Spark
Hive
Spark

4、values

       values只会把Pair RDD中的value返回形成一个新的RDD。

(Hadoop,1)

(Spark,1)

(Hive,1)

(Spark,1)

>>> list = [("Hadoop",1),("Spark",1),("Hive",1),("Spark",1)]
>>> pairRDD = sc.parallelize(list)
>>> pairRDD.values().foreach(print)
1
1
1
1

5、sortByKey()

       sortByKey()的功能是返回一个根据键排序的RDD。

(Hadoop,1)

(Spark,1)

(Hive,1)

(Spark,1)

>>> list = [("Hadoop",1),("Spark",1),("Hive",1),("Spark",1)]
>>> pairRDD = sc.parallelize(list)
>>> pairRDD.foreach(print)
('Hadoop', 1)
('Spark', 1)
('Hive', 1)
('Spark', 1)
>>> pairRDD.sortByKey().foreach(print)
('Hadoop', 1)
('Hive', 1)
('Spark', 1)
('Spark', 1)

6、sortBy()

       sortBy()可以根据其他字段进行排序。

>>> d1 = sc.parallelize([("c",8),("b",25),("c",17),("a",42), \
... ("b",4),("d",9),("e",17),("c",2),("f",29),("g",21),("b",9)])
>>> d1.reduceByKey(lambda a,b:a+b).sortBy(lambda x:x,False).collect()
[('g', 21), ('f', 29), ('e', 17), ('d', 9), ('c', 27), ('b', 38), ('a', 42)]
>>> d1.reduceByKey(lambda a,b:a+b).sortBy(lambda x:x[0],False).collect()
[('g', 21), ('f', 29), ('e', 17), ('d', 9), ('c', 27), ('b', 38), ('a', 42)]
>>> d1.reduceByKey(lambda a,b:a+b).sortBy(lambda x:x[1],False).collect()
[('a', 42), ('b', 38), ('f', 29), ('c', 27), ('g', 21), ('e', 17), ('d', 9)]

       在上述语句中, sortBy(lambda x:x[1],False)中的"x[1]”表示每个键值对RDD元素的value,也就是根据value来排序,False表示按照降序排序。

与sortByKey()对比:

>>> d1 = sc.parallelize([("c",8),("b",25),("c",17),("a",42), \
... ("b",4),("d",9),("e",17),("c",2),("f",29),("g",21),("b",9)])
>>> d1.reduceByKey(lambda a,b:a+b).sortByKey(False).collect()
[('g', 21), ('f', 29), ('e', 17), ('d', 9), ('c', 27), ('b', 38), ('a', 42)]

     sortByKey(False)括号中的参数False表示按照降序排序,如果没有提供参数False,则默认采用升序排序(即参数取值为True)。从排序后的效果可以看出,所有键值对都按照key的降序进行了排序,因此输出[(g, 21),(f,29), (e',17),(d, 9),(c,27),(b,38),(a', 42)]。

       但是,如果要根据21、29、17等数值进行排序,就无法直接使用sortByKey0来实现,这时可以使用sortBy()。

7、mapValues(func)

       对键值对RDD中的每个value都应用一个函数,但是,key不会发生变化。

(Hadoop,1)

(Spark,1)

(Hive,1)

(Spark,1)

>>> list = [("Hadoop",1),("Spark",1),("Hive",1),("Spark",1)]
>>> pairRDD = sc.parallelize(list)
>>> pairRDD1 = pairRDD.mapValues(lambda x:x+1)
>>> pairRDD1.foreach(print)
('Hadoop', 2)
('Spark', 2)
('Hive', 2)
('Spark', 2)

8、join()

       join就表示内连接。对于内连接,对于给定的两个输入数据集(K,V1)和(K,V2),只有在两个数据集中都存在的key才会被输出,最终得到一个(K,(V1,V2))类型的数据集。

>>> pairRDD1 = sc. \
... parallelize([("spark",1),("spark",2),("hadoop",3),("hadoop",5)])
>>> pairRDD2 = sc.parallelize([("spark","fast")])
>>> pairRDD3 = pairRDD1.join(pairRDD2)
>>> pairRDD3.foreach(print)
('spark', (1, 'fast'))
('spark', (2, 'fast'))

      从上述代码及其执行结果可以看出,pairRDD1中的键值对("spark",1)和pairRDD2中的键值对("spark","fast"),因为二者具有相同的key(即"spark"),所以会产生连接结果("spark",(1,"fast"))。

9、combineByKey

       创建一个代码文件“/home/zhc/mycode/Combine.py”,并输入如下代码:

#/home/zhc/mycode/Combine.py
from pyspark import SparkConf, SparkContext
conf = SparkConf().setMaster("local").setAppName("Combine ")
sc = SparkContext(conf = conf)
data=sc.parallelize([("company-1",88),("company-1",96),("company-1",85), \
     ("company-2",94),("company-2",86),("company-2",74),("company-3",86), \
     ("company-3",88),("company-3",92)],3)
res = data.combineByKey(\
            lambda income:(income,1),\
            lambda acc,income:(acc[0]+income, acc[1]+1),\
            lambda acc1,acc2:(acc1[0]+acc2[0],acc1[1]+acc2[1])). \
map(lambda x:(x[0],x[1][0],x[1][0]/float(x[1][1])))
res.repartition(1).saveAsTextFile("file:///home/zhc/mycode/combineresult")

执行如下命令运行该程序:

[root@bigdata mycode]# spark-submit Combine.py 
23/12/17 16:14:25 WARN NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable

执行后,在“file:///home/zhc/mycode/combineresult”目录下查看part-00000文件。

(三)一个综合实例

题目:给定一组键值对("spark",2),("hadoop",6),("hadoop",4),("spark",6),键值对的key表示图书名称,value表示某天图书销量,请计算每个键对应的平均值,也就是计算每种图书的每天平均销量。

>>> rdd = sc.parallelize([("spark",2),("hadoop",6),("hadoop",4),("spark",6)])
>>> rdd.mapValues(lambda x:(x,1)).\
... reduceByKey(lambda x,y:(x[0]+y[0],x[1]+y[1])).\
... mapValues(lambda x:x[0]/x[1]).collect()
[('hadoop', 5.0), ('spark', 4.0)]

三、数据读写

介绍在RDD编程中如何进行文件数据读写和HBase数据读写。

(一)文件数据读写

1、本地文件系统的数据读写

(1)从文件中读取数据创建RDD

"file:///home/zhc/mycode/word.txt"文件内容如下:

Hadoop is good

Spark is better

Spark is fast

>>> textFile = sc.textFile("file:///home/zhc/mycode/word.txt")
>>> textFile.first()
'Hadoop is good'

       因为Spark采用了惰性机制,在执行转换操作的时候,即使输入了错误的语句,spark-shell也不会马上报错(假设word123.txt不存在)。只有当后面继续执行textFile.first()操作时,系统才会报错。

>>> textFile = sc.textFile("file:///home/zhc/mycode/word123.txt")

(2)把RDD写入到文本文件中

>>> textFile = sc.textFile("file:///home/zhc/mycode/word.txt")
>>> textFile.saveAsTextFile("file:///home/zhc/mycode/writeback")

2、分布式文件系统HDFS的数据读写

       从分布式文件系统HDFS中读取数据,也是采用textFile()方法,可以为textFile()方法提供一个HDFS文件或目录地址,如果是一个文件地址,它会加载该文件,如果是一个目录地址,它会加载该目录下的所有文件的数据。具体语句如下:

>>> textFile = sc.textFile("hdfs://localhost:9000/user/zhc/word.txt")
>>> textFile.first()

如下三条语句都是等价的:

>>> textFile = sc.textFile("hdfs://localhost:9000/user/zhc/word.txt")
>>> textFile = sc.textFile("/user/zhc/word.txt")
>>> textFile = sc.textFile("word.txt")

同样,可以使用saveAsTextFile()方法把RDD中的数据保存到HDFS文件中,命令如下:

>>> textFile = sc.textFile("word.txt")
>>> textFile.saveAsTextFile("writeback")

(二)读写HBase数据

Hbase的下载安装与配置可以参照博客:大数据存储技术(3)—— HBase分布式数据库-CSDN博客

https://blog.csdn.net/Morse_Chen/article/details/135036296

1、创建一个HBase表

       因为HBase是伪分布式模式,需要调用HDFS,所以,请首先在终端中输入下面命令启动Hadoop:

[root@bigdata zhc]# start-dfs.sh

     然后,执行命令启动HBase:

1. [root@bigdata zhc]# start-hbase.sh
2. [root@bigdata zhc]# hbase shell

     其次,创建一个student表,在这个表中录入数据。

hbase> create 'student','info'
hbase> put 'student','1','info:name','Xueqian'                                                                                                                                                                      
hbase> put 'student','1','info:gender','F'                                                                                                                                                                        
hbase> put 'student','1','info:age','23'                                                                                                                                                                        
hbase> put 'student','2','info:name','Weiliang'                                                                                                                                                                       
hbase> put 'student','2','info:gender','M'                                                                                                                                                                         
hbase> put 'student','2','info:age','24'

录入结束后,可以执行如下命令查看已经录入的数据:

1. hbase> get 'student','1'      # 查看第一行数据
2. hbase> scan 'student'         # 查看全部数据

2、配置Spark

参考博客:三、(三)配置Spark

大数据存储技术(3)—— HBase分布式数据库-CSDN博客

https://blog.csdn.net/Morse_Chen/article/details/135036296

3、编写程序读取HBase数据

       如果要让Spark读取HBase,就需要使用SparkContext提供的newAPIHadoopRDD这个API将表的内容以RDD的形式加载到Spark中。

在“/home/zhc/mycode/RDD/SparkOperateHBase.py”文件中输入:

#/home/zhc/mycode/RDD/SparkOperateHBase.py
from pyspark import SparkConf, SparkContext
conf = SparkConf().setMaster("local").setAppName("ReadHBase")
sc = SparkContext(conf = conf)
host = 'localhost'
table = 'student'
conf = {"hbase.zookeeper.quorum": host, "hbase.mapreduce.inputtable": table}
keyConv = "org.apache.spark.examples.pythonconverters.ImmutableBytesWritableToStringConverter"
valueConv = "org.apache.spark.examples.pythonconverters.HBaseResultToStringConverter"
hbase_rdd = sc.newAPIHadoopRDD("org.apache.hadoop.hbase.mapreduce.TableInputFormat","org.apache.hadoop.hbase.io.ImmutableBytesWritable","org.apache.hadoop.hbase.client.Result",keyConverter=keyConv,valueConverter=valueConv,conf=conf)
count = hbase_rdd.count()
hbase_rdd.cache()
output = hbase_rdd.collect()
for (k, v) in output:
    print (k, v)

执行该代码,命令如下:

[root@bigdata RDD]# vi SparkOperateHBase.py
[root@bigdata RDD]# spark-submit SparkOperateHBase.py

得到如下结果:

4、编写程序向HBase写入数据

在“/home/zhc/mycode/RDD/SparkWriteHBase.py”文件中输入:

#/home/zhc/mycode/RDD/SparkWriteHBase.py
from pyspark import SparkConf, SparkContext
conf = SparkConf().setMaster("local").setAppName("ReadHBase")
sc = SparkContext(conf = conf)
host = 'localhost'
table = 'student'
keyConv = "org.apache.spark.examples.pythonconverters.StringToImmutableBytesWritableConverter"
valueConv = "org.apache.spark.examples.pythonconverters.StringListToPutConverter"
conf = {"hbase.zookeeper.quorum": host,"hbase.mapred.outputtable": table,"mapreduce.outputformat.class": "org.apache.hadoop.hbase.mapreduce.TableOutputFormat","mapreduce.job.output.key.class": "org.apache.hadoop.hbase.io.ImmutableBytesWritable","mapreduce.job.output.value.class": "org.apache.hadoop.io.Writable"}
rawData = ['5,info,name,Rongcheng','5,info,gender,M','5,info,age,26','6,info,name,Guanhua','6,info,gender,M','6,info,age,27']
sc.parallelize(rawData).map(lambda x: (x[0],x.split(','))).saveAsNewAPIHadoopDataset(conf=conf,keyConverter=keyConv,valueConverter=valueConv)

执行该代码,命令如下:

1. [root@bigdata RDD]# vi SparkWriteHBase.py
2. [root@bigdata RDD]# spark-submit SparkWriteHBase.py

得到如下结果:

 

最后友情提醒:使用完HBase和Hadoop后,要先关闭HBase,再关闭Hadoop!


相关实践学习
lindorm多模间数据无缝流转
展现了Lindorm多模融合能力——用kafka API写入,无缝流转在各引擎内进行数据存储和计算的实验。
云数据库HBase版使用教程
&nbsp; 相关的阿里云产品:云数据库 HBase 版 面向大数据领域的一站式NoSQL服务,100%兼容开源HBase并深度扩展,支持海量数据下的实时存储、高并发吞吐、轻SQL分析、全文检索、时序时空查询等能力,是风控、推荐、广告、物联网、车联网、Feeds流、数据大屏等场景首选数据库,是为淘宝、支付宝、菜鸟等众多阿里核心业务提供关键支撑的数据库。 了解产品详情:&nbsp;https://cn.aliyun.com/product/hbase &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
2月前
|
存储 缓存 分布式计算
大数据-89 Spark 集群 RDD 编程-高阶 编写代码、RDD依赖关系、RDD持久化/缓存
大数据-89 Spark 集群 RDD 编程-高阶 编写代码、RDD依赖关系、RDD持久化/缓存
44 4
|
7月前
|
存储 分布式计算 Hadoop
Spark 【RDD编程(一)RDD编程基础】
Spark 【RDD编程(一)RDD编程基础】
|
7月前
|
缓存 分布式计算 Java
Spark【RDD编程(二)RDD编程基础】
Spark【RDD编程(二)RDD编程基础】
|
7月前
|
分布式计算 Hadoop 数据处理
Spark【RDD编程(三)键值对RDD】
Spark【RDD编程(三)键值对RDD】
|
存储 缓存 分布式计算
深入 RDD_定义_RDD 的特点 | 学习笔记
快速学习 深入 RDD_定义_RDD 的特点
126 0
深入 RDD_定义_RDD 的特点 | 学习笔记
|
存储 分布式计算 自然语言处理
RDD 入门_RDD 是什么|学习笔记
快速学习 RDD 入门_RDD 是什么
RDD 入门_RDD 是什么|学习笔记
|
分布式计算 并行计算 大数据
RDD 入门_创建 RDD 的三种方式|学习笔记
快速学习 RDD 入门_创建 RDD 的三种方式
RDD 入门_创建 RDD 的三种方式|学习笔记
|
分布式计算 大数据 开发者
RDD 入门_FlatMap 算子|学习笔记
快速学习 RDD 入门_FlatMap 算子
RDD 入门_FlatMap 算子|学习笔记
|
分布式计算 大数据 开发者
RDD 入门_ReduceByKey 算子|学习笔记
快速学习 RDD 入门_ReduceByKey 算子
RDD 入门_ReduceByKey 算子|学习笔记
|
分布式计算 大数据 开发者
RDD 入门_Map 算子|学习笔记
快速学习 RDD 入门_Map 算子
RDD 入门_Map 算子|学习笔记