原文作者:李海强,来自平安银行零售大数据团队
前言
Spark是一个开源的通用分布式计算框架,支持海量离线数据处理、实时计算、机器学习、图计算,结合大数据场景,在各个领域都有广泛的应用。Spark支持多种开发语言,包括Python、Java、Scala、R,上手容易。其中,Python因为入门简单、开发效率高(人生苦短,我用Python),广受大数据工程师喜欢,本文主要探讨Pyspark的工作原理。
环境准备
因为我的环境是Mac,所以本文一切以Mac环境为前提,不过其它环境过车过都是差不多的。
Spark环境
首先下载安装Anaconda
https://www.jetbrains.com/idea/download/#section=mac,
选择Python 3.7。
Anaconda安装完之后,开一个终端,执行如下命令安装Pyspark和Openjdk,然后启动Jupyterlab。
- 创建一个虚拟Python环境,名字是test,避免影响Anaconda原始环境
% conda create --clone base -n test
% source activate test
- 安装Pyspark和Openjdk
% conda install pyspark=2.4.4
% conda install openjdk
- 安装并启动Jupyterlab
% conda install jupyterlab
% jupyter-lab
到此会启动一个基于浏览器的开发环境,可用于编写、调试Python代码。
阅读Spark代码环境
Spark本身是用Scala、Java、Python开发的,建议安装IntelliJ IDEA
https://www.jetbrains.com/idea/download/#section=mac
安装完IDEA,通过下面的命令下载Spark-2.4.4的代码。
% git clone https://github.com/apache/spark.git
% cd spark
% git checkout v2.4.4
代码下载完之后,打开IEDA,选择New->Project from existing sources,新建一个项目,IDEA会扫描整个项目、下载依赖,完成之后就可以阅读代码了。
深入Pyspark
Pyspark用法
在学习Pyspark的工作原理之前,我们先看看Pyspark是怎么用的,先看一段代码。代码很简单,首先创建spark session,然后从csv文件创建dataframe,最后通过rdd的map算子转换数据形式。中间利用了自定义函数test来转换输入数据,test函数的输入数据是一行数据。
from pyspark.sql import SparkSession
from pyspark.sql import Row
# 创建spark session
spark = SparkSession \
.builder \
.appName("pyspark demo") \
.getOrCreate()
# 从csv文件创建dataframe
df = spark.read.csv("stock.csv", header=True)
# 自定义分布式函数,将输入行转成另外一种形式
def test(r):
return repr(r)
# dataframe转成RDD,通过map转换数据形式,最后获取10条数据
df.rdd.map(lambda r: test(r)).take(10)
通过在Jupyterlab里面启动spark session之后,我们来看一下相关的进程父子关系。05920是Jupyterlab进程,我启动一个Python kernel,进程05964。然后启动spark session,这是一个Java进程,ID是06450。同时Spark java进程启动了一个Python守护进程,这个进程是处理PythonRDD数据的。因为我起的Spark是local模式,所以只有一个Spark进程和一个Python进程。如果是yarn模式,每一个executor都会启动一个Python进程,PythonRDD在Python守护进程里处理然后返回结果给Spark Task线程。
| | \-+= 05920 haiqiangli /Users/haiqiangli/anaconda3/envs/ml/bin/python3.7 /Users/haiqiangli/anaconda3/envs/ml/bin/jupyter-lab
| | \-+= 05964 haiqiangli /Users/haiqiangli/anaconda3/envs/ml/bin/python -m ipykernel_launcher -f /Users/haiqiangli/Library/Jupyter/runtime/kernel-62a08e01-a4c7-4fe6-b92f-621e9967197e.json
| | \-+- 06450 haiqiangli /Users/haiqiangli/anaconda3/envs/ml/jre/bin/java -cp /Users/haiqiangli/anaconda3/envs/ml/lib/python3.7/site-packages/pyspark/conf:/Users/haiqiangli/anaconda3/envs/ml/lib/python3.7/site-packages/pyspark/jars/* -Xmx1g org.
| | \--= 06750 haiqiangli python -m pyspark.daemon
PythonRDD实现
我们从这段代码开始分析,先看df.rdd,代码在pyspark/sql/dataframe.py。
df.rdd.map(lambda r: test(r)).take(10)
jrdd是通过py4j调用Java代码将Spark driver内部当前这个dataframe转成Python rdd,类RDD是Python rdd的封装,我们看一下Python rdd的定义,代码在pyspark/rdd.py。
@property
@since(1.3)
def rdd(self):
"""Returns the content as an :class:`pyspark.RDD` of :class:`Row`.
"""
if self._lazy_rdd is None:
jrdd = self._jdf.javaToPython()
self._lazy_rdd = RDD(jrdd, self.sql_ctx._sc, BatchedSerializer(PickleSerializer()))
return self._lazy_rdd
class RDD(object):
"""
A Resilient Distributed Dataset (RDD), the basic abstraction in Spark.
Represents an immutable, partitioned collection of elements that can be
operated on in parallel.
"""
def __init__(self, jrdd, ctx, jrdd_deserializer=AutoBatchedSerializer(PickleSerializer())):
self._jrdd = jrdd
self.is_cached = False
self.is_checkpointed = False
self.ctx = ctx
self._jrdd_deserializer = jrdd_deserializer
self._id = jrdd.id()
self.partitioner = None
.