PYSPARK_DRIVER_PYTHON=jupyter PYSPARK_DRIVER_PYTHON_OPTS=notebook ./bin/pyspark
from pyspark import SparkConf, SparkContext
Master = yarn # 集群URL:可选local, yarn-client
conf = SparkConf().SetMaster(Master).setAppName("My app")
...
# deploy-mode: 可选client, cluster,指定yarn的模式
spark-submit --master yarn --deploy-mode client test.py
注意点:
在以上运行过程中出现错误:
Current usage: 80.2 MB of 1 GB physical memory used; 2.2 GB of 2.1 GB virtual memory used. Killing container.
解决方法:
在yarn-site.xml中添加以下代码即可,记得要重启hadoop和spark。
yarn.nodemanager.pmem-check-enabled</name>
false</value>
</property>
yarn.nodemanager.vmem-check-enabled</name>
false</value>
</property>
驱动器程序在Spark应用中有以下两种职责:
Spark执行器节点是一种工作进程,负责在Spark作业中运行任务,任务间相互独立,执行器崩溃或异常,Spark应用也可正常运行。
执行器的两大作用:
集群上运行Spark应用的过程总结:
(1) 用户通过spark-submit脚本提交应用。
(2)spark-submit脚本启动驱动器程序,调用用户定义的main()方法。
(3)驱动器程序与集群管理器通信,申请资源已启动执行器节点。
(4)集群管理器为驱动器程序启动执行器节点
(5)驱动器进程执行用户应用中的操作。根据程序中的所定义的对RDD的转化操作和行动操作,驱动器节点把工作以任务的形式发送到执行器进程
(6)任务在执行器程序中进行计算并保存结果。
(7)如果驱动器程序的main()方法退出,或者调用了SparkContext.stop(),驱动器程序会终止执行器操作,并且通过集群管理器释放资源。
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的笛卡尔积 |
函数名 | 目的 |
---|---|
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中的每个元素使用给定的函数 |
注意点:
pairs = lines.map(lambda x: (x.split(" ")[0], x))
当用python从一个内存中的数据集创建pairRDD时,只需要对这个由二元组组成的集合调用SparkContext.parallelize()方法。
函数名 | 目的 |
---|---|
reduceByKey(func) | 合并具有相同键的值 |
groupByKey() | 对具有相同键的值进行分组 |
combineByKey( createCombiner, mergeValue, mergeCombiners, partitioner) | 使用不同的返回类型合并具有相同键的值 |
mapValues(func) | 对pairRDD中的每个值应用一个函数而不改变键,功能类型于:map{ case (x, y) : (x, func(y))} |
flatMapValues(func) | 对pairRDD中的每个值应用一个返回迭代器的函数,然后对返回的每个元素都生成一个对应原键的键值对记录,通常用于符号化。 |
keys() | 返回一个仅包含键的RDD |
values() | 返回一个仅包含值得RDD |
sortByKey() | 返回一个根据键排序的RDD |
sutractByKey() | 删掉RDD中间与other RDD中的键相同的元素 |
join() | 对两个RDD进行内连接 |
rightOuterJoin() | 对两个RDD进行连接操作,确保第一个RDD的键必须存在 |
leftOuterJoin() | 对两个RDD进行连接操作,确保第二个RDD的键必须存在 |
cogroup() | 将两个RDD中拥有相同键的数据分组到一起。 |
例:使用reduceByKey()和mapValues()来计算每个键对应的平均值。
rdd.mapValues(lambda x: (x, 1).reduceByKey(lambda x, y: (x[0] + y[0], x[1] + y[1]))
(1). 例:实现单词计数
rdd = sc.textFile("s3://...")
words = rdd.flatMap(lambda x: x.split(" "))
resault = words.map(lambda x: (x, 1).reduceByKey(lambda x, y: x+y)
(2). 例:使用combineByKey求每个键对应的平均值
sumCount = nums.combineByKey((lambda x: (x, 1)), #createCombiner:在每个分区中第一次出现各个键时,创建那个键对应的累加器的初始值
(lambda x, y: (x[0] + y, x[1] + 1)), #mergeValue():在当前分区,若是已遇到的键,将该键的累加器对应的当前值与这个新的值进行合并
(lambda x, y: (x[0] + y[0], x[1] + y[1]))) # mergeCombiner:将各个分区的结果进行合并
sumCount.map(lambda key, xy: (key, xy[0]/xy[1])).collectAsMap()
(3) 并行度调优
通过调整分区进行并行度调优,默认只在分组操作和聚合操作中,此类操作大多可接受第二个参数指定RDD的分区数。对于其他操作,可使用 repartition() 函数或优化版的 coalesce()来改变RDD分区,但要确保调用 coalesce() 时将RDD合并到比 rdd.getNumPartiions() 查看的RDD分区数更少的分区中。
(4). groupByKey()和cogroup()
groupByKey可以用于未成对的数据上,也可以根据除键相同以外的条件进行分组,可以接受一个函数,对源RDD中的每个元素使用该函数,将返回结果作为键再进行分组。
cogroup()可对多个共享同一个键的RDD进行分组。除此之外,还可以用于实现连接操作。用来求键的交集,能同时应用于三个及三个以上的RDD。
函数 | 描述 |
---|---|
countByKey() | 对每个键对应的元素分别计数 |
collectAsMap() | 将结果一映射表的形式返回,以便查询 |
lookup() | 返回给定键对应的所有值 |
2.1 文本文件
(1)读取文本文件:textFile(),wholeTextFiles()
使用文件路径作为参数调用textFile()函数,可输入多个文件,使用包含数据所有部分的目录或使用通配字符。多个文件作为一个RDD。
>>> files = sc.textFile("/user/hadoop/file*.txt")
>>> files.collect()
['hello abc', 'hello cad', 'hello kitty', 'hello mom', 'hello youku', 'hi nihao', 'hi tengxu', 'hi yy', 'hi youyou']
(2)保存文本文件:saveAsTextFile(outputfile)
outputfile是一个目录,在该目录下输出多个文件,这样就可并行输出了,注意使用RDD的输出方式。
2.2 JSON
(1)读取JSON
import json
data = input.map(lambda x: json.loads(x))
处理格式不正确的记录可能会引起严重的问题,如果选择跳过格式不正确的数据,应该尝试使用累加器来跟踪错误的个数。
(2) 保存JSON
将由结构化数据组成的RDD转为字符串RDD,然后使用Spark的文本文件API写出去。
(data.filter(lambda x: x["lovesPandas"]).map(lambda x: json.dumps(x)).saveAsTextFile(outputFile))
2.3 逗号分隔值与制表符分隔值
(1) 读取CSV(逗号分隔值)
如果CSV的所有数据字符均没有包含换行符:textFile()
import csv
import StringIO
...
def loadRecord(line):
"解析一行CSV记录"
input StringIO.StringIO(line)
reader = csv.DicReader(input, fieldnames=["names", "favouriteAnimal"])
return reader.next()
input = sc.textFile(inputFile).map(loadRecord)
如果在字段中嵌有换行符,就需要完整读入每个文件,然后解析各段。
def loadRecords(fileNameContents):
"""读取给定文件中的所有记录"""
input = StringIO.StringIO(fileNameContents[1])
reader = csv.DictReader(inputm fieldnames=["name", "favoriteAnimal"])
return reader
fullFileData = sc.wholeTextFiles(inputFile).flatMap(loadRecords)
(2) 保存CSV
def writeRecords(records):
"""写出一些CSV记录"""
output = StringIO.StringIO()
writer = scv.DictWriter(output.fieldnames=["name","favoriteAnimals"])
for record in records:
writer.writer(record)
return [output.getvalue()]
pandaLovers.mapPartitions()
2.4 SequenceFile
SequeceFile是有没有相对关系结构的键值对文件组成的常用Hadoop格式。
Spark的Python API只能将Hadoop中存在的基本Writable类型转为Python类型,并尽量基于可用的getter方法处理别的类型。
(1).读取SequenceFile
sequenceFile(path, keyclass, valueclass, minPartitions)
data = sc.sequenceFile(inFile, 'org.apache.hadoop.io.Text','org.apache.hadoop.io.IntWritable')
(2).保存SequenceFile
python方法未知
2.5 对象文件
对象文件在Python中无法使用,可用Pickle序列化库替代。
(1). 读取文件:pickleFile()
(2). 保存文件:saveAsPickleFile()
2.6 文件压缩
尽管Spark的textFile()方法可以处理压缩过的输入,但即使输入数据被以可分隔读取的方式压缩,Spark也不会打开splittable。因此,如果你要读取单个压缩过的输入,最好不要考虑使用Spark的封装,而是使用newAPIHadoopFile或者hadoopFile,并指定正确的压缩编解码器。
rdd = sc.textFile(“file:///home/hadoop/text.txt”)
要在Spark中访问S3数据,应该首先把你的S3访问凭据设置为AWS_ACCESS_KEY_ID和AWS_SECRET_ACCESS_KEY环境变量。然后以s3n://方式传入。
指定路径为hdfs://master:port/path
from pyspark import HiveContext
hiveCtx = HiveContext(sc)
rows = hiveCtx.sql("SELECT name, age FROM user")
firstRow = rows.first()
print firstRow.name
若JSON数据记录间结构一致,则SparkSQL可自动推断出他们的结构信息,并将这些数据读取为记录。使用HiveContext.jsonFile方法来从整个文件中获取有row对象组成的RDD。也可将RDD注册成为一张表,然后从中选出特定的字段。
tweets = hiveCtx.json("tweets.json")
tweets.registerTempTable("tweets")
results = hiveCtx.sql("SELECT usr.name, text From tweets")
validSignCount = sc.accumulator(0)
invalidSignCount = sc.accumulator(0)
def validateSigh(sigh):
global validSighCount, invalidSighCount
if re.math(r"\A\d?[a-zA-Z]{1,2}[a-zA-Z]{1,3}\Z", sign):
validSighCount += 1
return True
else:
invalidSighCount += 1
return False
validSigns = callSighs.filter(validateSign)
contactCount = validSign.map(lambda sign: (sign,1)).reduceByKey(lambda (x, y): x+y)
contactCount.count()
if invalidSignCount.value < 0.1*validSignCount.value:
contactCount.saveAsTextFile(outputDir + "/contactCount")
else:
print "Too many errors: %d in %d" % (invalidSignCount.value, validSignCount.value)
第二种共享变量可以让程序高效的向所有工作节点发送一个较大的只读值。以供一个或多个Spark操作使用。
避免为每个任务发送造成的浪费,以及多次发送造成的浪费
广播变量的使用过程:
signPrefixes = sc.broadcast(loadCallSignTable())
def processSignCount(sign_count, signPrefixes):
country = lookupCountry(sign_count[0], signPrefixes.value)
count = sign_count[1]
return (country, vount)
countryContactCounts = (contactCounts.
map(processSignCount).
reduceByKey((lambda x,y: x+y)))
countryContactCounts.saveAsTextFile(outputDir + "/countries.txt")