Apache spark 为什么火花计数操作分三个阶段执行

Apache spark 为什么火花计数操作分三个阶段执行,apache-spark,apache-spark-sql,Apache Spark,Apache Spark Sql,我已经加载了一个csv文件。将其重新分区为4,然后对数据帧进行计数。当我看到DAG时,我看到这个动作分三个阶段执行 为什么这个简单的动作分为3个阶段执行。我认为第一阶段是加载文件,第二阶段是查找每个分区的计数 那么第三阶段发生了什么 这是我的密码 val sample = spark.read.format("csv").option("header", "true").option("inferSchema", "true").option("delimiter", ";").load("s

我已经加载了一个csv文件。将其重新分区为4,然后对数据帧进行计数。当我看到DAG时,我看到这个动作分三个阶段执行

为什么这个简单的动作分为3个阶段执行。我认为第一阶段是加载文件,第二阶段是查找每个分区的计数

那么第三阶段发生了什么

这是我的密码

val sample = spark.read.format("csv").option("header", "true").option("inferSchema", "true").option("delimiter", ";").load("sample_data.csv")

sample.repartition(4).count()
  • 第一阶段=读取文件。由于重新分区(因为它是需要洗牌的广泛转换),它不能加入到具有部分计数的单个阶段(第二阶段)

  • 第二阶段=本地计数(计算每个分区的计数)

  • 第三阶段=驱动程序上的结果聚合

  • Spark generage每个动作或宽变换的单独阶段。要获得关于窄/宽转换的更多详细信息,以及为什么宽转换需要单独的阶段,请查看或

    让我们在本地测试一下这个假设。首先,您需要创建一个数据集:

    dataset/test-data.json

    然后运行以下代码:

        StructType schema = new StructType()
                .add("key", DataTypes.IntegerType)
                .add("value", DataTypes.StringType);
    
        SparkSession session = SparkSession.builder()
                .appName("sandbox")
                .master("local[*]")
                .getOrCreate();
    
        session
                .read()
                .schema(schema)
                .json("file:///C:/<you_path>/dataset")
                .repartition(4) // comment on the second run
                .registerTempTable("df");
    
        session.sqlContext().sql("SELECT COUNT(*) FROM df").explain();
    
    StructType架构=新的StructType()
    .add(“key”,数据类型.IntegerType)
    .add(“值”,数据类型.StringType);
    SparkSession会话=SparkSession.builder()
    .appName(“沙盒”)
    .master(“本地[*]”)
    .getOrCreate();
    一场
    .读()
    .schema(schema)
    .json(“file:///C://dataset")
    .repartition(4)//第二次运行时的注释
    .注册可撤销(“df”);
    session.sqlContext().sql(“从df中选择COUNT(*”).explain();
    
    输出将是:

    == Physical Plan ==
    *(3) HashAggregate(keys=[], functions=[count(1)])
    +- Exchange SinglePartition
       +- *(2) HashAggregate(keys=[], functions=[partial_count(1)])
          +- Exchange RoundRobinPartitioning(4)
             +- *(1) FileScan json [] Batched: false, Format: JSON, Location: InMemoryFileIndex[file:/C:/Users/iaroslav/IdeaProjects/sparksandbox/src/main/resources/dataset], PartitionFilters: [], PushedFilters: [], ReadSchema: struct<>
    
    ==物理计划==
    *(3) HashAggregate(键=[],函数=[计数(1)])
    +-交换单分区
    +-*(2)HashAggregate(键=[],函数=[部分计数(1)])
    +-Exchange RoundRobinParting(4)
    +-*(1)FileScan json[]批处理:false,格式:json,位置:InMemoryFileIndex[文件:/C:/Users/iaroslav/IdeaProjects/sparksandbox/src/main/resources/dataset],分区过滤器:[],PushedFilters:[],ReadSchema:struct
    
    但是,如果您注释/删除.repartition(4)字符串,请注意TableScan和partial_count是在单个阶段内完成的,输出如下所示:

    == Physical Plan ==
    *(2) HashAggregate(keys=[], functions=[count(1)])
    +- Exchange SinglePartition
       +- *(1) HashAggregate(keys=[], functions=[partial_count(1)])
          +- *(1) FileScan json [] Batched: false, Format: JSON, Location: InMemoryFileIndex[file:/C:/Users/iaroslav/IdeaProjects/sparksandbox/src/main/resources/dataset], PartitionFilters: [], PushedFilters: [], ReadSchema: struct<>
    
    ==物理计划==
    *(2) HashAggregate(键=[],函数=[计数(1)])
    +-交换单分区
    +-*(1)HashAggregate(键=[],函数=[部分计数(1)])
    +-*(1)FileScan json[]批处理:false,格式:json,位置:InMemoryFileIndex[文件:/C:/Users/iaroslav/IdeaProjects/sparksandbox/src/main/resources/dataset],分区过滤器:[],PushedFilters:[],ReadSchema:struct
    

    另请注意,额外的阶段可能会对性能产生重大影响,因为它需要磁盘I/O(请看一看),并且是影响并行化的某种同步障碍,这意味着在大多数情况下,Spark在第1阶段完成之前不会启动第2阶段。如果
    重新分区
    提高并行度,可能还是值得的。

    推断模式
    设置为
    会导致Spark对数据进行一次额外的传递,以发现模式。@hristoilev即使没有
    推断模式
    配置,您也会得到3个阶段。请检查我的answer@VB_我懂了。非常好的解释。谢谢你的回答。这是有道理的。所以这个最终结果聚合任务应该总是在驱动程序上运行?如果我在DF中取一个字段的max()。因此,在每个分区上找到最大值后,最终的值将在驱动程序中找到?@是的,
    count
    将始终将结果拉到驱动程序。我认为
    max
    也可以这样做,否则它如何在分区之间比较结果。但我认为这里的要点是,您不必在这里优化
    count
    操作,因为每个分区只有一个整数被移动到驱动程序。这里最繁重的操作是
    重新分区
    ,如果您最初有4个以上的分区,请查看
    合并
    ,或者根本不重新分区(如果分区数量合理)。假设您有2个分区,不进行重新分区的情况下,
    count
    可能比
    重新分区(4)更快。count()
    @ѕƒ如果要谈论进一步的优化(假设您控制了CSV文件创建),您可以查看不同的文件格式(柱状拼花地板,对于
    count
    操作,读取单个柱而不是整个文件就足够了)。如果你想继续使用CSV,也要检查压缩选项,而不是需要可拆分的压缩选项。@基于ost的优化器。这意味着使用某些格式(不是CSV格式)或者使用CBO,对于
    count
    操作,Spark不会读取数据,而只读取元数据。在这种情况下,
    count
    应该非常快,并且
    重新分区
    IMHO不需要感谢有价值的注释。我不寻求性能改进。我想了解的是Spark将如何协调结果从不同的执行器中获取数据,并将其聚合以显示最终结果(如果是count或max或其他操作)。我想知道在驱动程序从每个分区获得独立结果后,谁将在驱动程序端聚合数据。从您的回答中,这是将在驱动程序节点上运行的其他任务。
    == Physical Plan ==
    *(2) HashAggregate(keys=[], functions=[count(1)])
    +- Exchange SinglePartition
       +- *(1) HashAggregate(keys=[], functions=[partial_count(1)])
          +- *(1) FileScan json [] Batched: false, Format: JSON, Location: InMemoryFileIndex[file:/C:/Users/iaroslav/IdeaProjects/sparksandbox/src/main/resources/dataset], PartitionFilters: [], PushedFilters: [], ReadSchema: struct<>