pyspark运行加速方法思考(一)

pyspark工作原理

在这里插入图片描述
在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进程执行。语言层面的交互总体流程如下图所示,实线表示方法调用,虚线表示结果返回。

  1. driver: pyspark脚本和sparkContext的jvm使用py4j相互调用;
  2. 由于driver帮忙把spark算子封装好了,执行计划也生成了字节码,一般情况下不需要python进程参与;
  3. 仅当需要运行UDF(含lambda表达式形式)时,将它委托给python进程处理,此时JVM和python进程使用socket通信。

工作中的联想

pyspark可以把很多常见的运算封装到JVM中,但是显然对于很多定制化工作,需要写好代码封装到JVM中,实现UDF的调用,加速数据的处理工作。

案例借鉴

首先我们需要用scala重写一下UDF:

object UdfUtils extends java.io.Serializable {

  case class Idfa(idfa: String, idfv: String) {
    private def coalesce(V: String, defV: String) =
      if (V == null) defV else V

    override def toString: String = coalesce(idfa, "-1") + "#" + coalesce(idfv, "-1")
  }

  def str2idfa(txt: String): Option[String] = {
    try {
      val decodeTxt: Array[Byte] = Base64.getDecoder.decode(txt)
      // TODO 省略一些处理逻辑
      val str = "after_some_time"
      val gson = new Gson()
      val reader = new JsonReader(new StringReader(str))
      reader.setLenient(true)
      val idfaType: Type = new TypeToken[Idfa]() {}.getType
      Some(gson.fromJson(reader, idfaType).toString)
    }
    catch {
      case e: Throwable =>
        println(txt)
        e.printStackTrace()
        None
    }
  }
  // 关键是这里把普通函数转成UDF:
  def str2idfaUDF: UserDefinedFunction = udf(str2idfa _)

然后在pyspark脚本里调用jar包中的UDF:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
from pytoolkit import TDWSQLProvider, TDWUtil, TDWProvider
from pyspark import SparkContext, SQLContext
from pyspark.sql import SparkSession, Row
from pyspark.sql.types import StructType, LongType, StringType, StructField, IntegerType
from pyspark.sql.functions import udf, struct, array
from pyspark.sql.column import Column
from pyspark.sql.column import _to_java_column
from pyspark.sql.column import _to_seq
from pyspark.sql.functions import col

def str2idfa(col):
    _str2idfa = sc._jvm.com.tencent.kandian.utils.UdfUtils.str2idfaUDF()
    return Column(_str2idfa.apply(_to_seq(sc, [col], _to_java_column)))


spark = SparkSession.builder.appName(app_name).getOrCreate()
sc = spark.sparkContext
if __name__ == '__main__':
    in_provider = TDWSQLProvider(spark, user=user, passwd=passwd, db=db_name)
    in_df = in_provider.table('t_dw_dcxxxx', ['p_2019042100'])  # 分区数组
    print(in_df.columns)
    in_df.createOrReplaceTempView("t1")
    out_t1 = in_df.select(col('uin')
                          , str2idfa(col("value"))) # 直接使用scala的udf,节省43%时间,减少两个transform
    print(out_t1.columns)
    print(out_t1.take(10))

其中_jvm变量是sparkContext中JVMView对象的名字,此外sc中还有_gateway变量以连接JVM中的GatawayServer。
提交时,在tesla上的配置spark-confjar包路径:

spark.driver.extraClassPath=pipe-udf-1.0-SNAPSHOT-jar-with-dependencies.jar
spark.executor.extraClassPath=pipe-udf-1.0-SNAPSHOT-jar-with-dependencies.jar

总结

  • 在pyspark中尽量使用spark算子和spark-sql,同时尽量将UDF(含lambda表达式形式)封装到一个地方减少JVM和python脚本的交互。
  • 可以把UDF部分用scala重写打包成jar包,其他部分则保持python脚本以获得不用编译随时修改的灵活性,以兼顾性能和开发效率
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值