本来应该上周更新的,结果碰上五一,懒癌发作,就推迟了 = =。以后还是要按时完成任务。废话不多说,第四章-第六章主要讲了三个内容:键值对、数据读取与保存与Spark的两个共享特性(累加器和广播变量)。

键值对(PaiRDD)

1.创建

 #在Python中使用第一个单词作为键创建一个pairRDD,使用map()函数
pairs = lines.map(lambda x:(x.split(" ")[0],x))

2.转化(Transformation)

转化操作很多,有reduceByKey,foldByKey(),combineByKey()等,与普通RDD中的reduce()、fold()、aggregate()等类似,只不过是根据键来进行操作。

reduceByKey():与recude()类似,只不过是根据键进行聚合

foldByKey():与fold()类似

combineByKey():与aggregate()类似

 #用Python对第二个元素进行筛选
result = pairs.filter(lambda keyValue:len(keyValue[1]) < 20) #在Python中使用reduceByKey()和mapValues()计算每个键对应的平均值
rdd.mapValues(lambda x:(x,1)).reduceByKey(lambda x,y:(x[0]+y[0],x[1]+y[1])) #用Python实现单词计数
rdd.sc.textFile("文件地址")
words = rdd.flatMap(lambda x:x.split(" "))
result = words.map(lambda x:(x,1)).reduceByKey((x,y)=>x+y) #在Python中使用combineByKey()求每个键对应的平均值
sumCount = nums.combineByKey((lambda x:(x,1)),
(lambda x,y:(x[0]+y,x[1]+1)),
(lambda x,y:(x[0]+y[0],x[1]+y[1])))
sumCount.map(lambda key,xy:(key.xy[0]/xy[1])).collectAsMap() #在Python中自定义reduceByKey()的并行度
data = [("a",3),("b",4),("a",1)]
sc.parallelize(data).reduceByKey(lambda x,y:x+y)#默认并行度
sc.parallelize(data).reduceByKey(lambda x,y:x+y,10)#自定义并行度 #在Python中以字符串顺序对整数进行自定义排序
rdd.sortByKey(ascending = True,numPartitions = None,keyFunc = lambda x: str(x))

3.行动操作(Action)

  数据分区:数据比较大时,可以用partitionBy()转化为哈希分区。即通过向partitionBy传递一个spark.HashPartitioner对象来实现该操作。在Python中不能将HashPartitioner对象传递给partitionBy,只需要把需要的分区数传递过去(如 rdd.partitionBy(100))。

  在spark中,会为生成的结果RDD设好分区方式的操作有:cogroup(),groupWith(),join(),leftOuterJoin(),rightOutJoin,groupByKey(),reduceByKey(),combineByKey(),partitionBy(),sort(),mapValues(),flatMapValues(),filter()。最后三种只有当父RDD有分区方式时,结果RDD才会有分区RDD。其他的操作生成的结果都不会存在特定的分区方式。

  自定义分区方式:

#Python自定义分区方式
import urlparse def hash_domain(url):
return hash(urlparse.urlparse(url).netloc) rdd.partitionBy(20,hash_domain) #创建20个分区

数据的读取与保存

文件格式

格式名称 结构化 备注

文本文件

普通的文本文件,每行一条记录

JSON

半结构化 常见的基于文本的格式,半结构化;大多数库要求每行一条记录

CSV

常见文本结构

SequenceFile

一种用于键值对数据的常见Hadoop文件格式

Protocol buffers

一种快读、节约空间的跨语言格式

对象文件

用来将Spark作业中的数据存储下来以让共享的代码读取。改变类的时候回失效。因为它依赖于Java序列化

文本文件  

 #读取文本文件
input=sc.textFile("文件地址")
#保存文本文件
result.saveAsTextFile(outputFile)

JSON

 #读取Jason
