Spark函数别名-高性能udfs

时间:2019-07-27 15:22:29

标签: apache-spark apache-spark-sql apache-spark-2.0

上下文

在我编写的许多sql查询中,我发现自己以完全相同的方式组合了spark预定义函数,这常常导致冗长和重复的代码,而我的开发人员本能是想重构它。

所以,我的问题是:是否有某种方法可以为函数组合定义某种别名,而无需求助于udfs(出于性能考虑而避免使用)-目的是使代码更清晰,更清洁。本质上,我想要的是类似udfs的东西,但是没有性能损失。此外,这些功能必须可在spark.sql调用中使用的spark-sql查询中调用。

示例

例如,假设我的业务逻辑是反转一些字符串并像这样对其进行散列:(请注意,此处的功能组合无关紧要,重要的是它是现有预定义的spark功能的某种组合-可能很多)

SELECT 
    sha1(reverse(person.name)),
    sha1(reverse(person.some_information)),
    sha1(reverse(person.some_other_information))
    ...
FROM person

有没有一种方法可以声明一个business函数,而不必付出使用udf的性能代价,因此可以将上面的代码重写为:

SELECT 
    business(person.name),
    business(person.some_information),
    business(person.some_other_information)
    ...
FROM person

我在spark文档和此网站上进行了很多搜索,但没有找到实现这一目标的方法,这对我来说很奇怪,因为它看起来很自然,而且我也不明白为什么您必须支付定义和调用udf的黑盒价格。

1 个答案:

答案 0 :(得分:2)

  

有没有一种方法可以声明业务功能而无需付出使用udf的性能代价

您不必使用udf,可以扩展Expression类,或者使用最简单的操作-UnaryExpression。然后,您将仅需实现几种方法,然后开始。除了允许使用一些优势功能(例如代码生成)之外,它还与Spark集成在一起。

对于您来说,添加business函数非常简单:

def business(column: Column): Column = {
  sha1(reverse(column))
}
  

必须可在spark.sql调用中使用的spark-sql查询中调用

这比较棘手,但可以实现。
您需要创建自定义函数注册器:

import org.apache.spark.sql.catalyst.FunctionIdentifier
import org.apache.spark.sql.catalyst.expressions.Expression 

object FunctionAliasRegistrar {

val funcs: mutable.Map[String, Seq[Column] => Column] = mutable.Map.empty

  def add(name: String, builder: Seq[Column] => Column): this.type = {
    funcs += name -> builder
    this
  }

  def registerAll(spark: SparkSession) = {
    funcs.foreach { case (alias, builder) => {
      def b(children: Seq[Expression]) = builder.apply(children.map(expr => new Column(expr))).expr
      spark.sessionState.functionRegistry.registerFunction(FunctionIdentifier(alias), b)
    }}
  }
}

然后您可以按以下方式使用它:

FunctionAliasRegistrar
  .add("business1", child => lower(reverse(child.head)))
  .add("business2", child => upper(reverse(child.head)))
  .registerAll(spark) 

dataset.createTempView("data")

spark.sql(
  """
    | SELECT business1(name), business2(name) FROM data
    |""".stripMargin)
.show(false)

输出:

+--------------------+--------------------+
|lower(reverse(name))|upper(reverse(name))|
+--------------------+--------------------+
|sined               |SINED               |
|taram               |TARAM               |
|1taram              |1TARAM              |
|2taram              |2TARAM              |
+--------------------+--------------------+

希望这会有所帮助。