spark的使用入门
- 使用notebook打开pyspark
PYSPARK_DRIVER_PYTHON=jupyter PYSPARK_DRIVER_PYTHON_OPTS=notebook ./bin/pyspark
- 用python写独立应用
初始化SparkContext
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。
<property>
<name>yarn.nodemanager.pmem-check-enabled</name>
<value>false</value>
</property>
<property>
<name>yarn.nodemanager.vmem-check-enabled</name>
<value>false</value>
</property>
驱动器节点
驱动器程序在Spark应用中有以下两种职责:
- 把用户程序转为任务
spark驱动器负责把用户程序转为多个物理执行的单元,这些单元也被称为任务。任务是Spark中最小的工作单元,用户程序通常要启动成百上千的独立任务。 - 为执行器节点调度任务
驱动器进程始终对应用中所有的执行节点有完整的记录。每个执行节点代表一个能够处理任务和存储RDD数据的进程。
Spark驱动器程序会更具当前的执行器节点集合,尝试把所有任务基于数据所在位置分配给合适的执行器进程。当任务执行时,执行器进程会把缓存数据存储起来,而驱动器进程同样会跟踪这些缓存数据的位置,并且利用这些位置来调度以后的任务,以尽量减少数据的网络传输。
执行器节点
Spark执行器节点是一种工作进程,负责在Spark作业中运行任务,任务间相互独立,执行器崩溃或异常,Spark应用也可正常运行。
执行器的两大作用:
- 它们负责组成Spark应用的任务,并将结果返回给驱动器进程
- 它们通过自身的块管理器为用户程序中要求缓存的RDD提供内存式存储。RDD式是直接缓存在执行器进程内的,因此任务可以在运行时充分利用缓存数据加速运算。
集群上运行Spark应用的过程总结:
(1) 用户通过spark-submit脚本提交应用。
(2)spark-submit脚本启动驱动器程序,调用用户定义的main()方法。
(3)驱动器程序与集群管理器通信,申请资源已启动执行器节点。
(4)集群管理器为驱动器程序启动执行器节点
(5)驱动器进程执行用户应用中的操作。根据程序中的所定义的对RDD的转化操作和行动操作,驱动器节点把工作以任务的形式发送到执行器进程
(6)任务在执行器程序中进行计算并保存结果。
(7)如果驱动器程序的main()方法退出,或者调用了SparkContext.stop(),驱动器程序会终止执行器操作,并且通过集群管理器释放资源。
pyspark快速入门:
一.RDD编程
RDD支持两种操作:转化操作和行动操作
- 转化操作
- 转化操作为惰性操作,只有在执行行动操作时,才开始执行转化操作。所以不应该将RDD看做放着特定数据的数据集,而是通过转化操作构建出来的、记录如何计算数据的指令列表。
- 若希望能多次使用同一个RDD,使用持久化存储:persist()
- 移除持久化的RDD:unpersist()
- 转化操作可以操作任意数量的输入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的笛卡尔积 |
- 行动操作
基本的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中的每个元素使用给定的函数 |
注意点:
- collect() 要求所有数据都必须能一同放入单台机器的内存中。
- take(n) 尝试只访问尽量少的分区,因此会得到一个不均衡的集合,返回元素的顺序与预期不一致。
- top() 可以通过提供自己的比较函数,来提取前几个元素
- foreach() 不将任何结果返回到驱动程序中,把数据发送到一个网络服务器中或数据库中。
二、键值对操作
- 例:使用第一个单词作为键创建出一个pairRDD
pairs = lines.map(lambda x: (x.split(" ")[0], x))
当用python从一个内存中的数据集创建pairRDD时,只需要对这个由二元组组成的集合调用SparkContext.parallelize()方法。
- pair RDD的转化操作
函数名 | 目的 |
---|---|
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。
- Pair RDD的行动操作
函数 | 描述 |
---|---|
countByKey() | 对每个键对应的元素分别计数 |
collectAsMap() | 将结果一映射表的形式返回,以便查询 |
lookup() | 返回给定键对应的所有值 |
三、数据读取与保存
1. 三类常见的数据源:
- 文件格式与文件系统
- Spark SQL中的结构化数据源 :Hive
- 数据库与键值存储:Hbase
2. 文件格式
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,并指定正确的压缩编解码器。
文件系统
1. 本地文件系统
rdd = sc.textFile(“file:///home/hadoop/text.txt”)
2. Amazon S3
要在Spark中访问S3数据,应该首先把你的S3访问凭据设置为AWS_ACCESS_KEY_ID和AWS_SECRET_ACCESS_KEY环境变量。然后以s3n://方式传入。
3. HDFS
指定路径为hdfs://master:port/path
Spark SQL中的结构化数据
1. Apacha Hive
from pyspark import HiveContext
hiveCtx = HiveContext(sc)
rows = hiveCtx.sql("SELECT name, age FROM user")
firstRow = rows.first()
print firstRow.name
2. JSON
若JSON数据记录间结构一致,则SparkSQL可自动推断出他们的结构信息,并将这些数据读取为记录。使用HiveContext.jsonFile方法来从整个文件中获取有row对象组成的RDD。也可将RDD注册成为一张表,然后从中选出特定的字段。
tweets = hiveCtx.json("tweets.json")
tweets.registerTempTable("tweets")
results = hiveCtx.sql("SELECT usr.name, text From tweets")
数据库
1. Mysql
2. HBase
Spark编程进阶
累加器
- 第一种共享变量。提供了将工作节点中的值聚合到驱动器程序中的简单语法。累加器的一个常见用途是在调试时对作业执行过程中的事件进行计数。
累加器的用法如下:
- 通过驱动器中调用SparkContext.accumulator(initialValue)方法,创建出存有初始值的累加器。返回值为org.apache.spark.Accumulator[T]对象。其中T是初始值initialValue的类型。
- Spark闭包里的执行器代码可以使用累加器+=方法增加累加器的值。
- 驱动器程序中可以调用累加器的value属性访问累加器的值。注意:工作节点上的任务不能访问累加器的值。
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只会把每个任务对各累加器的修改应用一次。对于在RDD转换操作中使用的累加器,就不能保证有这种效果
广播变量
第二种共享变量可以让程序高效的向所有工作节点发送一个较大的只读值。以供一个或多个Spark操作使用。
避免为每个任务发送造成的浪费,以及多次发送造成的浪费
广播变量的使用过程:
- 通过对一个类型T的对象调用SparkContext.broadcast创建出一个Broadcast[T]对象。任何可序列化的类型都可以被这么实现。
- 通过value属性访问该对象的值
- 变量只会被发到各个节点一次。应作为只读值处理。
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")