PySpark实际应用踩坑
PySpark踩坑记录
PySpark是Apache Spark支持python编写的工具,python简单实用,使用python操作spark做简单的数据分析处理也是一种选择。Cassandra是分布式NoSQL数据库系统,可以简单理解它和HBase和MongoDB类似。本文主要分享PySpark实际应用和通过PySpark操作Cassandra遇到的坑。
1. spark cassandra connector
首先,pyspark连接Cassandra的两种方法。
(1)一种是datastax connector,通用于各种语言,根据spark、cassandra版本选择connector版本,参考链接:
[https://github.com/datastax/spark-cassandra-connector]
pyspark添加connector,可以在提交任务中添加:
./bin/pyspark --packages com.datastax.spark:spark-cassandra-connector_2.11:2.4.2
也可以在python脚本中添加:
os.environ["PYSPARK_SUBMIT_ARGS"] = '--packages com.datastax.spark:spark-cassandra-connector_2.11:2.4.2
(2)另一种是适合pyspark的connector
[https://github.com/anguenot/pyspark-cassandra]
./bin/pyspark --packages anguenot/pyspark-cassandra:2.4.0
这一种方式支持rdd写入Cassandra,读取Cassandra表为rdd格式,前一种connector只支持pyspark的dataframe格式。
第二个connector的文档中的例子,比如:
import pyspark_cassandra
conf = SparkConf() \
.setAppName("PySpark Cassandra Test") \
.setMaster("spark://spark-master:7077") \
.set("spark.cassandra.connection.host", "cas-1")
sc = CassandraSparkContext(conf=conf)
pyspark_cassandra不是直接安装在python的包,而是在–packages中的。所以,写代码时会报错,说找不到包,不过提交时不会有问题。
2. 配置spark master和worker节点的python环境
Python版本问题首先是一个坑
如果写代码用的python3,但是master和worker上默认python2,此时会报python运行的错误。对于用java和scala,spark-submit上传打好的jar包即可提交执行,因为driver和executor都是以JVM为载体来运行和执行任务。而对于PySpark,Spark是在外围包装一层Python API,借助Py4j实现Python和Java的交互。
因此,如果提交的脚本python版本是3,需要在master和worker节点安装python3,并在python脚本中配置节点的python路径:
os.environ["PYSPARK_PYTHON"] = '/bin/python3'
os.environ["PYSPARK_DRIVER_PYTHON"] = '/bin/python3'
此处的python路径,是master和worker节点的python路径,与上传spark任务的机器中的python路径无关。
3. PySpark操作Cassandra
(1)第一种是应用sparksql中的sparksession对象,创建spark dataset或spark dataframe。
样例如下:
from pyspark.sql import SparkSession
spark = SparkSession.builder.appName(sys.argv[0])\
.config("spark.cassandra.connection.host", "cassandra_host_ip").getOrCreate()
# 加载Cassandra数据到dataframe
df = spark.read.format("org.apache.spark.sql.cassandra")\
.options(table="table_name", keyspace="keyspace_name").load()
# UDF
from pyspark.sql.functions import udf
class UdfSample(object):
......
udf1 = udf(lambda z: UdfSample(z)......, StringType())
# dataframe写入Cassandra
df.write.format("org.apache.spark.sql.cassandra")\
.options(table="table_name ", keyspace="keyspace_name")\
.mode('append').save()
Dataframe中特别多的会应用udf和lambda。对于自定义udf,序列化、反序列化、及通信IO性能损耗比较明显,而Spark sql中内置的udf会降低数据在executor jvm和python worker之间序列化反序列化、通信等损耗。
spark dataframe和pandas dataframe是两种不同的数据格式,虽然有方法实现两者相互转化,但是实用性不大,且浪费资源。
(2)第二种是spark rdd的方式
文档中没有给出定义列名的rdd的写入Cassandra表的方式,可以参考下面的例子:
rdd.saveToCassandra("keyspace", "table_name", columns=["column_1", "column_2", "column_3"])
4. crontab定时任务无法执行
使用spark-submit提交python脚本到spark集群,例如
./bin/spark-submit --master spark://ip:port ./python1.py
同样的命令手动输入运行没问题,但是在crontab上无法执行。
问题本质和crontab无关,这也是python环境的问题。如果在python脚本中加入打印sys.path的代码,会发现手动执行和crontab执行的sys.path中python路径不同。简单直接的方法是在提交命令中加入本地的python运行路径。
./bin/spark-submit
--master spark://ip:port
--conf spark.pyspark.python=/bin/python
--conf spark.pyspark.driver.python=/bin/python
./python1.py
5. pyspark依赖打包
对于较大的项目,会有多个文件互相依赖,需要把整个项目打包并提交spark任务。
打包过程:
使用zip命令,需要注意一点是zip会把路径也打包进去,所以打包前要进入项目路径。
zip -r /data/xxx/pyzip/sample.zip ./
然后提交spark任务时,引用打包好的项目
–py-files=’/data/xxx/pyzip/sample.zip’
注意:
如果任务中使用到的依赖脚本中,import的包在worker中没有,会报错。如果提交脚本的import的包在worker中没有,并且在spark过程中调用,也会报错,因为worker不认识。
总体来说
对于简单离线任务,可以使用PySpark快速部署提交,简单方便。但在大数据情景中,JVM和Python进程间频繁通信会导致性能损耗,尽量慎用PySpark。后续实际中遇到问题,也会继续补充。