前言
示例中基于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文件夹,其结构类似下图:
实际数据文件是其内的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
官方文档