/** Spark SQL源代码分析系列文章*/

在SQL的世界里,除了官方提供的经常使用的处理函数之外。一般都会提供可扩展的对外自己定义函数接口,这已经成为一种事实的标准。

在前面Spark SQL源代码分析之核心流程一文中,已经介绍了Spark SQL Catalyst Analyzer的作用,其中包括了ResolveFunctions这个解析函数的功能。可是随着Spark1.1版本号的公布。Spark SQL的代码有非常多新完好和新功能了。和我先前基于1.0的源代码分析多少有些不同,比方支持UDF:

spark1.0及曾经的实现:

  protected[sql] lazy val catalog: Catalog = new SimpleCatalog
@transient
protected[sql] lazy val analyzer: Analyzer =
new Analyzer(catalog, EmptyFunctionRegistry, caseSensitive = true) //EmptyFunctionRegistry空实现
@transient
protected[sql] val optimizer = Optimizer

  Spark1.1及以后的实现:

  protected[sql] lazy val functionRegistry: FunctionRegistry = new SimpleFunctionRegistry //SimpleFunctionRegistry实现。支持简单的UDF

  @transient
protected[sql] lazy val analyzer: Analyzer =
new Analyzer(catalog, functionRegistry, caseSensitive = true)

一、引子:

对于SQL语句中的函数,会经过SqlParser的的解析成UnresolvedFunction。UnresolvedFunction最后会被Analyzer解析。

SqlParser:

除了非官方定义的函数外,还能够定义自己定义函数。sql parser会进行解析。


  ident ~ "(" ~ repsep(expression, ",") <~ ")" ^^ {
case udfName ~ _ ~ exprs => UnresolvedFunction(udfName, exprs)

将SqlParser传入的udfName和exprs封装成一个class class UnresolvedFunction继承自Expression。

仅仅是这个Expression的dataType等一系列属性和eval计算方法均无法訪问。强制訪问会抛出异常,由于它没有被Resolved,仅仅是一个载体。

case class UnresolvedFunction(name: String, children: Seq[Expression]) extends Expression {
override def dataType = throw new UnresolvedException(this, "dataType")
override def foldable = throw new UnresolvedException(this, "foldable")
override def nullable = throw new UnresolvedException(this, "nullable")
override lazy val resolved = false // Unresolved functions are transient at compile time and don't get evaluated during execution.
override def eval(input: Row = null): EvaluatedType =
throw new TreeNodeException(this, s"No function to evaluate expression. type: ${this.nodeName}") override def toString = s"'$name(${children.mkString(",")})"
}<strong></strong>

Analyzer:

Analyzer初始化的时候会须要Catalog,database和table的元数据关系,以及FunctionRegistry来维护UDF名称和UDF实现的元数据,这里使用SimpleFunctionRegistry。

  /**
* Replaces [[UnresolvedFunction]]s with concrete [[catalyst.expressions.Expression Expressions]].
*/
object ResolveFunctions extends Rule[LogicalPlan] {
def apply(plan: LogicalPlan): LogicalPlan = plan transform {
case q: LogicalPlan =>
q transformExpressions { //对当前LogicalPlan进行transformExpressions操作
case u @ UnresolvedFunction(name, children) if u.childrenResolved => //假设遍历到了UnresolvedFunction
registry.lookupFunction(name, children) //从UDF元数据表里查找udf函数
}
}
}

二、UDF注冊

2.1 UDFRegistration

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvb29wc29vbQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />

registerFunction("len", (x:String)=>x.length)

registerFunction是UDFRegistration下的方法,SQLContext如今实现了UDFRegistration这个trait。仅仅要导入SQLContext,即能够使用udf功能。

UDFRegistration核心方法registerFunction:

registerFunction方法签名def registerFunction[T: TypeTag](name: String, func: Function1[_, T]): Unit

接受一个udfName 和 一个FunctionN。能够是Function1 到Function22。

即这个udf的參数仅仅支持1-22个。

(scala的痛啊)

内部builder通过ScalaUdf来构造一个Expression,这里ScalaUdf继承自Expression(能够简单的理解眼下的SimpleUDF即是一个Catalyst的一个Expression),传入scala的function作为UDF的实现,而且用反射检查字段类型是否是Catalyst同意的,见ScalaReflection.

    def registerFunction[T: TypeTag](name: String, func: Function1[_, T]): Unit = {
def builder(e: Seq[Expression]) = ScalaUdf(func, ScalaReflection.schemaFor(typeTag[T]).dataType, e)//构造Expression
functionRegistry.registerFunction(name, builder)//向SQLContext的functionRegistry(维护了一个hashMap来管理udf映射)注冊
}

2.2 注冊Function:

注意:这里FunctionBuilder是一个type FunctionBuilder = Seq[Expression] => Expression

class SimpleFunctionRegistry extends FunctionRegistry {
val functionBuilders = new mutable.HashMap[String, FunctionBuilder]() //udf映射关系维护[udfName,Expression] def registerFunction(name: String, builder: FunctionBuilder) = { //put expression进Map
functionBuilders.put(name, builder)
} override def lookupFunction(name: String, children: Seq[Expression]): Expression = {
functionBuilders(name)(children) //查找udf,返回Expression
}
}