import json
data = input.map(lambda x: json.loads(x))
#保存
(data.filter(lambda x : x["lovaPandas"]).map(lambda x:json.dumps(x))).saveAsTextFile(outputF

CSV文件

 #用textFile读取csv
import csv
import StringIO
def loadRecord(line):
"""解析一行csv记录"""
input = StringIO.StringIO(line)
reader = csv.DictReader(input,filenames =["name","favouriteAnimal"])
return reader.next()
input = sc.textFile(inputFile).map(loadRecord) #读取完整csv
def loadRecords(filenameContents):
"""读取给定文件中的所有记录"""
input = StringIO.StringIO(filenameContents[1])
reader = csv.DictReader(input,fieldnames = ["name","favouriteAnimal"])
return reader
fullFileData = sc.wholeTextFiles(inputFile).flatMap(loadRecords) #保存csv
def writeRecords(records):
"""写出一些csv记录"""
output = StringIO.StringIO()
writer = csv.DictReader(output,filenames = ["name","favouriteAnimal"])
for record in records:
writer.writerow(record)
return [output.getvalue()]
pandaLovers.mapPartitions(writeRecords).saveAsTextFile(outputFile)

SequenceFile

 #读取SequenceFile
val data = sc.sequenceFile(inFile,"ord.apache.hadoop.io.Text","org.apache.hadoop.io.InWitable")

对象文件

#对象文件,用Java序列化写的,速度慢,保存用saveAsObjectFile(),读取用 SparkContext中的objectFile()函数接收一个路径,返回对应的RDD。它无法在Python中使用

Spark SQL中的结构化数据

Apache Hive

 #Apache Hive
#用Python创建HiveContext并查询数据
from pyspark.sql import HiveContext hiveCtx =HiveContext(sc)
rows = hiveCtx.sql("SELECT name, age FROM users ")
firstRow = rows.first()
print firstRow.name

JSON数据

 #JSON数据示例
{"user":{"name":"Holden","location":"San Francisco"},"text":"Nice day out today"}
{"user":{"name":"Matei","location":"Berkeley"},"text":"Even nicer here :)"} #在Python中使用SparkSQL读取JSON数据
tweets = hiveCtx.jsonFile("tweets.json")
# UserWarning: jsonFile is deprecated. Use read.json() instead.
# warnings.warn("jsonFile is deprecated. Use read.json() instead.") tweets.registerTempTable("tweets")
results = hiveCtx.sql("SELECT user.name,text FROM tweets")

  

  这章关于sql的命令比较少,关于SQL的其他命令可以看看Spark的官方文档(PySpark 1.6.1 documentation),讲的比较详细。注意,这是spark 1.6版本,如果你安装的是1.2版本,1.6的有些命令是用不了的,可以先升级再用。

  最后再来讲讲Spark中两种类型的共享变量:累加器(accumulator)和广播变量(broadcast variable)

  累加器:对信息进行聚合。常见得一个用法是在调试时对作业执行进行计数。举个例子:假设我们从文件中读取呼号列表对应的日志,同时也想知道输入文件中有多少空行,就可以用到累加器。实例:

 #一条JSON格式的呼叫日志示例
#数据说明:这是无线电操作者的呼叫日志。呼号由国家分配,每个国家有自己呼号号段,所以可以根据呼号查到相对应的国家。有一些呼叫日志中包含操作者的地理位置,用来帮助确定距离
{"address":"address here","band":"40m","callSigns":"KK6JLK","city":"SUNNYVALE",
"contactlat":"37.384733","contactlong":"-122.032164",
"county":"Santa Clara","dxcc":"","fullname":"MATTHEW McPherrin",
"id":57779,"mode":"FM","mylat":"37.751952821","mylong":"-122.4208688735",...}
 #在Python中累加空行
file = sc.textFile(inputFile)
#创建Accumulator[int] 并初始化为0
blankLines = sc.accumulator(0) def extractCallSigns(line):
global blankLines #访问全局变量
if (line == ""):
blankLines += 1
return line.split(" ") callSigns = file.flatMap(extractCallSigns)
callSigns.saveAsTextFile(outputDir + "/callSigns")
print "Blank Lines:%d " % blankLines.value

  我们来看看这段程序,首先创建了一个叫做blankLines的Accumulator[Int]对象,然后在输入中看到空行就+1,执行完转化操作后就打印出累加器中的值。注意:只有在执行完saveAsTextFile()这个action操作后才能看到正确的计数,flatMap()是transformation操作,是惰性的,这点在上一篇博文已经讲过。

  但是我们上一篇文章中也提到过reduce()等这样的操作也是聚合操作,那为什么还有累加器这个东西存在呢?因为RDD本身提供的同步机制粒度太粗,尤其在transformation操作中变量状态不能同步,而累加器可以对那些与RDD本身的范围和粒度不一样的值进行聚合,不过它是一个write-only的变量,无法读取这个值,只能在驱动程序中使用value方法来读取累加器的值。

  累加器的用法:

  • 通过在驱动器中调用SparkContext.accumulator(initialValue)方法,创建出存有初始值的累加器。返回值为org.apache.spark.Accumulator[T]对象,其中T是初始值initialValue的类型。
  • Spark闭包里的执行器代码可以使用累加器的 += 方法(在Java中是add)增加累加器的值。
  • 驱动器程序可以调用累加器的Value属性来访问累加器的值(在Java中使用value()或setValue())

  对于之前的数据,我们可以做进一步计算:

 #在Python中使用累加器进行错误计数
#创建用来验证呼号的累加器
validSignCount = sc.accumulator(0)
invalidSignCount = sc.accumulator(0) def validataSign(sign):
global validSignCount,invalidSignCount
if re.match(r"\A\d?[a-zA-Z]{1,2}\d{1,4}[a-zA-Z]{1,3}\Z",sign):
validSignCount += 1
return True
else:
invalidSignCount += 1
return False #对与每个呼号的联系次数进行计数
validSigns = callings.filter(validataSign)
contactCount = validSigns.map(lambda sign:(sign,1)).reduceByKey(lambda (x,y):x+y) #强制求值计算计数
contactCount.count()
if validSignCount.value < 0.1 * validSignCount.value:
contactCount.saveAsTextFile(outputDir + "/contactCount")
else:
print "Too many errors: %d in %d" %(invalidSignCount.value,validSignCount.value)

  累加器与容错性:

  我们知道Spark是分布式计算,当有些机器执行得比较慢或者出错的时候,Spark会自动重新执行这些失败的或比较慢的任务。这样会导致同一个函数可能对同一个数据运行了多次,简单的说就是耗内存,降低了计算速度。在这种情况下,累加器怎么处理呢?

  对于要在Action操作中使用的累加器,Spark只会把每个任务对累加器的修改应用一次,一般放在foreach()操作中。而对于Transformation操作中的累加器,可能不止更新一次。所以Transformation中的累加器最好只在调试中使用。

广播变量

  广播变量允许程序员缓存一个只读的变量在每台机器上面,而不是每个任务保存一份拷贝。利用广播变量,我们能够以一种更有效率的方式将一个大数据量输入集合的副本分配给每个节点。广播变量通过两个方面提高数据共享效率:1,集群中每个节点(物理机器)只有一个副本,默认的闭包是每个任务一个副本;2,广播传输是通过BT下载模式实现的,也就是P2P下载,在集群多的情况下,可以极大的提高数据传输速率。广播变量修改后,不会反馈到其他节点。

  在Spark中,它会自动的把所有引用到的变量发送到工作节点上,这样做很方便,但是也很低效:一是默认的任务发射机制是专门为小任务进行优化的,二是在实际过程中可能会在多个并行操作中使用同一个变量,而Spark会分别为每个操作发送这个变量。举个例子,假设我们通过呼号的前缀查询国家,用Spark直接实现如下:

 #在Python中查询国家
#查询RDD contactCounts中的呼号的对应位置,将呼号前缀读取为国家前缀来进行查询
signPrefixes = loadCallSignTable() def processSignCount(sign_count,signPrefixes):
country = lookupCountry(sign_count[0],signPrefixes)
count = sign_count[1]
return (country,count) countryContactCounts = (contactCounts.map(processSignCount).reduceByKey((lambda x,y:x+y)))

  数据量小的时候可以运行,但是如果这个表很大,signPrefixes的很容易达到MB级别,从主节点为每个任务发送这样的数组会非常消耗内存,而且如果之后还需要用到signPrefixes这个变量,还需要再向每个节点发送一遍。

  如果把signPrefixes变为广播变量,就可以解决这个问题:

 #在Python中使用广播变量来查询国家
#查询RDD contactCounts中的呼号的对应位置,将呼号前缀读取为国家前缀来进行查询
signPrefixes = sc.broadcast(loadCallSignTable()) def processSignCount(sign_count,signPrefixes):
country = lookupCountry(sign_count[0],signPrefixes.value)
count = sign_count[1]
return (country,count) countryContactCounts = (contactCounts.map(processSignCount).reduceByKey((lambda x,y:x+y))) countryContactCounts.saveAsTextFile(outputDir +"/contries.txt")

  总结一下广播变量的过程:

  • 通过对一个类型T的对象调用SparkContext.broadcast创建一个Broadcast[T]对象。任何可序列化的对象都可以这么实现。
  • 通过value属性访问该对象的值
  • 变量只会发到各个节点一次,应作为只读值处理(修改这个值不会影响到别的节点)。 

广播的优化

  如果广播的值比较大,可以选择既快又好的序列化格式。Scala和Java API中默认使用Java序列化库,对于除基本类型的数组以外的任何对象都比较低效。我们可以使用spark.serializer属性选择另一个序列化库来优化序列化过程。(也可以使用reduce()方法为Python的pickle库自定义序列化)

基于分区进行操作

  两个函数:map() 和 foreach()

函数名 调用所提供的 返回的 对于RDD[T]的函数签名
mapPartitions() 该分区中元素的迭代器 返回的元素的迭代器 f:(Iterator[T])->Iterator[U]
mapPartitionsWithIndex() 分区序号,以及每个分区中的元素的迭代器 返回的元素的迭代器 f:(Int,Iterator[T])->Iterator[U]
foreachPartitions() 元素迭代器 f:(Iterator[T])->Unit

  示例:我们有一个在线的电台呼号数据,可以通过这个数据库查询日志中记录过的联系人呼号列表。

 #在Python中使用共享连接池
def processCallSigns(signs):
"""使用连接池查询呼号"""
#创建一个连接池
http = urllib3.PoolManager()
#与每条呼号记录相关的URL
urls = map(lambda x: "http://73s.com/qsos/%s.json" % x,signs)
#创建请求(非阻塞)
requests = map(lambda x:(x,http.request('GET',x)),urls)
#获取结果
result = map(lambda x:(x[0],json.loads(x[1].data)),requests)
#删除空的结果并返回
return filter(lambda x:x[1] is not None,result) def fetchCallSigns(input):
"""获取呼号"""
return input.mapPartitions(lambda callsigns:processCallSigns(callsigns)) contactsCountList = fetchCallSigns(validSigns)

  再举个例子说明一下mapPartitions()的功能:

 #在Python中不实用mapPartitions()求平均值
def combineCtrs(c1,c2):
return (c1[0]+c2[0],c1[1]+c2[1]) def basicAvg(nums):
"""计算平均值"""
nums.map(lambda num:(num,1)).reduce(combineCtrs) #在Python中使用mapPartitions()求平均值
def partitionCtr(nums):
"""计算分区的sumCounter"""
sumCount = [0,0]
for num in nums:
sumCount[0] +=num
sumCount[1] +=1
return [sumCount] def fastAvg(nums):
"""计算平均值"""
sumCount = nums.mapPartitions(partitionCtr).reduce(combineCtrs)
return sumCount[0]/float(sumCount[1])

数值RDD的操作

方法 含义

count()

RDD中的元素个数

mean()

元素的平均值

sum()

总和

max()

最大值

min()

最小值

variance()

元素的方差

sampleVariance()

采样的方差

stdev()

标准差

sampleStdev()

采样的标准差

  举例:从呼叫日志中移除距离过远的联系点

 #用Python移除异常值
#要把String类型的RDD转化为数字数据,这样才能使用统计函数并移除异常值
distanceNumerics = distances.map(lambda string :float(string))
stats = distanceNumerics.stats()
stddev = stdts.stdev()
mean =stats.mean()
reasonableDistances = distanceNumerics.filter(lambda x:math.fabs(x-mean) < 3 * stddev)
print reasonableDistances.collect()

  这三章的内容比较实用,在生产中也会有实际应用。下周更新第7-9章,主要讲Spark在集群上的运行、Spark调优与调试和Spark SQL。

【原】Learning Spark (Python版) 学习笔记(二)----键值对、数据读取与保存、共享特性的更多相关文章

  1. 【原】Learning Spark (Python版) 学习笔记(三)----工作原理、调优与Spark SQL

    周末的任务是更新Learning Spark系列第三篇,以为自己写不完了,但为了改正拖延症,还是得完成给自己定的任务啊 = =.这三章主要讲Spark的运行过程(本地+集群),性能调优以及Spark ...

  2. 【原】Learning Spark (Python版) 学习笔记(一)----RDD 基本概念与命令

    <Learning Spark>这本书算是Spark入门的必读书了,中文版是<Spark快速大数据分析>,不过豆瓣书评很有意思的是,英文原版评分7.4,评论都说入门而已深入不足 ...

  3. 【原】Learning Spark (Python版) 学习笔记(四)----Spark Sreaming与MLlib机器学习

    本来这篇是准备5.15更的,但是上周一直在忙签证和工作的事,没时间就推迟了,现在终于有时间来写写Learning Spark最后一部分内容了. 第10-11 章主要讲的是Spark Streaming ...

  4. Learning Spark (Python版) 学习笔记(一)----RDD 基本概念与命令

    <Learning Spark>这本书算是Spark入门的必读书了,中文版是<Spark快速大数据分析>,不过豆瓣书评很有意思的是,英文原版评分7.4,评论都说入门而已深入不足 ...

  5. 机器学习实战(Machine Learning in Action)学习笔记————09.利用PCA简化数据

    机器学习实战(Machine Learning in Action)学习笔记————09.利用PCA简化数据 关键字:PCA.主成分分析.降维作者:米仓山下时间:2018-11-15机器学习实战(Ma ...

  6. 机器学习实战(Machine Learning in Action)学习笔记————06.k-均值聚类算法(kMeans)学习笔记

    机器学习实战(Machine Learning in Action)学习笔记————06.k-均值聚类算法(kMeans)学习笔记 关键字:k-均值.kMeans.聚类.非监督学习作者:米仓山下时间: ...

  7. Learning Spark中文版--第四章--使用键值对(2)

    Actions Available on Pair RDDs (键值对RDD可用的action)   和transformation(转换)一样,键值对RDD也可以使用基础RDD上的action(开工 ...

  8. Learning Spark中文版--第四章--使用键值对(1)

      本章介绍了如何使用键值对RDD,Spark中很多操作都基于此数据类型.键值对RDD通常在聚合操作中使用,而且我们经常做一些初始的ETL(extract(提取),transform(转换)和load ...

  9. python cookbook第三版学习笔记二:字典

    一般来说字典中是一个键对应一个单值的映射,如果想一个键值映射多个值,那么就需要将这些值放到另外的容器中,比如列表或者集合. 比如d={'a':[1,2]} Collections中的defaultdi ...

随机推荐

  1. javascript创建对象的几种模式

    在js中有几种模式可以创建对象,通过对象操作所包含的属性与方法. 一般来说,构造函数名称的第一个字母为大写字母,非构造函数名称的第一个字母为小写字母,当然,构造函数与一般函数唯一的区别只是调用的方式不 ...

  2. 仿腾讯QQ竖直滑动导航菜单

    菜单就像qq软件的分组,鼠标经过自动显示相应组的内容. 效果体验网址:http://keleyi.com/a/bjad/nf86w2dv.htm 以下是源代码: <html> <he ...

  3. arcgis10.2.2桌面版具体的安装步骤过程

    先声明一下,这里的截图虽说是ArcGIS10.1版本的,但是安装步骤是对的,本人用ArcGIS10.2.2软件测试成功安装上 一.ArcGIS许可证管理器安装 1.在软件包文件夹license man ...

  4. Android 四大组件 与 MVC 架构模式

    作为一个刚从JAVA转过来的Android程序员总会思考android MVC是什么样的? 首先,我们必须得说Android追寻着MVC架构,那就得先说一下MVC是个啥东西! 总体而来说MVC不能说是 ...

  5. Solr实现Low Level查询解析(QParser)

    Solr实现Low Level查询解析(QParser) Solr基于Lucene提供了方便的查询解析和搜索服务器的功能,可以以插件的方式集成,非常容易的扩展我们自己需要的查询解析方式.其中,Solr ...

  6. 为什么基于TCP UDP服务的一些简单服务端口号都是奇数

    TCP是有NCP(网络控制协议)派生出来的,NCP是单工的,通信需要两个链接,所以会预留奇偶端口号.当TCP/UDP成为传输层协议,每个程序只是用一个端口号,且使用奇端口号.

  7. 关于 Java 数组的 12 个最佳方法

    1.  声明一个数组 String[] aArray = new String[5]; String[] bArray = {"a","b","c&q ...

  8. 2-3 Linux文件管理命令详解

    1. 复制文件 cp: copy 格式  SRCFILE         DEST 一个文件到一个文件 多个文件到一个目录 注,cd 后面什么都不加,是进入用户的家目录 如果目标文件不存在,则先创建文 ...

  9. MySQL server has gone away报错原因分析/

    在平时和开发的交流 以及 在论坛回答问题的或称中会发现这个问题被问及的频率非常高. 程序中报错: MySQL server has gone away 是什么意思? 如何避免? 因此,感觉有必要总结一 ...

  10. Python+selenium自动化脚本编辑过程中遇到的问题和小技巧

    应该也不算是问题和技巧,算是实践中学习到的Python,记录下,也不定时更新 1.通过截取url判断 实例: self.assertEqual(self.broswer.current_url[sel ...