在Spark中,如何将多个数据帧转换为avro?

时间:2017-06-01 16:59:02

标签: apache-spark pyspark avro emr spark-avro

我有一个Spark工作,可以将一些数据处理成几个单独的数据帧。我将这些数据帧存储在一个列表中,即dataframes []。最后,我想将这些数据帧组合成一个分层格式,然后用avro编写输出。 avro架构是这样的:

{
    "name": "mydata",
    "type": "record",
    "fields": [
        {"name": "data", "type": {
            "type": "array", "items": {
                "name": "actualData", "type": "record", "fields": [
                    {"name": "metadata1", "type": "int"},
                    {"name": "metadata2", "type": "string"},
                    {"name": "dataframe", "type": {
                        "type": "array", "items": {
                            "name": "dataframeRecord", "type": "record", "fields": [
                                {"name": "field1", "type": "int"},
                                {"name": "field2", "type": "int"},
                                {"name": "field3", "type": ["string", "null"]}]
                            }
                        }
                    }]
                }
            }
        }
    ]
}

可以推断,每个数据帧都有三个字段,field1,field2和field3,我想在avro文件中将其作为一个数组写入。还有一些与每个数据帧相关的元数据。

我目前的做法是,一旦处理完这些数据,就将数据帧写入S3,然后使用单独的程序从S3中提取这些数据,使用avro库编写avro文件,然后将其上传到S3试。

但是,随着数据量的增长,这变得非常缓慢。我已经查看了databricks库直接编写avro文件,但我不知道如何将数据帧组合在内存中,或者数据库库如何确定我使用的模式。

在Spark中有没有惯用的方法呢?

P.S。我在Python中使用EMR和Spark 2.0.0。

2 个答案:

答案 0 :(得分:0)

如果架构相同,并且您只想将所有记录放入同一个DataFrame中,则可以使用DataFrame unionAll方法。

http://spark.apache.org/docs/1.6.3/api/python/pyspark.sql.html#pyspark.sql.DataFrame.unionAll

此函数将采用一个数据帧并将其附加到另一个数据帧。问题是它假设两列之间的列顺序相同,因此您可能需要做一些工作才能使它们排成一行,并为任何缺失的列创建空列。这是我用来安全地合并多个数据帧的python函数

def union_multiple_dataframes(iterable_list_df):
    input_dfs = list(iterable_list_df)

    # First figure out all the field names
    field_types = {}
    for df in input_dfs:
        for field in df.schema.fields:
            # Check for type mismatch
            if field in field_types:
                if field.dataType != field_types[field.name]:
                    raise ValueError("Mismatched data types when unioning dataframes for field: {}".format(field))
            else:
                field_types[field.name] = field.dataType

    # First add in empty fields so all df's have the same schema
    fields = set(field_types.keys())
    for i, df in enumerate(input_dfs):
        missing = fields - set(df.schema.names)
        for field in missing:
            df = df.withColumn(field, F.lit(None))

        input_dfs[i] = df

    # Finally put all the df's columns in the same order, and do the actual union
    sorted_dfs = [df.select(*sorted(fields)) for df in iterable_list_df]
    return reduce(lambda x, y: x.unionAll(y), sorted_dfs)

示例用法如下:

input_dfs = [do_something(..) for x in y]
combined_df = union_multiple_dataframes(input_dfs)
combined_df.write.format("com.databricks.spark.avro").save("s3://my-bucket/path")

答案 1 :(得分:0)

我找到了一个特定于PySpark的解决方案:

对于每个数据框,我使用.collect()来获取行列表。对于每个Row对象,我调用asDict()来获取字典。从那里,我能够用一个简单的循环构建一个字典列表。一旦我有了这个词典列表,数据退出Spark并进入纯Python领域,并且“更容易”处理(但效率较低)。

或者,如果我选择Scala而不是Python,我可能已经能够将数据帧转换为数据集,这似乎提供了一些方法来执行我需要的操作,但这完全是另一个故事。