PySpark任务开发-全场景配置参考示例

前言

示例中基于spark版本:2.4.5

spark3.0后的 Dataframe使用语法与2.x版本差异较大,需要注意查看官方文档
且不同版本spark需要选用对应的spark-mongo-connector

一、示例说明

1.1 示例场景说明

  • 使用自定义的python虚拟环境提供项目第三方依赖

  • 任务执行非单一脚本文件,存在导包项目结构

  • 使用spark-mongo-connetor直接连接MongoDB数据库读取为Dataframe处理

  • 操作Hdfs

1.2 示例spark任务提交参数预览

--master yarn \
--deploy-mode cluster \
--driver-cores 1 \
--driver-memory 2G \
--num-executors 4 \
--executor-cores 4 \
--executor-memory 8G \
--conf spark.shuffle.consolidateFiles=True \
--conf spark.shuffle.partitions=64 \
--conf spark.pyspark.driver.python=./pyspark_venv/bin/python \
--conf spark.pyspark.python=./pyspark_venv/bin/python \
--conf spark.yarn.dist.archives=hdfs://xxxt:9000/home/user1/venv/pyspark_venv.tar.gz#pyspark_venv \
--py-files make_intelligence_package.zip \
--packages org.mongodb.spark:mongo-spark-connector_2.11:2.4.2

二、 如何在Spark任务中使用自定义的Python虚拟环境

  • 参考文档:https://databricks.com/blog/2020/12/22/how-to-manage-python-dependencies-in-pyspark.html

2.1 使用Anaconda在本地打包虚拟环境

2.1.1 安装Anaconda

注:虚拟环境需使用linux环境进行安装及打包

  • anaconda安装包地址:https://repo.anaconda.com/archive/

  • 安装过程: ;可参考:https://blog.csdn.net/wyf2017/article/details/118676765

2.1.2 创建并打包Spark项目所需的虚拟环境

  • 创建虚拟环境

    conda create -n py37 python=3.7 conda-pack

  • 进入虚拟环境

    conda activate py37

  • 安装pyspark项目依赖

    pip install -r requirements.txt

  • 打包虚拟环境

    conda-pack -f -o pyspark_conda_env.tar.gz

2.2 上传虚拟环境文件到HDFS

  • 若在集群中使用spark,可将虚拟环境压缩包上传到Hdfs

    hadoop fs -put ./pyspark_conda_env.tar.gz ${HDFS_PATH}

  • 若是本地单机模式,则可放在本机任意位置进行引用

2.3 spark任务指定Python虚拟环境

spark项目所需使用的虚拟环境压缩包已经准备就绪,则只需要在提交任务时增加相应的配置参数即可使用指定的python环境运行spark任务

--conf spark.pyspark.driver.python=./pyspark_venv/bin/python \
--conf spark.pyspark.python=./pyspark_venv/bin/python \
--conf spark.yarn.dist.archives=hdfs://xxxx:9000/home/user1/venv/pyspark_venv.tar.gz#pyspark_venv \

参数示意:

  • spark.yarn.dist.archives: 它会自动上载并解压文件到#后面指定的目录名中,解压后的目录会存在每个执行器的working directory中。

  • spark.pyspark.python: 指定spark执行器使用的python路径,该路径为虚拟环境加压后的python相对路径。

  • spark.pyspark.driver.python: 指定spark驱动器使用的python路径,该路径为虚拟环境加压后的python相对路径。

三、 如何在Spark任务脚本中 import 项目模块依赖

  • 参考文档: https://janetvn.medium.com/how-to-add-multiple-python-custom-modules-to-spark-job-6a8b943cdbbc

3.1 package包结构说明

  • 项目代码结构示例:
    package根目录必须创建__init__.py文件,将整个目录作为依赖包

example_package
├──__init__.py
├── script1.py
├── script2.py
├── sub_package1
│ ├── __init__.py
│ └── script3.py
└── sub_package2
├── __init__.py
└── script4.py

  • 假如项目结构如上所示,若使用该package作为依赖文件,则你可以在pyspark执行脚本中进行导包操作,例如:
from sub_package1.script3 import *
from sub_package2.script4 import *
from script1 import *
...

3.2 打包package

注:打包需进入到package目录中进行,不要包含外层文件夹

cd example_package
zip -r example_package.zip .

即在本地得到example_package.zip压缩文件

3.3 Spark任务指定依赖模块

  • 若在集群中使用spark,可将依赖module压缩包先上传到Hdfs再在spark提交配置中引用绝对路径

    # 上传依赖压缩包到hdfs
    hadoop fs -put ./example_package.zip /home/user1
    

    在spark任务提交时指定参数--py-files,并使用hdfs文件绝对路径
    --py-files hdfs://xxxx:9000/home/user1/example_package.zip

  • 若是本地单机模式,则可放在本机任意位置进行引用
    --py-files ./example_package.zip

