Spark UDF变长参数的二三事儿

简介:

在复杂业务逻辑中,我们经常会用到Spark的UDF,当一个UDF需要传入多列的内容并进行处理时,UDF的传参该怎么做呢? 下面通过变长参数引出,逐一介绍三种可行方法以及一些不可行的尝试...

引子

变长参数对于我们来说并不陌生,在Java里我们这么写


 
 
  1. public void varArgs(String... args) 

在Scala里我们这么写


 
 
  1. def varArgs(cols: String*): String 

而在Spark里,很多时候我们有自己的业务逻辑,现成的functions满足不了我们的需求,而当我们需要处理同一行的多个列,将其经过我们自己的逻辑合并为一个列时,变长参数及其变种实现可以给我们提供帮助。

但是在Spark UDF里我们是 无法使用变长参数传值 的,但之所以本文以变长参数开头,是因为需求起于它,而通过对它进行变换,我们可以使用变长参数或Seq类型来接收参数。

下面通过Spark-Shell来做演示,以下三种方法都可以做到多列传参,分别是

  • 变长参数(接受array类型)
  • Seq类型参数(接受array类型)
  • Row类型参数(接受struct类型)

变长参数类型的UDF

定义UDF方法


 
 
  1. def myConcatVarargs(sep: String, cols: String*): String = cols.filter(_ != null).mkString(sep) 

注册UDF函数

由于变长参数只能通过方法定义,所以这里使用部分应用函数来转换


 
 
  1. val myConcatVarargsUDF = udf(myConcatVarargs _) 

可以看到该UDF的定义如下


 
 
  1. UserDefinedFunction(<function2>,StringType,List(StringType, ArrayType(StringType,true))) 

也即变长参数转换为了ArrayType,而且函数是只包括两个参数,所以变长参数列表由此也可看出无法使用的。

变长参数列表传值

