首先我们先回顾下Spark的基本运行时架构,如下图所示,其中橙色部分表示为JVM,Spark应用程序运行时主要分为Driver和Executor,Driver负载总体调度及UI展示,Executor负责Task运行,Spark可以部署在多种资源管理系统中,例如Yarn、Mesos等,同时Spark自身也实现了一种简单的Standalone(独立部署)资源管理系统,可以不用借助其他资源管理系统即可运行。
更多细节请参考 Spark Scheduler内部原理剖析。
用户的Spark应用程序运行在Driver上(某种程度上说,用户的程序就是Spark Driver程序),经过Spark调度封装成一个个Task,再将这些Task信息发给Executor执行,Task信息包括代码逻辑以及数据信息,Executor不直接运行用户的代码。
为了不破坏Spark已有的运行时架构,Spark在外围包装一层Python API,借助Py4j实现Python和Java的交互,进而实现通过Python编写Spark应用程序,其运行时架构如下图所示。
其中白色部分是新增的Python进程,在Driver端,通过Py4j实现在Python中调用Java的方法,即将用户写的PySpark程序”映射”到JVM中,例如,用户在PySpark中实例化一个Python的SparkContext对象,最终会在JVM中实例化Scala的SparkContext对象;在Executor端,则不需要借助Py4j,因为Executor端运行的Task逻辑是由Driver发过来的,那是序列化后的字节码,虽然里面可能包含有用户定义的Python函数或Lambda表达式,Py4j并不能实现在Java里调用Python的方法,为了能在Executor端运行用户定义的Python函数或Lambda表达式,则需要为每个Task单独启一个Python进程,通过socket通信方式将Python函数或Lambda表达式发给Python进程执行。语言层面的交互总体流程如下图所示,实线表示方法调用,虚线表示结果返回。
PySpark官方文档:https://spark.apache.org/docs/latest/api/python/index.html
pyspark编程指南(英文):https://www.datacamp.com/community/tutorials/apache-spark-python#PySpark
备忘清单:https://s3.amazonaws.com/assets.datacamp.com/blog_assets/PySpark_Cheat_Sheet_Python.pdf
spark-submit提交方式官网:http://spark.apache.org/docs/latest/submitting-applications.html
spark-submit \
--master yarn \
--deploy-mode client \
--num-executors 10 \
--executor-memory 10g \
--executor-cores 8 \
--driver-memory 10g \
--conf spark.pyspark.python=python3 \
--conf spark.pyspark.driver.python=python3 \
--py-files depend.zip \
demo.py 2020-08-23
说明:
1、depend.zip是demo.py的依赖包,注:depend.zip不包含demo.py
2、demo.py中可以直接使用import depend.xx.xx 或 from depend.xx.xx import xx类似语句引入依赖包
3、上述示例中的2020-08-23是传给demo.py的参数
参考:
pyspark spark-submit 集群提交任务以及引入虚拟环境依赖包攻略:https://www.cnblogs.com/piperck/p/10121097.html
# -*- coding: utf-8 -*-
import sys
import os
import datetime
from pyspark import SparkConf,SparkContext
sc = SparkConf().setAppName("wordcount")
spark = SparkContext(conf=sc)
text_file = spark.textFile("hdfs://examples/pyspark/words.txt")
word_cnt_rdd = text_file.flatMap(lambda line : line.split(' ')).map(lambda word : (word, 1)).reduceByKey(lambda x, y: x + y)
word_cnt_rdd.saveAsTextFile('hdfs://user/wordcount_result')
#spark-cluster-mode
./spark-submit \
--verbose \
--master yarn \
--deploy-mode cluster \
--num-executors 10 \
--executor-cores 1 \
--executor-memory 8G \
--driver-memory 4G \
--conf spark.pyspark.python=python3 \
wordcount.py
#spark-client-mode
./spark-submit \
--verbose \
--master yarn \
--deploy-mode client \
--num-executors 10 \
--executor-cores 1 \
--executor-memory 8G \
--driver-memory 4G \
--conf spark.pyspark.python=python3 \
--conf spark.pyspark.driver.python=python3 \
wordcount.py
flatMap有着一对多的表现,输入一输出多。并且会将每一个输入对应的多个输出整合成一个大的集合,当然不用担心这个集合会超出内存的范围,因为spark会自觉地将过多的内容溢写到磁盘。当然如果对运行的机器的内存有着足够的信心,也可以将内容存储到内存中。
用同样的方法来展示map操作,与flatMap不同的是,map通常是一对一,即输入一个,对应输出一个。但是输出的结果可以是一个元组,一个元组则可能包含多个数据,但是一个元组是一个整体,因此算是一个元素。
educe和reduceByKey是spark中使用地非常频繁的。那么reduce和reduceBykey的区别在哪呢?
reduce处理数据时有着一对一的特性,而reduceByKey则有着多对一的特性。比如reduce中会把数据集合中每一个元素都处理一次,并且每一个元素都对应着一个输出。而reduceByKey则不同,它会把所有key相同的值处理并且进行归并,其中归并的方法可以自己定义。
在单词统计中,我们采用的就是reduceByKey,对于每一个单词我们设置成一个键值对(key,value),我们把单词作为key,即key=word,而value=1,因为遍历过程中,每个单词的出现一次,则标注1。那么reduceByKey则会把key相同的进行归并,然后根据我们定义的归并方法即对value进行累加处理,最后得到每个单词出现的次数。而reduce则没有相同Key归并的操作,而是将所有值统一归并,一并处理。
参考:
http://sharkdtu.com/posts/pyspark-internal.html