PySpark源码分析之AM端运行流程(Driver)

先验知识

前面文章 Spark源码分析之ApplicationMaster运行流程 我们介绍了Java|Scala程序在AM端的运行流程,本文介绍Python程序在AM端的运行流程,首先Client看提交命令:

spark-submit --master yarn \
--deploy-mode cluster \
--conf spark.pyspark.python=/root/miniconda2/bin/python \
--py-files 7fresh-sco-fw_demo.zip \
pi.py 100

注:上面--py-files参数只是为了演示依赖包的运行流程,pi.py 没有调用其内部任何py文件。
提交给集群的上下文参数:
在这里插入图片描述
由上图可以看出,启动AM的运行命令是:

{{JAVA_HOME}}/bin/java -server -Xmx1024m -Djava.io.tmpdir={{PWD}}/tmp -Dspark.yarn.app.container.log.dir=<LOG_DIR> org.apache.spark.deploy.yarn.ApplicationMaster --class 'org.apache.spark.deploy.PythonRunner' --primary-py-file pi.py --arg '100' --properties-file {{PWD}}/__spark_conf__/__spark_conf__.properties 1> <LOG_DIR>/stdout 2> <LOG_DIR>/stderr

AM入口类是org.apache.spark.deploy.yarn.ApplicationMaster,定义的的入口类--classorg.apache.spark.deploy.PythonRunner

PythonRunner启动Python过程

Spark源码分析之ApplicationMaster运行流程 中我们介绍了,AM启动后最终会在 org.apache.spark.deploy.yarn.ApplicationMaster 类的startUserApplication()函数中反射实例化--class类用于初始化SparkContext,针对PySpark程序的运行,在此函数首先判断如果是py,则会重新组织参数,运行提交命令中的org.apache.spark.deploy.PythonRunner类(注:如果用户依赖的第三方|自定义的Python依赖包,则通过launch_container.sh设置PYTHONPATH的方式传递,因此第二个参数是空,如上文提交任务生成的格式,例如:PythonRunner pi.py 空 100):
在这里插入图片描述
在这里插入图片描述
我们继续看 org.apache.spark.deploy.PythonRunner 类的main函数,代码分析见注释如下图:
在这里插入图片描述

基于Py4J的通信模型

一文弄懂PySpark原理与实践 一文中我们知道,为了不破坏 Spark 已有的运行时架构,Spark 在外围包装一层 Python API,借助 Py4j实现 Python 和 Java 的交互,进而实现通过 Python 编写 Spark 应用程序。其原理图下图:
在这里插入图片描述
Py4J提供了一套文本协议用来在tcp socket间传递命令,主要作用在Driver端(如上图红圈范围),而在executor端则是通过启动的pyspark.daemon后通过socket直接通信的。由上节我们知道PythonRunner在创建Python子进程时会把Py4J监听的端口写入到子进程的环境变量中,这样Python就知道从哪个端口访问JVM了,当然Python在创建JavaGateway时,也可以同时创建一个CallbackClient,实现JVM调用Python过程。默认情况下,PySpark Job是不会启动回调服务的,所有的交互都是 Python -> JVM模式,但在SparkStreaming中才会用到JVM -> Python的过程(本文不再重点讲解)。我们首先看一下Driver与Python的整体的运行流程图:
在这里插入图片描述
我们接上节,PythonRunner启动子Python进程运行 python pi.py 100 后,开始初始化SparkContext,如运行pi.py代码:

    spark = SparkSession\
        .builder\
        .appName("PythonPi")\
        .getOrCreate()

首先会依次调用session.pycontext.py 进行初始化SparkContext,其代码调用链为 SparkSession.Builder.getOrCreate() -> SparkContext.getOrCreate() -> SparkContext.__init__(),我们直接看SparkContext.__init__()函数:
在这里插入图片描述
首先我们看SparkContext._ensure_initialized()函数。
在这里插入图片描述
如上图,新启动一个Gateway赋值给 _gateway变量(JavaGateway对象)和 _jvm变量(JVMView对象),这样就可以通过这个_jvm变量来访问jvm中的Java对象和方法。下面我们分析 java_gateway.pylaunch_gateway() 函数,代码如下:

def launch_gateway(conf=None, popen_kwargs=None):
    if "PYSPARK_GATEWAY_PORT" in os.environ:
        gateway_port = int(os.environ["PYSPARK_GATEWAY_PORT"])
        gateway_secret = os.environ["PYSPARK_GATEWAY_SECRET"]
        # Process already exists
        proc = None

    ...
    # Connect to the gateway (or client server to pin the thread between JVM and Python)
    if os.environ.get("PYSPARK_PIN_THREAD", "false").lower() == "true":
        gateway = ClientServer(
            java_parameters=JavaParameters(
                port=gateway_port,
                auth_token=gateway_secret,
                auto_convert=True),
            python_parameters=PythonParameters(
                port=0,
                eager_load=False))
    else:
        gateway = JavaGateway(
            gateway_parameters=GatewayParameters(
                port=gateway_port,
                auth_token=gateway_secret,
                auto_convert=True))

    # Import the classes used by PySpark
    java_import(gateway.jvm, "org.apache.spark.SparkConf")
    java_import(gateway.jvm, "org.apache.spark.api.java.*")
    java_import(gateway.jvm, "org.apache.spark.api.python.*")
    java_import(gateway.jvm, "org.apache.spark.ml.python.*")
    java_import(gateway.jvm, "org.apache.spark.mllib.api.python.*")
    # TODO(davies): move into sql
    java_import(gateway.jvm, "org.apache.spark.sql.*")
    java_import(gateway.jvm, "org.apache.spark.sql.api.python.*")
    java_import(gateway.jvm, "org.apache.spark.sql.hive.*")
    java_import(gateway.jvm, "scala.Tuple2")

    return gateway

首先从环境变量中拿到环境变量PYSPARK_GATEWAY_PORT,这个就是我们在PythonRunner中设置的环境变量,然后启动一个JavaGateway同GatewayServer进行通讯,最后把Python Api中需要的Java|Scala的类引入引进来,完成了上面的工作后我们就可以真正的初始化SparkContext了
我们回到SparkContext.__init__(),分析如何_jsc变量的初始化(其中_jsc就是JVM中的SparkContext对象在Python中的影子)?

    def _do_init(self, master, appName, sparkHome, pyFiles, environment, batchSize, serializer,
                 conf, jsc, profiler_cls):
        ...
        # Create the Java SparkContext through Py4J
        self._jsc = jsc or self._initialize_context(self._conf._jconf)
        # Reset the SparkConf to the one actually used by the SparkContext in JVM.
        self._conf = SparkConf(_jconf=self._jsc.sc().conf())
        ...
     def _initialize_context(self, jconf):
        """
        Initialize SparkContext in function to allow subclass specific initialization
        """
        return self._jvm.JavaSparkContext(jconf)   

完成了SparkContext的初始化后,就能在业务代码实现自己的逻辑了,如示例pi.py中使用:
在这里插入图片描述

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值