只要有可用资源,就可以并行运行Spark代码,而不是按顺序运行

时间:2018-05-25 19:31:43

标签: python pyspark

我使用PySpark创建了一个管道,它基本上循环遍历查询列表,每个查询都使用JDBC连接器在MySQL数据库上运行,将结果存储在Spark DataFrame中,过滤其只有一个值的列,然后将其保存为Parquet文件。

由于我使用for循环遍历查询列表,因此每个查询和列过滤过程都是按顺序完成的,所以我没有使用所有可用的CPU。

我想要完成的是每当有CPU可用时启动一个新进程(查询+过滤器+ Parquet持久性)。

注意:我每次都处理不同的输入(查询),这与询问here的内容不同,后者在同一输入中进行不同的处理。另外,我不想指定同时运行多少进程,相反,我想在第一个进程中使用所有可用的CPU,如果仍有可用资源,请启动一个新进程一。如果仍有可用资源,请启动另一个资源,依此类推......

这是我正在运行的脚本:

# Imports
from pyspark.sql import SparkSession
from pyspark.sql.functions import isnull, when, count, countDistinct
from time import time


# Spark session initialization
spark = SparkSession \
    .builder \
    .appName('Filtering Columns') \
    .config('spark.driver.memory', '16g') \
    .config('spark.executor.memory', '16g') \
    .config('spark.driver.extraClassPath',
            '/path/to/mysql-connector-java-5.1.38.jar') \
    .getOrCreate()


# JDBC config
jdbc_config = {
    'url': 'jdbc:mysql://my_db_ip_address',
    'properties': {
        'user': 'my_db_user',
        'password': 'my_db_password'
    }
}


# My queries... Didn't put the real queries here, but
# they have nothing in special
queries_to_run = [
    {
        'table_name': 'table1',
        'query': '''
             (some query) as tmp
        '''
    },
    {
        'table_name': 'table2',
        'query': '''
             (some query) as tmp
        '''
    },
    {
        'table_name': 'table3',
        'query': '''
             (some query) as tmp
        '''
    },
    ...
]


# The function I'm using to filter the columns
def drop_constant_columns(df):
    cols_to_drop_map = df.select([
        when(countDistinct(column_name) == 1, True).alias(column_name)
        for column_name in df.columns
    ]).first().asDict()

    cols_to_drop = [
        col for col, should_drop in cols_to_drop_map.iteritems()
        if should_drop
    ]

    return df.drop(*cols_to_drop)


# Here's the code that loops through the queries and, for each 
# one of them:
# 1) Query a MySQL db
# 2) Store the result in a Spark DF
# 3) Filter the constant columns
# 4) Save the filtered DF in a Parquet format
for query in queries_to_run:
    print('Querying {}'.format(query['table_name']))
    df = spark.read.jdbc(table=query['query'], **jdbc_config)

    print('Filtering {}'.format(query['table_name']))
    n_cols = len(df.columns)

    start = time()
    df = drop_constant_columns(df)
    elapsed = time() - start

    n_cols_filtered = n_cols - len(df.columns)
    print('Filtered {} of {} columns in {:.2f} secs'.format(n_cols_filtered, n_cols, elapsed))

    print('Persisting {}'.format(query['table_name']))
    df.write.mode('overwrite').parquet('./{}_test.parquet'.format(query['table_name']))

我在Ubuntu 2.2.1上使用PySpark 2.7.12,Python 16.04

1 个答案:

答案 0 :(得分:1)

基本上,您需要为Spark上下文设置FAIR调度模式,创建多个线程并在每个线程中执行spark操作,以实现接近100%的集群饱和度(假设您的作业受CPU限制)。

虽然您提到您不想对线程数设置限制,但我仍然建议您这样做。您可以在操作系统中创建有限数量的线程,它们都可以从驱动程序中获取宝贵的内存和CPU资源。例如。你无法创建一百万个线程,无论如何都必须使用某种排队方式(例如信号量和锁的组合)。

另一方面,当所有执行程序都忙于100%的时间并且调度程序没有接受任何新任务时,有一个收益递减点,并且许多Spark作业只是闲置,等待执行程序变为可用。 Spark调度在任务级别完成,即如果某个执行者正在运行某个作业的任务,则不会被抢占。

您可以通过实验计算出足够多的并发请求,从而为所有请求提供最佳的整体处理时间。