我有一个例子,我使用PySpark(或者Spark,如果我不能使用Python,则需要使用Scala或Java)从几百个缺少主键的数据库表中提取数据。(为什么甲骨文会创建一个包含主键表的ERP产品是另一个主题。。。但是无论如何,我们需要能够提取数据并将每个数据库表中的数据保存到Parquet文件中。)我最初尝试使用Sqoop而不是PySpark,但是由于我们遇到了许多问题,尝试使用PySpark/Spark更有意义。在
理想情况下,我希望计算集群中的每个任务节点:取一个表的名称,从数据库中查询该表,并将该表另存为S3中的Parquet文件(或Parquet文件集)。我的第一步是让它在独立模式下本地工作。(如果我对每个给定的表都有一个主键,那么我可以将查询和文件保存过程划分为给定表的不同行集,并将行分区分布到计算集群中的任务节点上,以并行执行文件保存操作,但这是因为Oracle的ERP产品缺少表的主键值得关注的是,这不是一个选择。)
我能够用PySpark成功地查询目标数据库,并且能够使用多线程将数据成功地保存到parquet文件中,但是由于某些原因,只有一个线程可以执行任何操作。如果另一个线程只执行了一个文件名,则只会将该文件保存为一个单独的文件名。我猜可能发生了某种类型的锁定问题。
如果我正确理解这里的评论:How to run multiple jobs in one Sparkcontext from separate threads in PySpark?
那么我要做的应该是可能的,除非有与执行并行jdbcsql查询相关的特定问题。在
编辑:我正在寻找一种方法,允许我使用某种类型的线程池,这样我就不需要为需要处理的每个表手动创建线程,并在集群中的任务节点之间手动平衡它们。在
即使我试着设置:--master local[*]
以及
^{pr2}$
问题依然存在。在
另外,为了简单地解释我的代码,我需要使用一个自定义的JDBC驱动程序,并且我在Windows上的Jupyter笔记本中运行代码,所以我使用了一个解决方法来确保PySpark以正确的参数启动。
(我没有反对其他操作系统的记录,但我的Windows机器是我最快的工作站,所以我才使用它。)
以下是我的设置:driverPath = r'C:\src\NetSuiteJDBC\NQjc.jar'
os.environ["PYSPARK_SUBMIT_ARGS"] = (
"--driver-class-path '{0}' --jars '{0}' --master local[*] --conf 'spark.scheduler.mode=FAIR' --conf 'spark.scheduler.allocation.file=C:\\src\\PySparkConfigs\\fairscheduler.xml' pyspark-shell".format(driverPath)
)
import findspark
findspark.init()
from pyspark import SparkContext, SparkConf
from pyspark.sql import SparkSession, Column, Row, SQLContext
from pyspark.sql.functions import col, split, regexp_replace, when
from pyspark.sql.types import ArrayType, IntegerType, StringType
spark = SparkSession.builder.appName("sparkNetsuite").getOrCreate()
spark.sparkContext.setLogLevel("INFO")
spark.sparkContext.setLocalProperty("spark.scheduler.pool", "production")
sc = SparkContext.getOrCreate()
然后,为了测试多重处理,我创建了这个文件sparkMethods.py在我运行Jupyter笔记本的目录中,并将此方法放入其中:def testMe(x):
return x*x
当我跑步时:from multiprocessing import Pool
import sparkMethods
if __name__ == '__main__':
pool = Pool(processes=4) # start 4 worker processes
# print "[0, 1, 4,..., 81]"
print(pool.map(sparkMethods.testMe, range(10)))
在我的Jupyter笔记本中,我得到了预期的输出:[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
此方法包含用于建立JDBC连接的逻辑:# In sparkMethods.py file:
def getAndSaveTableInPySpark(tableName):
import os
import os.path
from pyspark.sql import SparkSession, SQLContext
spark = SparkSession.builder.appName("sparkNetsuite").getOrCreate()
spark.sparkContext.setLogLevel("INFO")
spark.sparkContext.setLocalProperty("spark.scheduler.pool", "production")
jdbcDF = spark.read \
.format("jdbc") \
.option("url", "OURCONNECTIONURL;") \
.option("driver", "com.netsuite.jdbc.openaccess.OpenAccessDriver") \
.option("dbtable", tableName) \
.option("user", "USERNAME") \
.option("password", "PASSWORD") \
.load()
filePath = "C:\\src\\NetsuiteSparkProject\\" + tableName + "\\" + tableName + ".parquet"
jdbcDF.write.parquet(filePath)
fileExists = os.path.exists(filePath)
if(fileExists):
return (filePath + " exists!")
else:
return (filePath + " could not be written!")
然后,回到我的Jupyter笔记本,我运行:import sparkMethods
from multiprocessing import Pool
if __name__ == '__main__':
with Pool(5) as p:
p.map(sparkMethods.getAndSaveTableInPySpark, top5Tables)
问题是似乎只有一个线程在执行。
当我在控制台输出中执行它时,我看到它最初包括以下内容:The process cannot access the file because it is being used by another process.
The system cannot find the file
C:\Users\DEVIN~1.BOS\AppData\Local\Temp\spark-class-launcher-output-3662.txt.
. . .
这让我怀疑,可能是某种类型的锁定发生了。在
雷加尔德斯s、 其中一个线程将始终成功运行并成功地查询其对应的表,并根据需要将其保存到Parquet文件中。进程中存在一些不确定性,因为不同的执行会导致不同的线程赢得竞争,从而处理不同的表。
有趣的是,只有一个作业被执行,如Spark UI所示:
但是,这里的文章:https://medium.com/@rbahaguejr/threaded-tasks-in-pyspark-jobs-d5279844dac0
这意味着如果Spark UI成功启动了多个作业,我应该会看到它们。在
现在,如果问题是PySpark实际上不能跨不同的任务节点并行运行多个JDBC查询,那么我的解决方案可能是使用JDBC连接池,甚至只为每个表打开一个连接(只要我在线程末尾关闭连接)。
当获取要处理的表列表时,我成功地通过jaydebeapi库连接到数据库,如下所示:import jaydebeapi
conn = jaydebeapi.connect("com.netsuite.jdbc.openaccess.OpenAccessDriver",
"OURCONNECTIONURL;",
["USERNAME", "PASSWORD"],
r"C:\src\NetSuiteJDBC\NQjc.jar")
top5Tables = list(pd.read_sql("SELECT TOP 5 TABLE_NAME FROM OA_TABLES WHERE TABLE_OWNER != 'SYSTEM';", conn)["TABLE_NAME"].values)
conn.close()
top5Tables
输出为:['SALES_TERRITORY_PLAN_PARTNER',
'WORK_ORDER_SCHOOLS_TO_INSTALL_MAP',
'ITEM_ACCOUNT_MAP',
'PRODUCT_TRIAL_STATUS',
'ACCOUNT_PERIOD_ACTIVITY']
因此,可以想象的是,如果问题是PySpark不能用于像这样跨任务节点分布多个查询,那么也许我可以使用jaydebeapi库来建立连接。但是,在这种情况下,我仍然需要一种方法来将jdbcsql查询的输出写入Parquet文件(这在理想情况下可以利用Spark的模式推理功能),但是如果可行的话,我愿意采用这种方法。在
那么,如何在主节点不按顺序执行所有查询的情况下,成功地查询数据库并并行地将输出保存到Parquet文件(即分布在任务节点上)?在