参数释义:

  • --py-files: 指定依赖的python文件到执行器的当前工作目录,若为压缩文件将会自动解压文件到执行器的当前工作目录,解压后不包含外层文件夹。

四、 如何在Spark任务中使用mongo-spark-connector

  • 参考文档: https://www.mongodb.com/docs/spark-connector/v2.4/

    注意: 需要参考官网说明对照使用的spark版本选择合适的connector 版本

4.1 提交spark任务时指定--package参数

提交spark任务时指定package参数, 需指定对应spark版本的mongo-spark-connector,

使用spark版本为2.4.5,选用如下connector版本指定参数即可,运行时将会自动下载依赖包:

--packages org.mongodb.spark:mongo-spark-connector_2.11:2.4.2

4.2 python代码中使用mongo-connector

  • 参考文档: https://www.mongodb.com/docs/spark-connector/v2.4/python-api/

在python代码中配置使用mongo-spark-connector进行数据处理示例:

from pyspark.sql.session import SparkSession
from pyspark.sql.functions import col, udf
from pyspark.sql.types import *

# 带登陆认证的mongouri,指定默认的数据库与集合
mongo_uri = "mongodb://username:[email protected]:27017/db.coll?authSource=admin"

# 指定spark-mongo-connector jar包版本
spark_mongo_connector = "org.mongodb.spark:mongo-spark-connector_2.11:2.4.2"


spark = SparkSession.builder.\
            appName("app_name").\
            config("spark.mongodb.input.uri", mongo_uri). \  # 输入的mongo uri数据源
            config('spark.jars.packages', spark_mongo_connector). \
            getOrCreate()

# 读取数据库数据为spark Dataframe
spark_df = spark.read.format("com.mongodb.spark.sql.DefaultSource").load()

五、pyspark任务提交

使用spark-submit对脚本python文件进行提交,执行任务

${SPARK_PATH}/bin/spark-submit \
--master yarn \
--deploy-mode cluster \
--driver-cores 1 \
--driver-memory 2G \
--num-executors 4 \
--executor-cores 4 \
--executor-memory 8G \
--queue ${YARN_QUEUE} \
--conf spark.shuffle.consolidateFiles=True \
--conf spark.shuffle.partitions=64 \
--conf spark.default.parallelism=150 \
--conf spark.pyspark.driver.python=./pyspark_venv/bin/python \
--conf spark.pyspark.python=./pyspark_venv/bin/python \
--conf spark.yarn.dist.archives=hdfs://xxxt:9000/home/user1/venv/pyspark_venv.tar.gz#pyspark_venv \
--py-files make_intelligence_package.zip \
--packages org.mongodb.spark:mongo-spark-connector_2.11:2.4.2 \
spark_task.py

六、pyspark Datframe常见数据处理方法

  • 参考文档: https://spark.apache.org/docs/2.4.5/sql-getting-started.html

    注:spark Dataframe的API参考官方文档,spark 3.0前后API函数存在较大差异,需要注意。

6.1 pyspark Dataframe数据处理代码示例

示例代码包含以下常见场景:

  • mongo-spark-connector读取数据

  • schema数据结构格式化

  • udf处理函数

  • filter|drop|withcolumns等数据处理方法

  • 输出单个结果文件hdfs

from pyspark.sql.session import SparkSession
from pyspark.sql.functions import col, udf
from pyspark.sql.types import *


# 带登陆认证的mongouri,指定默认的数据库与集合
mongo_uri = "mongodb://username:[email protected]:27018/db.coll?authSource=admin"

# spark-mongo-connector jar包版本
spark_mongo_connector = "org.mongodb.spark:mongo-spark-connector_2.11:2.4.2"

spark = SparkSession.builder.\
            appName("app_name").\
            config("spark.mongodb.input.uri", mongo_uri). \
            config('spark.jars.packages', spark_mongo_connector). \
            getOrCreate()

# 数据库数据字段格式显式指定,也可不指定,将使用自动推断
DATA_SCHEMA = StructType([
    StructField("ip", StringType(), True),
    StructField("count", StringType(), True),
    StructField("update_time", IntegerType(), True)
])

# 读取数据库数据为spark Dataframe
spark_df = spark.read.format("com.mongodb.spark.sql.DefaultSource").load(schema=DATA_SCHEMA)

# 数据处理
# 定义udf处理函数,对列数据进行处理,类似于pandas中的apply
udf_count = udf(lambda count: 1 if count > 1000 else 2, returnType=IntegerType())

