前言
上一篇文章介绍了RDD编程中非常重要的键值对RDD编程方法。本小节按照原定计划,继续整理RDD编程章节中的后续内容。
⭐️前文提到,完整的RDD编程章节会分为4部分进行介绍:
- RDD编程基础
- 键值对RDD
- 数据读写
- 综合案例
截至目前,RDD编程基础和键值对RDD已经介绍过了。本节首先介绍数据读写相关的内容,然后再通过几个综合案例,深化对RDD编程的三篇文章的理解 。
⭐️本文(RDD数据读写)目录如下:
前言
文件数据读写
读写HBase数据
RDD综合实例
总结
Part1.文件数据读写
⭐️可以从本地文件系统中读取数据。
1)从文件中读取数据创建RDD 。
>>>
因为Spark采用了惰性机制,在执行转换操作的时候,即使输入了错误的语句, 假设word123.txt不存在,spark-shell也不会马上报错 。
>>> textFile = sc.
... textFile("file:///usr/local/spark/mycode/wordcount/word123.txt")
2)把RDD写入到文本文件中。
>>> textFile = sc.
... textFile("file:///usr/local/spark/mycode/rdd/word.txt")
>>> textFile.
... saveAsTextFile("file:///usr/local/spark/mycode/rdd/writeback")
⭐️如果想再次把数据加载在RDD中,只要使用writeback这个目录 :
>>> textFile = sc.
... textFile("file:///usr/local/spark/mycode/rdd/writeback")
⭐️可以从分布式文件系统HDFS中读写数据。
从分布式文件系统HDFS中读取数据,也是采用textFile()方法,可以为textFile() 方法提供一个HDFS文件或目录地址,如果是一个文件地址,它会加载该文件, 如果是一个目录地址,它会加载该目录下的所有文件的数据。如下三条语句都是等价的:
>>> textFile = sc.textFile("hdfs://localhost:9000/user/hadoop/word.txt")
>>> textFile = sc.textFile("/user/hadoop/word.txt")
>>> textFile = sc.textFile("word.txt")
⭐️同样,可以使用saveAsTextFile()方法把RDD中的数据保存到HDFS文件中,命 令如下:
>>> textFile = sc.textFile("word.txt")
>>> textFile.saveAsTextFile("writeback")
Part3.读写HBase数据
HBase是Google BigTable的开源实现。之所以叫做BigTable,是因为他是一个很大很大的表。每个值是一个未经解释的字符串,没有数据类型。但与关系型数据库不同。HBase是一个稀疏、多维度、排序的映射表 。这张表的索引是行键、 列族、列限定符和时间戳。用户在表中存储数据,每一行都有一个可排序的行键和任意多的列。表在水平方向由一个或者多个列族组成,一个列族中可以包含任意多 个列,同一个列族里面的数据存储在一起。
列族支持动态扩展 ,可以很轻松地添加一个列族或列,无需预先定义 列的数量以及类型,所有列均以字符串形式存储,用户需要自行进行 数据类型转换 。HBase中执行更新操作时,并不会删除数据旧的版本,而是生成一个 新的版本,旧有的版本仍然保留 (这是和HDFS只允许追加不允许修 改的特性相关的)。
⭐️分别解析一下HBase的索引:
- 表:HBase采用表来组织数据,表由行 和列组成,列划分为若干个列族 ;
- 行:每个HBase表都由若干行组成,每 个行由行键(row key)来标识;
- 列族:一个HBase表被分组成许多“列 族”(Column Family)的集合,它是 基本的访问控制单元;
- 列限定符:列族里的数据通过列限定符 (或列)来定位;
- 单元格:在HBase表中,通过行、列族 和列限定符确定一个“单元格”(cell ),单元格中存储的数据没有数据类型 ,总被视为字节数组byte[];
- 时间戳:每个单元格都保存着同一份数 据的多个版本,这些版本采用时间戳进行索引。
HBase中需要根据行键、列族、列限定符和时间戳来确定一个单元格 。因此 ,可以视为一个“四维坐标”,即[行键, 列族, 列限定符, 时间戳]。上表中的两个元素索引如下表。
![v2-336502ba4b832c9de773e7d3fdec2f77_b.jpg](http://img-03.proxy.5ce.com/view/image?&type=2&guid=8da71176-3f2f-eb11-8da9-e4434bdf6706&url=https://pic4.zhimg.com/v2-336502ba4b832c9de773e7d3fdec2f77_b.jpg)
对应到表中示意如下:
![v2-1902c6db4fd419a849076b31702733a9_b.jpg](http://img-02.proxy.5ce.com/view/image?&type=2&guid=8da71176-3f2f-eb11-8da9-e4434bdf6706&url=https://pic2.zhimg.com/v2-1902c6db4fd419a849076b31702733a9_b.jpg)
⭐️编写程序读取HBase数据。
如果要让Spark读取HBase,就需要使用SparkContext提供的 newAPIHadoopRDD这个API将表的内容以RDD的形式加载到Spark中。请注意代码中的注释。
#!/usr/bin/env python3
from pyspark import SparkConf, SparkContext
conf = SparkConf().setMaster("local").setAppName("ReadHBase")#本地模式,指定AppName
sc = SparkContext(conf = conf)#生成
host = 'localhost'
table = 'student'#表名
conf = {"hbase.zookeeper.quorum": host, "hbase.mapreduce.inputtable": table}
#Hbase只能保存为字符串,因此是tostring
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)
保存上述代码到文件SparkOperateHBase.py中,执行该代码文件,命令如下:
$ cd /usr/local/spark/mycode/rdd
$ /usr/local/spark/bin/spark-submit SparkOperateHBase.py
执行结果如下:
1 {"qualifier" : "age", "timestamp" : "1545728145163", "columnFamily" :
"info", "row" : "1", "type" : "Put", "value" : "23"}
{"qualifier" : "gender", "timestamp" : "1545728114020", "columnFamily" :
"info", "row" : "1", "type" : "Put", "value" : "F"}
{"qualifier" : "name", "timestamp" : "1545728100663", "columnFamily" :
"info", "row" : "1", "type" : "Put", "value" : "Xueqian"}
2 {"qualifier" : "age", "timestamp" : "1545728184030", "columnFamily" :
"info", "row" : "2", "type" : "Put", "value" : "24"}
{"qualifier" : "gender", "timestamp" : "1545728176815", "columnFamily" :
"info", "row" : "2", "type" : "Put", "value" : "M"}
{"qualifier" : "name", "timestamp" : "1545728168727", "columnFamily" :
"info", "row" : "2", "type" : "Put", "value" : "Weiliang"}
编写程序向HBase写入数据。
下面编写应用程序把表中的两个学生信息插入到HBase的student表中。
![v2-7f81362b9e1b9dab02b05b4bb8fd5c4d_b.jpg](http://img-01.proxy.5ce.com/view/image?&type=2&guid=8da71176-3f2f-eb11-8da9-e4434bdf6706&url=https://pic2.zhimg.com/v2-7f81362b9e1b9dab02b05b4bb8fd5c4d_b.jpg)
首先编写文件SparkWriteHBase.py,输入代码如下:
#!/usr/bin/env python3
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 =['3,info,name,Rongcheng','3,info,gender,M','3,info,age,26',
'4,info,name,Guanhua','4,info,gender,M','4,info,age,27']
sc.parallelize(rawData).map(lambda x:(x[0],x.split(','))).saveAsNewAPIHadoopDataset(
conf=conf,keyConverter=keyConv,valueConverter=valueConv)
接下来执行上述文件。
$ cd /usr/local/spark/mycode/rdd
$ /usr/local/spark/bin/spark-submit SparkWriteHBase.py
Part3.综合案例
⭐️案例1:求TOP值
四个键分别为orderid,userid,payment,productid。数据如下所示。求Top N个payment值。
![v2-3a2192d470e9497dd1d9107dd7f9302e_b.jpg](http://img-01.proxy.5ce.com/view/image?&type=2&guid=8da71176-3f2f-eb11-8da9-e4434bdf6706&url=https://pic3.zhimg.com/v2-3a2192d470e9497dd1d9107dd7f9302e_b.jpg)
首先总览一下答案代码,然后分条看一下运行逻辑。
#!/usr/bin/env python3
from pyspark import SparkConf, SparkContext
conf = SparkConf().setMaster("local").setAppName("ReadHBase")
sc = SparkContext(conf = conf)
lines = sc.textFile("file:///usr/local/spark/mycode/rdd/file")
result1 = lines.filter(lambda line:(len(line.strip()) > 0) and (len(line.split(","))== 4))
result2 = result1.map(lambda x:x.split(",")[2])
result3 = result2.map(lambda x:(int(x),""))
result4 = result3.repartition(1)
result5 = result4.sortByKey(False)
result6 = result5.map(lambda x:x[0])
result7 = result6.take(5)
for a in result7:
print(a)
接下来分别解读上述代码。
lines = sc.textFile("file:///usr/local/spark/mycode/rdd/file")
该语句从文件中读取数据生成RDD(名称为lines),执行后的效果如下:
![v2-cff6651182dfe0630ae6197bf383b530_b.jpg](http://img-03.proxy.5ce.com/view/image?&type=2&guid=8da71176-3f2f-eb11-8da9-e4434bdf6706&url=https://pic1.zhimg.com/v2-cff6651182dfe0630ae6197bf383b530_b.jpg)
result1 = lines.filter(lambda line:(len(line.strip()) > 0) and (len(line.split(","))== 4))
result2 = result1.map(lambda x:x.split(",")[2])
该语句执行效果如下:
![v2-89d41e1386003fdc56c490a142fbe345_b.jpg](http://img-01.proxy.5ce.com/view/image?&type=2&guid=8da71176-3f2f-eb11-8da9-e4434bdf6706&url=https://pic2.zhimg.com/v2-89d41e1386003fdc56c490a142fbe345_b.jpg)
result3 = result2.map(lambda x:(int(x),""))
result4 = result3.repartition(1)
result5 = result4.sortByKey(False)
该语句执行效果如下:
![v2-fd328d8984673242791e8ff433b4f297_b.jpg](http://img-03.proxy.5ce.com/view/image?&type=2&guid=8da71176-3f2f-eb11-8da9-e4434bdf6706&url=https://pic4.zhimg.com/v2-fd328d8984673242791e8ff433b4f297_b.jpg)
result6 = result5.map(lambda x:x[0])
result7 = result6.take(5)
该语句执行效果如下:
![v2-3cfce5c3cedf6feddd1fc4627d4ee189_b.jpg](http://img-03.proxy.5ce.com/view/image?&type=2&guid=8da71176-3f2f-eb11-8da9-e4434bdf6706&url=https://pic2.zhimg.com/v2-3cfce5c3cedf6feddd1fc4627d4ee189_b.jpg)
⭐️案例2:文件排序
有多个输入文件,每个文件中的每一行内容 均为一个整数。要求读取所有文件中的整数, 进行排序后,输出到一个新的文件中,输出 的内容个数为每行两个整数,第一个整数为 第二个整数的排序位次,第二个整数为原待 排序的整数
![v2-495db4a1feeb80199ee0e9f2cb63cb05_b.jpg](http://img-02.proxy.5ce.com/view/image?&type=2&guid=8da71176-3f2f-eb11-8da9-e4434bdf6706&url=https://pic2.zhimg.com/v2-495db4a1feeb80199ee0e9f2cb63cb05_b.jpg)
![v2-8566d2eefba347bdc44b94ba2d62dc82_b.jpg](http://img-01.proxy.5ce.com/view/image?&type=2&guid=8da71176-3f2f-eb11-8da9-e4434bdf6706&url=https://pic3.zhimg.com/v2-8566d2eefba347bdc44b94ba2d62dc82_b.jpg)
首先看代码总览,然后分段观察其运行逻辑。
#!/usr/bin/env python3
from pyspark import SparkConf, SparkContext
index = 0
def getindex():
global index
index+=1
return index
def main():
conf = SparkConf().setMaster("local[1]").setAppName("FileSort")
sc = SparkContext(conf = conf)
lines = sc.textFile("file:///usr/local/spark/mycode/rdd/filesort/file*.txt")
index = 0
result1 = lines.filter(lambda line:(len(line.strip()) > 0))
result2 = result1.map(lambda x:(int(x.strip()),""))
result3 = result2.repartition(1)
result4 = result3.sortByKey(True)
result5 = result4.map(lambda x:x[0])
result6 = result5.map(lambda x:(getindex(),x))
result6.foreach(print)
result6.saveAsTextFile("file:///usr/local/spark/mycode/rdd/filesort/sortresult")
if __name__ == '__main__':
main()
接下来分段看一下结果。
lines = sc.textFile("file:///usr/local/spark/mycode/rdd/filesort/file*.txt")
result1 = lines.filter(lambda line:(len(line.strip()) > 0))
该语句执行效果如下:
![v2-74b31683216bc1878e86b60b87e2c7ec_b.jpg](http://img-01.proxy.5ce.com/view/image?&type=2&guid=8da71176-3f2f-eb11-8da9-e4434bdf6706&url=https://pic1.zhimg.com/v2-74b31683216bc1878e86b60b87e2c7ec_b.jpg)
result2 = result1.map(lambda x:(int(x.strip()),""))
result3 = result2.repartition(1)
result4 = result3.sortByKey(True)
该语句执行效果如下:
![v2-f8239b67a3e4d2dd62553ad0624a8612_b.jpg](http://img-03.proxy.5ce.com/view/image?&type=2&guid=8da71176-3f2f-eb11-8da9-e4434bdf6706&url=https://pic3.zhimg.com/v2-f8239b67a3e4d2dd62553ad0624a8612_b.jpg)
result5 = result4.map(lambda x:x[0])
result6 = result5.map(lambda x:(getindex(),x))
该语句执行效果如下:
![v2-ffc2965b5f035b8daf2959bf5099ddff_b.jpg](http://img-02.proxy.5ce.com/view/image?&type=2&guid=8da71176-3f2f-eb11-8da9-e4434bdf6706&url=https://pic4.zhimg.com/v2-ffc2965b5f035b8daf2959bf5099ddff_b.jpg)
result6.saveAsTextFile("file:///usr/local/spark/mycode/rdd/filesort/sortresult")
该语句执行效果如下:
![v2-7e944442fbded4877f00aab88de47793_b.jpg](http://img-02.proxy.5ce.com/view/image?&type=2&guid=8da71176-3f2f-eb11-8da9-e4434bdf6706&url=https://pic4.zhimg.com/v2-7e944442fbded4877f00aab88de47793_b.jpg)
⭐️案例3:二次排序
对于一个给定的文件(数据如file1.txt所示),请对数据进行排序,首先根据第 1列数据降序排序,如果第1列数据相等,则根据第2列数据降序排序。
![v2-88f2f5c5582469d9b4d32ab19f2ed78e_b.jpg](http://img-03.proxy.5ce.com/view/image?&type=2&guid=8da71176-3f2f-eb11-8da9-e4434bdf6706&url=https://pic3.zhimg.com/v2-88f2f5c5582469d9b4d32ab19f2ed78e_b.jpg)
也就是说,期望能实现如下结果:
![v2-daceac25af6b1a489e2b5793491b2e87_b.jpg](http://img-01.proxy.5ce.com/view/image?&type=2&guid=8da71176-3f2f-eb11-8da9-e4434bdf6706&url=https://pic4.zhimg.com/v2-daceac25af6b1a489e2b5793491b2e87_b.jpg)
这就会遇到一个问题:用于排序的key必须是可比较的对象。如下的形式是不能用于比较的:
![v2-0feb153e9ba176038c26697a4642a22d_b.png](http://img-01.proxy.5ce.com/view/image?&type=2&guid=8da71176-3f2f-eb11-8da9-e4434bdf6706&url=https://pic2.zhimg.com/v2-0feb153e9ba176038c26697a4642a22d_b.png)
林子雨老师给出的解题思路如下:
![v2-36d30ba0128850cb6741bcf558ec73e9_b.jpg](http://img-02.proxy.5ce.com/view/image?&type=2&guid=8da71176-3f2f-eb11-8da9-e4434bdf6706&url=https://pic2.zhimg.com/v2-36d30ba0128850cb6741bcf558ec73e9_b.jpg)
转换为文字语言就是:
- 第一步:定义一个类SecondarySortKey,实现自定义的用于排序的key
- 第二步:将要进行二次排序的文件加载进来生成(key,value)类型的RDD
- 第三步:使用sortByKey()基于自定义的key进行二次排序
- 第四步:去除掉排序的key,只保留排序的结果
两部分代码如下所示。
from operator import gt
from pyspark import SparkContext, SparkConf
class SecondarySortKey():
def __init__(self, k):
self.column1 = k[0]
self.column2 = k[1]
def __gt__(self, other):
if other.column1 == self.column1:
return gt(self.column2,other.column2)
else:
return gt(self.column1, other.column1)
def main():
conf = SparkConf().setAppName('spark_sort').setMaster('local[1]')
sc = SparkContext(conf=conf)
file="file:///usr/local/spark/mycode/rdd/secondarysort/file4.txt"
rdd1 = sc.textFile(file)
rdd2 = rdd1.filter(lambda x:(len(x.strip()) > 0))
rdd3 = rdd2.map(lambda x:((int(x.split(" ")[0]),int(x.split(" ")[1])),x))
rdd4 = rdd3.map(lambda x: (SecondarySortKey(x[0]),x[1]))
rdd5 = rdd4.sortByKey(False)
rdd6 = rdd5.map(lambda x:x[1])
rdd6.foreach(print)
if __name__ == '__main__':
main()
下面分别来看一下每部分的运行逻辑。
rdd1 = sc.textFile(file)
rdd2 = rdd1.filter(lambda x:(len(x.strip()) > 0))
运行逻辑如下。
![v2-f81d45c9704b5c448712922ee61a5613_b.jpg](http://img-02.proxy.5ce.com/view/image?&type=2&guid=8da71176-3f2f-eb11-8da9-e4434bdf6706&url=https://pic4.zhimg.com/v2-f81d45c9704b5c448712922ee61a5613_b.jpg)
rdd3 = rdd2.map(lambda x:((int(x.split(" ")[0]),int(x.split(" ")[1])),x))
rdd4 = rdd3.map(lambda x: (SecondarySortKey(x[0]),x[1]))
运行逻辑如下。
![v2-2fcf473cec4772298e85970fa0848205_b.jpg](http://img-03.proxy.5ce.com/view/image?&type=2&guid=8da71176-3f2f-eb11-8da9-e4434bdf6706&url=https://pic2.zhimg.com/v2-2fcf473cec4772298e85970fa0848205_b.jpg)
rdd5 = rdd4.sortByKey(False)
rdd6 = rdd5.map(lambda x:x[1])
运行逻辑如下。
![v2-c2602ef9166cb6b3bf3b91f3d3b04ce8_b.jpg](http://img-01.proxy.5ce.com/view/image?&type=2&guid=8da71176-3f2f-eb11-8da9-e4434bdf6706&url=https://pic1.zhimg.com/v2-c2602ef9166cb6b3bf3b91f3d3b04ce8_b.jpg)
总结
本文主要是对数据读写的梳理。包括从本地读写和从HBase中读写。最后通过三个例子对RDD编程系列进行一个复盘。感谢阅读 。
推荐厦门大学林子雨老师的课程:
Spark编程基础(Python版) - 网易云课堂study.163.com