聚合指南,Apache Spark 3.0:自定义聚合的显著改进

  在最近关于Spark 3.0的官方公告的背景下,由于Spark 3.0解决了早期Aggregator机制中的关键可用性和共存问题,因此Aggregator现在将成为对数据集执行自定义聚合的默认机制。 阅读故事以了解详细信息。

  聚合运算符在用于数据挖掘和分析的Spark应用程序中大量使用。 因此,Spark提供了多种现成的聚合功能以及用于构建自定义聚合功能的框架。 这些聚合函数可以多种方式用于数据集,以得出聚合结果。

  使用"自定义聚合"框架,用户可以实现特定的聚合流来聚合一组记录。 对于自定义聚合,Spark的早期版本提供了两种方法,第一种基于" UserDefinedAggregationFunction",第二种基于" Aggregator"。

  前面的故事" UDAF和聚合器:Apache Spark中数据集的定制聚合方法"详细介绍了这两种方法。 在这两种方法中,首先引入了" UserDefinedAggregationFunction"(也称为UDAF),以支持在Spark中对数据框(数据的无类型视图)进行自定义聚合。 但是,后来,随着Dataset的推出,该数据集支持数据的类型化视图和非类型化视图,另外还引入了"聚合器"方法以支持对数据类型化视图的自定义聚合。

  如果将两种方法进行比较:

  · UDAF方法针对的是无类型的数据视图,因此在UDAF中,包括输入,输出和中间聚合数据在内的所有内容(在聚合缓冲区中)本质上都是特定数据类型的一行行,而Aggregator方法是 面向数据的类型化视图,因此在Aggregator中,包括输入,输出和中间聚合数据在内的所有内容实质上都是某种类型的对象。

  · UDAF方法在更新或检索聚合缓冲区中的复杂数据类型(例如Map)时效率不高。 这是由于与用户空间和催化剂空间之间的复杂数据类型的转换相关的大量成本。 鉴于,在更新和检索聚合缓冲区对象中的任何数据类型时,聚合器方法非常有效,因为没有UDAF中的转换成本。

  · 此外,UDAF方法在聚合缓冲区中使用用户定义的数据类型(UDT)实现复杂的聚合流时,在编程上并不那么优雅。 尽管Aggregator方法在程序上是优雅的,但可以实现复杂的聚合流,其中涉及用于聚合缓冲区对象的复杂数据类型。

  但是,UDAF与SQL方言保持一致,并且可以与其他现成的聚合功能共存,以对数据集的无类型视图执行聚合。 这使用户更容易使用UDAF。 相反,在Spark 3.0之前,Aggregator尚未与SQL方言对齐,并且无法与其他现成的聚合功能共存以对数据集的无类型视图执行聚合。 这使用户很难在Spark 3.0之前使用Aggregator。

  但是,在Spark 3.0中,聚合器的用法现在与UDAF方法保持一致,从而可以在无类型的数据集视图上另外执行聚合。 而且,聚合器现在可以与现成的聚合功能(Spark中存在各种各样的聚合功能)一起使用。 聚合器方法的这些新变化以及聚合器相对于UDAF的固有优势最终使聚合器成为Spark 3.0中自定义聚合的默认选择。 早期的UDAF方法已经过时。

  在Spark 3.0中,扩展了Aggregator框架,以便现在也可以将Aggregator注册为类似于UDAF的用户定义函数:

  Aggregator<IN, BUF, OUT> agg=// custom Aggregator

  Encoder enc=// input encoder

  // register a UDF based on agg and enc (JAVA compatabile API)

  spark.udf.register("myCustomAgg", functions.udaf(agg, enc))

  一旦将聚合器定义为用户定义的函数,则可以使用相应的注册函数对数据集的无类型视图执行聚合。 同样,基于聚合器的用户定义函数可与现成的聚合函数和其他用户定义的函数(在其他聚合器上注册)一起使用,以对数据集的无类型视图执行多个聚合。

  下面是在数据集的无类型视图上使用Aggregator的示例。 在此示例中,定义了一个聚合器MyMedian,然后将其注册为用户定义的聚合函数,以对未类型的样本数据集执行中值计算:

  import java.io.Serializable;

  import org.apache.spark.sql.Dataset;

  import org.apache.spark.sql.Encoder;

  import org.apache.spark.sql.Encoders;

  import org.apache.spark.sql.Row;

  import org.apache.spark.sql.SparkSession;

  import org.apache.spark.sql.expressions.Aggregator;

  import org.apache.spark.sqlctions;

  public static class Median implements Serializable {

  private ArrayList arrLong;

  private Long count;

  public Median() {}

  public Median(ArrayList arrLong, long count) {

  this.arrLong=arrLong;

  this.count=count;

  }

  public ArrayList getArrLong() {

  return arrLong;

  }

  public void setArrLong(ArrayList arrLong) {

  this.arrLong=arrLong;

  }

  public Long getCount() {

  return count;

  }

  public void setCount(Long count) {

  this.count=count;

  }

  }

  public static class MyMedian extends Aggregator<Long, Median, Double>

  {

  public Median zero() {

  return new Median(new ArrayList(), 0L);

  }

  public Median reduce(Median buffer, Long data) {

  buffer.setArrLong(buffer.getArrLong().add(data));

  buffer.setCount(buffer.getCount() + 1);

  return buffer;

  }

  public Median merge(Median b1, Median b2) {

  b1.setArrLong(b1.getArrLong().addAll(b2.getArrLong()));

  b1.setCount(b1.getCount() + b2.getCount());

  return b1;

  }

  public Double finish(Median buffer) {

  double median;

  Collections.sort(buffer.getArrLong());

  ArrayList arrLong=buffer.getArrLong();

  if (buffer.getCount() % 2==0) {

  median=((double)arrLong.get(buffer.getCount()/2] +

  (double)arrLong.get[buffer.getCount()/2 - 1])/2;

  } else {

  median=(double)arrLong.get[buffer.getCount()/2]

  }

  return median;

  }

  // Specifies the Encoder for the intermediate value type

  public Encoder bufferEncoder() {

  return Encoders.bean(Median.class);

  }

  // Specifies the Encoder for the final output value type

  public Encoder outputEncoder() {

  return Encoders.DOUBLE();

  }

  }

  // Register the function to access it

  spark.udf().register("myMedian", functions.udaf(new MyMedian(), Encoders.LONG()));

  Dataset df=spark.read().json("data/src/samples.json");

  df.createOrReplaceTempView("samples");

  df();

  // +-------+------+

  // | Id |Value|

  // +-------+------+

  // | 1001| 100|

  // | 1002| 200|

  // | 1003| 300|

  // | 1004| 400|

  // +-------+------+

  /* Example showing use of only Aggregator based user defined function */

  Dataset output=spark.sql("SELECT myMedian(Value) as median_value FROM samples");

  output();

  // +--------------+

  // | median_value |

  // +--------------+

  // | 250.0|

  // +--------------+

  /* Example showing use of Aggregator based user defined function along with readymade function avg from Spark */

  Dataset output=spark.sql("SELECT myMedian(Value) as median_value, avg(Value) as average_value FROM samples");

  output();

  // +--------------+---------------+

  // | median_value | average_value |

  // +--------------+----------------

  // | 250.0| 250.0|

  // +--------------+---------------

  简介:考虑到自定义聚合要求在复杂数据分析和挖掘作业中的重要性,Spark 3.0已满足了人们期待已久的需求,即提供高效,全面且易于使用的框架在Spark应用程序中提供自定义聚合。 因此,聚合器是自定义聚合的新口号,而UDAF成为Spark 3.0的历史记录。

  如果对此故事有反馈或疑问,请在评论部分中回复。 希望您会发现它有用。 这是有关Apache Spark的其他综合故事的链接。

posted @ 2022-02-03 21:04  ebuybay  阅读(279)  评论(0编辑  收藏  举报