至此。我们将一个scala function注冊为一个catalyst的一个Expression,这就是spark的simple udf。

三、UDF计算:

UDF既然已经被封装为catalyst树里的一个Expression节点,那么计算的时候也就是计算ScalaUdf的eval方法。

先通过Row和表达式计算function所须要的參数。最后通过反射调用function,来达到计算udf的目的。

ScalaUdf继承自Expression:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvb29wc29vbQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" />

scalaUdf接受一个function, dataType,和一系列表达式。

比較简单。看凝视就可以:

case class ScalaUdf(function: AnyRef, dataType: DataType, children: Seq[Expression])
extends Expression { type EvaluatedType = Any def nullable = true override def toString = s"scalaUDF(${children.mkString(",")})"
 override def eval(input: Row): Any = {
    val result = children.size match {
      case 0 => function.asInstanceOf[() => Any]()
      case 1 => function.asInstanceOf[(Any) => Any](children(0).eval(input)) //反射调用function
      case 2 =>
        function.asInstanceOf[(Any, Any) => Any](
          children(0).eval(input), //表达式參数计算
          children(1).eval(input))
      case 3 =>
        function.asInstanceOf[(Any, Any, Any) => Any](
          children(0).eval(input),
          children(1).eval(input),
          children(2).eval(input))
      case 4 =>
......
  case 22 => //scala function仅仅支持22个參数。这里枚举了。
        function.asInstanceOf[(Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any) => Any](
          children(0).eval(input),
          children(1).eval(input),
          children(2).eval(input),
          children(3).eval(input),
          children(4).eval(input),
          children(5).eval(input),
          children(6).eval(input),
          children(7).eval(input),
          children(8).eval(input),
          children(9).eval(input),
          children(10).eval(input),
          children(11).eval(input),
          children(12).eval(input),
          children(13).eval(input),
          children(14).eval(input),
          children(15).eval(input),
          children(16).eval(input),
          children(17).eval(input),
          children(18).eval(input),
          children(19).eval(input),
          children(20).eval(input),
          children(21).eval(input))

四、总结

Spark眼下的UDF事实上就是scala function。将scala function封装到一个Catalyst Expression其中,在进行sql计算时。使用相同的Eval方法对当前输入Row进行计算。

编写一个spark udf非常easy。仅仅需给UDF起个函数名,而且传递一个scala function就可以。

依靠scala函数编程的表现能力,使得编写scala udf比較简单。且相较hive的udf更easy使人理解。

——EOF——

原创文章。转载请注明:

转载自:OopsOutOfMemory盛利的Blog。作者: OopsOutOfMemory

本文链接地址:http://blog.csdn.net/oopsoom/article/details/39395641

注:本文基于署名-非商业性使用-禁止演绎 2.5 中国大陆(CC BY-NC-ND 2.5 CN)协议,欢迎转载、转发和评论,可是请保留本文作者署名和文章链接。如若须要用于商业目的或者与授权方面的协商。请联系我。

Spark SQL Catalyst源代码分析之UDF的更多相关文章

  1. Spark SQL Catalyst源代码分析之TreeNode Library

    /** Spark SQL源代码分析系列文章*/ 前几篇文章介绍了Spark SQL的Catalyst的核心执行流程.SqlParser,和Analyzer,本来打算直接写Optimizer的,可是发 ...

  2. Spark SQL Catalyst源代码分析Optimizer

    /** Spark SQL源代码分析系列*/ 前几篇文章介绍了Spark SQL的Catalyst的核心运行流程.SqlParser,和Analyzer 以及核心类库TreeNode,本文将具体解说S ...

  3. Spark SQL Catalyst源代码分析之Analyzer

    /** Spark SQL源代码分析系列文章*/ 前面几篇文章解说了Spark SQL的核心运行流程和Spark SQL的Catalyst框架的Sql Parser是如何接受用户输入sql,经过解析生 ...

  4. 第八篇:Spark SQL Catalyst源码分析之UDF

    /** Spark SQL源码分析系列文章*/ 在SQL的世界里,除了官方提供的常用的处理函数之外,一般都会提供可扩展的对外自定义函数接口,这已经成为一种事实的标准. 在前面Spark SQL源码分析 ...

  5. 第三篇:Spark SQL Catalyst源码分析之Analyzer

    /** Spark SQL源码分析系列文章*/ 前面几篇文章讲解了Spark SQL的核心执行流程和Spark SQL的Catalyst框架的Sql Parser是怎样接受用户输入sql,经过解析生成 ...

  6. 第二篇:Spark SQL Catalyst源码分析之SqlParser

    /** Spark SQL源码分析系列文章*/ Spark SQL的核心执行流程我们已经分析完毕,可以参见Spark SQL核心执行流程,下面我们来分析执行流程中各个核心组件的工作职责. 本文先从入口 ...

  7. 第五篇:Spark SQL Catalyst源码分析之Optimizer

    /** Spark SQL源码分析系列文章*/ 前几篇文章介绍了Spark SQL的Catalyst的核心运行流程.SqlParser,和Analyzer 以及核心类库TreeNode,本文将详细讲解 ...

  8. 第六篇:Spark SQL Catalyst源码分析之Physical Plan

    /** Spark SQL源码分析系列文章*/ 前面几篇文章主要介绍的是spark sql包里的的spark sql执行流程,以及Catalyst包内的SqlParser,Analyzer和Optim ...

  9. 第四篇:Spark SQL Catalyst源码分析之TreeNode Library

    /** Spark SQL源码分析系列文章*/ 前几篇文章介绍了Spark SQL的Catalyst的核心运行流程.SqlParser,和Analyzer,本来打算直接写Optimizer的,但是发现 ...

随机推荐

  1. Typora——自定义设置

    Typora提供自定义设置,在偏好设置里面,有一个主题文件夹,如果对界面的样式进行设定,可以添加一个css文件,命名规范是 github.user.css,下面代码会对h1~h4进行自动序列化 bod ...

  2. org.apache.jasper.JasperException: javax.el.PropertyNotFoundException: Property [xxx] not readable on type [xxx]

    由于javaBean中的属性是custFullName,所以在使用jsp的时候,通过el表达式获取属性的值<td>${m.CustFullName}</td>.但是加载页面的时 ...

  3. HDU_1556_线段树区间更新

    Color the ball Time Limit: 9000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)To ...

  4. 服务器主机&软件性能测试自定标准

    PS:最近一直致力于代理ip的服务搭建,其中就要根据客户群体的不同来测试搭建环境和搭建软件的性能,但是不同的客户群体所处的环境和使用的软件是不同的,而业内又没有一套完整的评估方法.在忽略网络本身来讲, ...

  5. CAD指定区域绘制一个jpg文件

    主要用到函数说明: _DMxDrawX::DrawToJpg 把指定区域的内容绘制一个jpg文件中.详细说明如下: 参数 说明 BSTR sJpgFilePath Jpg文件名 DOUBLE dLbx ...

  6. vuex与redux,我们都一样

    vuex与redux的主要区别: redux:生成的全局数据流是通过每个组件的props逐层传递到各个子组件的,通过@connect装饰器绑定在this.props上面. vuex :生成的全局数据则 ...

  7. Python&机器学习总结(二)

    ① Python中的Sort Python中的内建排序函数有 sort()和sorted()两个 list.sort(func=None, key=None, reverse=False(or Tru ...

  8. Sublime 添加右键快捷(其他软件同样方法)

    1.进入注册表 开始-->运行-->regedit(或直接win+r,输入regedit)-->回车 2.进入shell目录 [HKEY_CLASSES_ROOT\*\shell] ...

  9. 如何用纯 CSS 创作一个按钮文字滑动特效

    效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. 在线预览 https://codepen.io/zhang-ou/pen/GdpPLE 可交互视频教 ...

  10. PHP 生成器Generators的入门理解和学习

    什么是生成器Generators 生成器允许你在 foreach 代码块中写代码来迭代一组数据而不需要在内存中创建一个数组, 那会使你的内存达到上限,或者会占据可观的处理时间.相反,你可以写一个生成器 ...