result_df= spark_df. \
        select("ip", "count"). \  # 选择保留列名
        filter(~col("ip").isin(["localhost", "127.0.0.1"])). \  # 过滤ip在数组中的行
        drop_duplicates(subset=["ip"]). \  # 删除ip列中重复数据的行
        withColumn("block_impact", udf_count("count")).\  # 创建新列block_impact,填充值为udf函数处理count列数据后的对应返回值
        drop("count")  # 删除列count

# 输出单文件数据到hdfs
result_df.repartition(1).write.mode("overwrite").options(header="true").json("/home/user1/result_jsont")

6.2 将write输出文件夹转换为单一文件

...
result_df.repartition(1).write.mode("overwrite").options(header="true").json("/home/user1/res.txt")

使用上述代码最后一行持久化输出文件到hdfs中时,将会生成一个result_json文件夹,其结构类似下图:

spark-df输出到目录.png

实际数据文件是其内的part*文件,若需想要直接输出为一个文件而非目录,可以增加使用如下方式进行转换,代码供参考:

from py4j.java_gateway import java_import

java_import(spark._jvm, 'org.apache.hadoop.fs.Path')

hdfs_dirpath = "/home/user1/result_json"  # 原导出的hdfs文件夹目录地址
hdfs_filepath = "/home/user1/result.json"  # 最终想转换为的单个文件地址

fs = spark._jvm.org.apache.hadoop.fs.FileSystem.get(spark._jsc.hadoopConfiguration())
file = fs.globStatus(spark.sparkContext._jvm.Path(f'{hdfs_dirpath}/part*'))[0].getPath().getName()
# 将原目录李的part*文件重命名并移动到指定的文件地址
fs.rename(spark.sparkContext._jvm.Path(hdfs_dirpath + "/" + file), spark.sparkContext._jvm.Path(hdfs_filepath))
# 删除原文件目录
fs.delete(spark.sparkContext._jvm.Path(hdfs_dirpath), True)

七、 操作HDFS

  • linux命令参考文档: https://sparkbyexamples.com/apache-hadoop/hadoop-hdfs-dfs-commands-and-starting-hdfs-dfs-services/

7.1 常用hdfs文件交互命令

有时需要读写hdfs中的文件,可使用相应linux命令交互,常见命令示例:

# 查看目录文件列表,同linux ls命令
hadoop fs -ls ${hdfs_path}
# 下载hdfs目录或文件到本地
hadoop fs -get ${hdfs_path} ${local_path}
# 上传本地目录或文件到hdfs路径
hadoop fs -put ${local_path} ${hdfs_path}
# 删除hdfs目录或文件
hadoop fs -rm -r ${hdfs_path}

7.2 使用 distcp 在S3与Hdfs中拷贝文件

hdfs与s3文件互传

# 将s3文件拷贝到hdfs中,同理克反向将hdfs文件拷贝到s3中
hadoop distcp \
  -Dfs.s3a.endpoint=xxx.xom \
  -Dfs.s3a.access.key=4VPxxxxxxxxxxQZ7L \
  -Dfs.s3a.secret.key=vhlbxxxxxxxxxxxxxxxxxxxxxxxxyxxxg24 \
  -Dfs.s3a.path.style.access=true \
  -Dfs.s3a.connection.ssl.enabled=True \
  -Dfs.s3a.fast.upload=true \
  s3a://bucket_name/filename hdfs:///xxx:9000/hdfs_path

7.3 使用python操作hdfs
推荐使用第三方python库:smart_open,可与绝大部分文件系统使用统一API当时进行交互,包括hdfs、s3、sftp等

  • 参考文档:https://github.com/RaRe-Technologies/smart_open

简单示例代码如下:

7.3.1 smart_open在S3与Hdfs之间拷贝文件

import boto3
from smart_open import open
s3_client = boto3.client(
            "s3",
            "cluster_name",
            use_ssl=False,
            endpoint_url="http://xxx.com",
            aws_secret_access_key="my_secrect_key",
            aws_access_key_id="my_access_key",
        )

s3_file_path = "s3://my_bucket/my_key"
hdfs_file_path = "hdfs:///path/file"

with open(s3_file_path, "rb", transport_params={'client': s3_client}) as f_in:
    with open(hdfs_file_path, "wb") as f_out:
        while True:
            chunk_data = f_in.read(1024 * 1024 * 10)
            if not chunk_data:
                break
            f_out.write(chunk_data)

更多示例及使用方法参考smart_open官方文档

你可能感兴趣的:(PySpark任务开发-全场景配置参考示例)