我们构造一个DataFrame如下


 
 
  1. val df = sc.parallelize(Array(("aa""bb""cc"),("dd","ee","ff"))).toDF("A""B""C"

然后直接传入多个String类型的列到myConcatVarargsUDF


 
 
  1. df.select(myConcatVarargsUDF(lit("-"), col("A"), col("B"), col("C"))).show 

结果出现如下报错


 
 
  1. java.lang.ClassCastException: anonfun$1 cannot be cast to scala.Function4 

由此可以看出,使用变长参数列表的方式Spark是不支持的,它会被识别为四个参数的函数,而UDF确是被定义为两个参数而不是四个参数的函数!

变换:使用array()转换做第二个参数

我们使用Spark提供的array() function来转换参数为Array类型


 
 
  1. df.select(myConcatVarargsUDF(lit("-"), array(col("A"), col("B"), col("C")))).show 

结果如下


 
 
  1. +-------------------+ 
  2. |UDF(-,array(A,B,C))| 
  3. +-------------------+ 
  4. |           aa-bb-cc| 
  5. |           dd-ee-ff| 
  6. +-------------------+ 

由此可以看出,使用变长参数构造的UDF方法,可以通过构造Array的方式传参,来达到多列合并的目的。

使用Seq类型参数的UDF

上面提到,变长参数最后被转为ArrayType,那不禁要想我们为嘛不使用Array或List类型呢?

实际上在UDF里,类型并不是我们可以随意定义的,比如使用List和Array就是不行的,我们自己定义的类型也是不行的,因为这涉及到数据的序列化和反序列化。

以Array/List为示例的错误

下面以Array类型为示例

定义函数


 
 
  1. val myConcatArray = (cols: Array[String], sep: String) => cols.filter(_ != null).mkString(sep) 

注册UDF


 
 
  1. val myConcatArrayUDF = udf(myConcatArray) 

可以看到给出的UDF签名是


 
 
  1. UserDefinedFunction(<function2>,StringType,List()) 

应用UDF


 
 
  1. df.select(myConcatArrayUDF(array(col("A"), col("B"), col("C")), lit("-"))).show 

会发现报错


 
 
  1. scala.collection.mutable.WrappedArray$ofRef cannot be cast to [Ljava.lang.String 

同样List作为参数类型也会报错,因为反序列化的时候无法构建对象,所以List和Array是无法直接作为UDF的参数类型的

以Seq做参数类型

定义调用如下


 
 
  1. val myConcatSeq = (cols: Seq[Any], sep: String) => cols.filter(_ != null).mkString(sep)  
  2. val myConcatSeqUDF = udf(myConcatSeq)  
  3. df.select(myConcatSeqUDF(array(col("A"), col("B"), col("C")), lit("-"))).show 

结果如下


 
 
  1. +-------------------+ 
  2. |UDF(array(A,B,C),-)| 
  3. +-------------------+ 
  4. |           aa-bb-cc| 
  5. |           dd-ee-ff| 
  6. +-------------------+ 

使用Row类型参数的UDF

我们可以使用Spark functions里struct方法构造结构体类型传参,然后用Row类型接UDF的参数,以达到多列传值的目的。


 
 
  1. def myConcatRow: ((Row, String) => String) = (row, sep) => row.toSeq.filter(_ != null).mkString(sep)  
  2. val myConcatRowUDF = udf(myConcatRow)  
  3. df.select(myConcatRowUDF(struct(col("A"), col("B"), col("C")), lit("-"))).show 

可以看到UDF的签名如下


 
 
  1. UserDefinedFunction(<function2>,StringType,List()) 

结果如下


 
 
  1. +--------------------+ 
  2. |UDF(struct(A,B,C),-)| 
  3. +--------------------+ 
  4. |            aa-bb-cc| 
  5. |            dd-ee-ff| 
  6. +--------------------+ 

使用Row类型还可以使用模式提取,用起来会更方便


 
 
  1. row match { 
  2.   case Row(aa:String, bb:Int) => 

最后

对于上面三种方法,变长参数和Seq类型参数都需要array的函数包装为ArrayType,而使用Row类型的话,则需要struct函数构建结构体类型,其实都是为了数据的序列化和反序列化。三种方法中,Row的方式更灵活可靠,而且支持不同类型并且可以明确使用模式提取,用起来相当方便。

而由此我们也可以看出,UDF不支持List和Array类型的参数,同时 自定义参数类型 如果没有混合Spark的特质实现序列化和反序列化,那么在UDF里也是 无法用作参数类型 的。当然,Seq类型是可以 的,可以接多列的数组传值。

此外,我们也可以使用柯里化来达到多列传参的目的,只是不同参数个数需要定义不同的UDF了。


本文作者:佚名

来源:51CTO

相关文章
|
14天前
|
分布式计算 大数据 Apache
ClickHouse与大数据生态集成:Spark & Flink 实战
【10月更文挑战第26天】在当今这个数据爆炸的时代,能够高效地处理和分析海量数据成为了企业和组织提升竞争力的关键。作为一款高性能的列式数据库系统,ClickHouse 在大数据分析领域展现出了卓越的能力。然而,为了充分利用ClickHouse的优势,将其与现有的大数据处理框架(如Apache Spark和Apache Flink)进行集成变得尤为重要。本文将从我个人的角度出发,探讨如何通过这些技术的结合,实现对大规模数据的实时处理和分析。
46 2
ClickHouse与大数据生态集成:Spark & Flink 实战
|
1月前
|
存储 分布式计算 算法
大数据-106 Spark Graph X 计算学习 案例:1图的基本计算、2连通图算法、3寻找相同的用户
大数据-106 Spark Graph X 计算学习 案例:1图的基本计算、2连通图算法、3寻找相同的用户
58 0
|
1月前
|
消息中间件 分布式计算 NoSQL
大数据-104 Spark Streaming Kafka Offset Scala实现Redis管理Offset并更新
大数据-104 Spark Streaming Kafka Offset Scala实现Redis管理Offset并更新
38 0
|
1月前
|
消息中间件 存储 分布式计算
大数据-103 Spark Streaming Kafka Offset管理详解 Scala自定义Offset
大数据-103 Spark Streaming Kafka Offset管理详解 Scala自定义Offset
81 0
|
15天前
|
SQL 机器学习/深度学习 分布式计算
Spark快速上手:揭秘大数据处理的高效秘密,让你轻松应对海量数据
【10月更文挑战第25天】本文全面介绍了大数据处理框架 Spark,涵盖其基本概念、安装配置、编程模型及实际应用。Spark 是一个高效的分布式计算平台,支持批处理、实时流处理、SQL 查询和机器学习等任务。通过详细的技术综述和示例代码,帮助读者快速掌握 Spark 的核心技能。
41 6
|
13天前
|
存储 分布式计算 Hadoop
数据湖技术:Hadoop与Spark在大数据处理中的协同作用
【10月更文挑战第27天】在大数据时代,数据湖技术凭借其灵活性和成本效益成为企业存储和分析大规模异构数据的首选。Hadoop和Spark作为数据湖技术的核心组件,通过HDFS存储数据和Spark进行高效计算,实现了数据处理的优化。本文探讨了Hadoop与Spark的最佳实践,包括数据存储、处理、安全和可视化等方面,展示了它们在实际应用中的协同效应。
53 2
|
14天前
|
存储 分布式计算 Hadoop
数据湖技术:Hadoop与Spark在大数据处理中的协同作用
【10月更文挑战第26天】本文详细探讨了Hadoop与Spark在大数据处理中的协同作用,通过具体案例展示了两者的最佳实践。Hadoop的HDFS和MapReduce负责数据存储和预处理,确保高可靠性和容错性;Spark则凭借其高性能和丰富的API,进行深度分析和机器学习,实现高效的批处理和实时处理。
53 1
|
15天前
|
分布式计算 大数据 OLAP
AnalyticDB与大数据生态集成:Spark & Flink
【10月更文挑战第25天】在大数据时代,实时数据处理和分析变得越来越重要。AnalyticDB(ADB)是阿里云推出的一款完全托管的实时数据仓库服务,支持PB级数据的实时分析。为了充分发挥AnalyticDB的潜力,将其与大数据处理工具如Apache Spark和Apache Flink集成是非常必要的。本文将从我个人的角度出发,分享如何将AnalyticDB与Spark和Flink集成,构建端到端的大数据处理流水线,实现数据的实时分析和处理。
47 1
|
25天前
|
分布式计算 大数据 Apache
利用.NET进行大数据处理:Apache Spark与.NET for Apache Spark
【10月更文挑战第15天】随着大数据成为企业决策和技术创新的关键驱动力,Apache Spark作为高效的大数据处理引擎,广受青睐。然而,.NET开发者面临使用Spark的门槛。本文介绍.NET for Apache Spark,展示如何通过C#和F#等.NET语言,结合Spark的强大功能进行大数据处理,简化开发流程并提升效率。示例代码演示了读取CSV文件及统计分析的基本操作,突显了.NET for Apache Spark的易用性和强大功能。
34 1
|
1月前
|
消息中间件 分布式计算 Kafka
大数据平台的毕业设计02:Spark与实时计算
大数据平台的毕业设计02:Spark与实时计算