部分内容原文地址:

AISeekOnline:Spark Caused by: java.io.NotSerializableException 序列化异常踩过的坑

zhou_jun:Spark运行程序异常信息: org.apache.spark.SparkException: Task not serializable 解决办法



一、Spark聚合函数特殊场景

在对数据进行统计分析时,如果对指标进行聚合运算,而待查询的字段中还包含了维度,则原则上我们还需要按照维度字段进行分组。倘若这个聚合运算为sum函数,分组之后就相当于分类汇总了。有一种特殊场景是我们对指标执行了sum聚合,查询字段也包含了维度,但我们不希望对维度分组。例如:

select name, role, sum(income) from employee

虽然返回的结果挺奇怪,因为它事实上是针对整张表的income进行了求和运算,与name、role无关。查询结果中返回的其实是第一条记录的name与role。但至少在MySQL中,这样的SQL语法是正确的。

但是在Spark中,执行这样的SQL语句,则会抛出org.apache.spark.sql.AnalysisException异常:

org.apache.spark.sql.AnalysisException: expression 'employee.`name`' is neither present in the group by, nor is it an aggregate function. Add to group by or wrap in first() (or first_value) if you don't care which value you get.

这是因为Spark SQL在执行SQL语句时,事先会调用CheckAnalysis的checkAnalysis方法对LogicalPlan进行检查:

def checkAnalysis(plan: LogicalPlan): Unit = {
case e: Attribute if groupingExprs.isEmpty =>
// Collect all [[AggregateExpressions]]s.
val aggExprs = aggregateExprs.filter(_.collect {
case a: AggregateExpression => a
}.nonEmpty)
failAnalysis(
s"grouping expressions sequence is empty, " +
s"and '${e.sql}' is not an aggregate function. " +
s"Wrap '${aggExprs.map(_.sql).mkString("(", ", ", ")")}' in windowing " +
s"function(s) or wrap '${e.sql}' in first() (or first_value) " +
s"if you don't care which value you get."
)
case e: Attribute if !groupingExprs.exists(_.semanticEquals(e)) =>
failAnalysis(
s"expression '${e.sql}' is neither present in the group by, " +
s"nor is it an aggregate function. " +
"Add to group by or wrap in first() (or first_value) if you don't care " +
"which value you get.")
}

按照给出的SQL语句,groupingExprs应该是Empty才对,然而根据抛出的错误提示,在对分析语句进行检查时,却是走的后一个模式匹配分支,即e: Attribute if !groupingExprs.exists(_.semanticEquals(e))。

根据提示,在不增加group by的情况下,需要对select中的字段包裹一个first()或者first_value()函数,如下所示:

spark.sql("select first(name),first(role), sum(income) from employee")

这里的维度包含name和role。如果添加了group by,但只针对其中的一个维度进行了分组,那么对于缺少分组的维度,也当用first()函数来包裹才对。

第一部分内容转载自:张逸-简书

二、spark sql group by

hiveContext.sql("select time,count(*) from page_click group by id").collect.foreach(println)
//报错
org.apache.spark.sql.AnalysisException: expression 'page_click.`time`' is neither present in the group by, nor is it an aggregate function. Add to group by or wrap in first() (or first_value) if you don't care which value you get.;
at org.apache.spark.sql.catalyst.analysis.CheckAnalysis$class.failAnalysis(CheckAnalysis.scala:40)
at org.apache.spark.sql.catalyst.analysis.Analyzer.failAnalysis(Analyzer.scala:58)
at org.apache.spark.sql.catalyst.analysis.CheckAnalysis$$anonfun$checkAnalysis$1.org$apache$spark$sql$catalyst$analysis$CheckAnalysis$class$$anonfun$$checkValidAggregateExpression$1(CheckAnalysis.scala:218)
at org.apache.spark.sql.catalyst.analysis.CheckAnalysis$$anonfun$checkAnalysis$1$$anonfun$apply$6.apply(CheckAnalysis.scala:245)
at org.apache.spark.sql.catalyst.analysis.CheckAnalysis$$anonfun$checkAnalysis$1$$anonfun$apply$6.apply(CheckAnalysis.scala:245)
at scala.collection.immutable.List.foreach(List.scala:381)
at org.apache.spark.sql.catalyst.analysis.CheckAnalysis$$anonfun$checkAnalysis$1.apply(CheckAnalysis.scala:245)
at org.apache.spark.sql.catalyst.analysis.CheckAnalysis$$anonfun$checkAnalysis$1.apply(CheckAnalysis.scala:67)
at org.apache.spark.sql.catalyst.trees.TreeNode.foreachUp(TreeNode.scala:126)
at org.apache.spark.sql.catalyst.analysis.CheckAnalysis$class.checkAnalysis(CheckAnalysis.scala:67)
at org.apache.spark.sql.catalyst.analysis.Analyzer.checkAnalysis(Analyzer.scala:58)
at org.apache.spark.sql.execution.QueryExecution.assertAnalyzed(QueryExecution.scala:49)
at org.apache.spark.sql.Dataset$.ofRows(Dataset.scala:64)
at org.apache.spark.sql.SparkSession.sql(SparkSession.scala:582)
at org.apache.spark.sql.SQLContext.sql(SQLContext.scala:682)
... 48 elided

time这一列在 group by的时候有多个查询结果,需要使用collect_set()一下。

scala>hiveContext.sql("select collect_set(time),count(*) from page_click group by id limit 10").collect.foreach(println)

第二部分内容转载自:time_exceed-CSDN

三、Spark Caused by: java.io.NotSerializableException 序列化异常踩过的坑

这部分之所以会进行转载学习,是因为在之前的项目中,DateTimeFormat这个方法和Redis初始化对象方法,会报Spark Caused by: java.io.NotSerializableException 这个错,故转载过来学习。

最近有需求需要在driver端创建好类实例,然后在rdd里面调用,但是使用过程中发现 Caused by: java.io.NotSerializableException,即序列化异常,通过查处网上资料发现是构建的类没有继承Serializable,没有继承Serializable的类是不会自动执行自动序列化操作的,因此我把构建的类继承了Serializable这个类,再次运行的时候发现依旧是序列化异常,百般不得其解。

序列异常天坑1(网上常见的)

在rdd外实例化的类没有继承Serializable,在实例化类在rdd中使用,如下代码块:

class ClassA {
def getClassName: String = this.getClass.getSimpleName
} object SerializableTest {
def main(args: Array[String]): Unit = {
val conf = new SparkConf(true)
.setMaster("local[*]")
.setAppName("SerializableTest")
.set("spark.rdd.compress", "true")
.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer") lazy val sc = new SparkContext(conf) val classA = new ClassA()
val rdd = sc.makeRDD(1 to 5) rdd.map(i => "getClassName in main -> " + classA.getClassName + s": $i")
.collect().foreach(println)
}
}

填坑方法

方法1:将ClassA修改为继承Serializable类:

class ClassA extends Serializable {
def getClassName: String = this.getClass.getSimpleName
}

方法2:将ClassA放在rdd里面进行实例化:

rdd.map(i => {
val classA = new ClassA
"getClassName in main -> " + classA.getClassName + s": $i"
}).collect().foreach(println)

方法3:将ClassA改成静态类,静态类自动实例化,在rdd里面直接调用其方法:

object ClassA {
def getClassName: String = this.getClass.getSimpleName
} object SerializableTest {
def main(args: Array[String]): Unit = {
val conf = new SparkConf(true)
.setMaster("local[*]")
.setAppName("SerializableTest")
.set("spark.rdd.compress", "true")
.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer") lazy val sc = new SparkContext(conf)
val rdd = sc.makeRDD(1 to 5) rdd.map(i => "getClassName in main -> " + ClassA.getClassName + s": $i")
.collect().foreach(println)
}
}

序列异常天坑2

在rdd里面调用类中某个类的方法报序列化异常,代码如下:

class ClassA {
def getClassName: String = this.getClass.getSimpleName
} class ClassB(sc: SparkContext) extends Serializable{
val classA = new ClassA() def fun(): Unit = {
val rdd = sc.makeRDD(1 to 5)
rdd.map(i => "getClassName in ClassB -> "+classA.getClassName + s": $i")
.collect.foreach(println)
}
} object SerializableTest {
def main(args: Array[String]): Unit = {
val conf = new SparkConf(true)
.setMaster("local[*]")
.setAppName("SerializableTest")
.set("spark.rdd.compress", "true")
.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer") lazy val sc = new SparkContext(conf)
val classB = new ClassB(sc)
val rdd = sc.makeRDD(1 to 5) rdd.map(i => "getClassName in main -> " + classB.classA.getClassName + s": $i")
.collect().foreach(println)
}
}

如上述,在rdd里面调用ClassB中属性ClassA中的方法报序列化异常

填坑方法

方法1:这个ClassB有点脑残,把ClassA作为属性实在不可取,如果只是为了达到调用ClassA内的方法,则可以让ClassB去继承ClassA

class ClassA extends Serializable {
def getClassName: String = this.getClass.getSimpleName
} class ClassB(sc: SparkContext) extends ClassA with Serializable{
} object SerializableTest {
def main(args: Array[String]): Unit = {
val conf = new SparkConf(true)
.setMaster("local[*]")
.setAppName("SerializableTest")
.set("spark.rdd.compress", "true")
.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer") lazy val sc = new SparkContext(conf)
val classB = new ClassB(sc)
val rdd = sc.makeRDD(1 to 5) rdd.map(i => "getClassName in main -> " + classB.getClassName + s": $i")
.collect().foreach(println)
}
}

方法2:在rdd外先把ClassB中ClassA取出来放到一个变量里面去,再在rdd里面调用该变量:

object SerializableTest {
def main(args: Array[String]): Unit = {
val conf = new SparkConf(true)
.setMaster("local[*]")
.setAppName("SerializableTest")
.set("spark.rdd.compress", "true")
.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer") lazy val sc = new SparkContext(conf) val classB = new ClassB(sc) val a = classB.classA val rdd = sc.makeRDD(1 to 5) rdd.map(i => "getClassName in main -> " + a.getClassName + s": $i")
.collect().foreach(println)
}
}

这种类似填坑1里面的,相当于重新new了一个ClassA

序列异常天坑3

在类ClassB中有方法fun,和属性classA,fun调用了classA中的方法:

class ClassA extends Serializable {
def getClassName: String = this.getClass.getSimpleName
} class ClassB(sc: SparkContext) extends Serializable{
val classA = new ClassA() def fun(): Unit = {
val rdd = sc.makeRDD(1 to 5)
rdd.map(i => "getClassName in ClassB -> "+classA.getClassName + s": $i")
.collect.foreach(println)
}
} object SerializableTest {
def main(args: Array[String]): Unit = {
val conf = new SparkConf(true)
.setMaster("local[*]")
.setAppName("SerializableTest")
.set("spark.rdd.compress", "true")
.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
lazy val sc = new SparkContext(conf)
val classB = new ClassB(sc)
classB.fun()
}
}

填坑方法

方法1:在fun里面不使用属性classA,而是在fun里面重新构建ClassA

def fun(): Unit = {
val classA = new ClassA()
val rdd = sc.makeRDD(1 to 5)
rdd.map(i => "getClassName in ClassB -> "+classA.getClassName + s": $i")
.collect.foreach(println)
}

这类似于天坑1的解决方式。但是很多时候我们的ClassA是一个比较全的工具类,不仅仅是在fun单个方法体里面调用,因此需要将放到ClassB作为属性。

方法2:与前面的一样,可以在fun方法里面的rdd前面先新增一个变量在调用

def fun(): Unit = {
val a = classA
val rdd = sc.makeRDD(1 to 5)
rdd.map(i => "getClassName in ClassB -> "+a.getClassName + s": $i")
.collect.foreach(println)
}

方法3:把ClassB修改成object修饰静态类:

class ClassA extends Serializable {
def getClassName: String = this.getClass.getSimpleName
} object ClassB extends Serializable{
val classA = new ClassA() def fun(sc: SparkContext): Unit = {
val rdd = sc.makeRDD(1 to 5)
rdd.map(i => "getClassName in ClassB -> "+classA.getClassName + s": $i")
.collect.foreach(println)
}
} object SerializableTest {
def main(args: Array[String]): Unit = {
val conf = new SparkConf(true)
.setMaster("local[*]")
.setAppName("SerializableTest")
.set("spark.rdd.compress", "true")
.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer") lazy val sc = new SparkContext(conf)
val classB = ClassB
classB.fun(sc)
}
}

四、org.apache.spark.SparkException: Task not serializable

错误信息:

org.apache.spark.SparkException: Task not serializable
at org.apache.spark.util.ClosureCleaner$.ensureSerializable(ClosureCleaner.scala:298)
at org.apache.spark.util.ClosureCleaner$.org$apache$spark$util$ClosureCleaner$$clean(ClosureCleaner.scala:288)
at org.apache.spark.util.ClosureCleaner$.clean(ClosureCleaner.scala:108)

问题原因:再对RDD进行操作时引用了类的成员变量而该成员变量无法被序列化所导致的。

例如:

object Test2 extends App{
val conf = new SparkConf().setAppName("RVM").setMaster("local")
val sc = new SparkContext(conf)
val matrix = new DenseMatrix(2,2,Array(1.0,2,3,4))
new Test(sc,matrix).run() } class Test(scc:SparkContext,PHI:DenseMatrix) extends Serializable{
val ts = 0.1
def run(): Unit ={
val rdds = scc.parallelize(0 to 3)
val a = rdds.map(
x =>{
PHI.toArray.apply(x)*x
}
)
a.collect.foreach(println(_))
}
}

出现“org.apache.spark.SparkException: Task not serializable"这个错误,一般是因为在map、filter等的参数使用了外部的变量,但是这个变量不能序列化。特别是当引用了某个类(经常是当前类)的成员函数或变量时,会导致这个类的所有成员(整个类)都需要支持序列化。解决这个问题最常用的方法有:

  1. 如果可以,将依赖的变量放到map、filter等的参数内部定义。这样就可以使用不支持序列化的类;
  2. 如果可以,将依赖的变量独立放到一个小的class中,让这个class支持序列化;这样做可以减少网络传输量,提高效率;
  3. 如果可以,将被依赖的类中不能序列化的部分使用transient关键字修饰,告诉编译器它不需要序列化。
  4. 将引用的类做成可序列化的。

总结

通过上面填坑过程发现如下规律:

1、在rdd应该外部变量类实例的时候,类需要继承Serializable

2、在非静态类中(class声明的类),若是类属性是一个对象,则该属性不能在rdd里面直接使用,尽管该对象是已经继承了Serializable,可以直接在rdd前将该属性赋值为一个变量,再在rdd里面调用该变量

Spark踩坑填坑-聚合函数-序列化异常的更多相关文章

  1. Phoenix踩坑填坑记录

    Phoenix踩坑填坑记录 Phoenix建表语句 如何添加二级索引 判断某表是否存在 判断索引是否存在 Date类型日期,条件判断 杂项 记录Phoenix开发过程中的填坑记录. 部分原文地址:ph ...

  2. Kafka踩坑填坑记录

    Kafka踩坑填坑记录 一.kafka通过Java客户端,消费者无法接收消息,生产者发送失败消息 二. 一.kafka通过Java客户端,消费者无法接收消息,生产者发送失败消息 在虚拟机上,搭建了3台 ...

  3. Linux踩坑填坑记录

    Linux踩坑填坑记录 yum安装失败[Errno 14] PYCURL ERROR 6 - "Couldn't resolve host 'mirrors.aliyun.com'" ...

  4. Cloudera Manager 5.9 和 CDH 5.9 离线安装指南及个人采坑填坑记

    公司的CDH早就装好了,一直想自己装一个玩玩,最近组了台电脑,笔记本就淘汰下来了,加上之前的,一共3台,就在X宝上买了CPU和内存升级了下笔记本,就自己组了个集群. 话说,好想去捡垃圾,捡台8核16线 ...

  5. 自制Amiibo 踩坑/填坑 指南

    去年买了台老版NS,后来得知有Amiibo这种东西的存在,但是学校附近都买不到. 再后来网上看见有人在X宝卖自制卡片,就寻思着是否能自己 DIY一套,于是掉坑里. 要想使用自制Amiibo,一共要做两 ...

  6. IdentityServer4结合AspNetCore.Identity实现登录认证踩坑填坑记录

    也可以自定义实现,不使用IdentityServer4.AspNetIdentity这个包,当然还要实现其他接口IResourceOwnerPasswordValidator. IProfileSer ...

  7. FreeSWITCH 安装配置的 各种坑, 填坑

    个人安装环境: OS:CentOS6.7 64位 FreeSWITCH Ver:1.6.17 一. 编译出错 安装 之前, 最好 先安装 这几个东西(如果有, 请忽略): yasm (有nasm的话 ...

  8. React Native填坑之旅--与Native通信之iOS篇

    终于开始新一篇的填坑之旅了.RN厉害的一个地方就是RN可以和Native组件通信.这个Native组件包括native的库和自定义视图,我们今天主要设计的内容是native库方面的只是.自定义视图的使 ...

  9. https填坑之旅

    Boss说,我们买了个权威证书,不如做全站式的https吧,让用户打开主页就能看到受信任的绿标.于是我们就开始了填坑之旅. [只上主域好不好?] 不好...console会报出一大堆warning因为 ...

随机推荐

  1. Jquery真的不难~第一回 编程基础知识

    Jquery真的不难~第一回 编程基础知识   回到目录 前言 说Jquery之前,先来学习一下Javascript(以后简称为JS)语言中的基础知识问题,其时对于每种编程语言来说基础知识都是大同小异 ...

  2. 关于领域驱动架构DDD思考

    一个高大上的概念领域驱动架构就这样展开. 开发了多年的软件,一直以来的习惯是拿到产品的需求 对照UI的图纸然后就干干干 碰到问题大不了找人沟通再次定义问题,最后交付.其实最后也能把一件事情完成 但如果 ...

  3. 美团在TIDB方面的实践

    摘自-https://www.v2ex.com/t/508094 一.背景和现状 在美团,基于 MySQL 构建的传统关系型数据库服务已经难于支撑公司业务的爆发式增长,促使我们去探索更合理的数据存储方 ...

  4. springboot 启动jar正确方式

    首先需要pom.xml配置一个插件: IDEA 在右侧执行顶上m图片按钮 在command Line 中执行clean package命令 执行打包注意 打完jar包后最好解压jar查看一下META- ...

  5. 简单4步,利用Prometheus Operator实现自定义指标监控

    本文来自Rancher Labs 在过去的文章中,我们花了相当大的篇幅来聊关于监控的话题.这是因为当你正在管理Kubernetes集群时,一切都会以极快的速度发生变化.因此有一个工具来监控集群的健康状 ...

  6. 五、Zookeeper、Hbase集群搭建

    一.前提 1.安装JDK 2.安装Hadoop 3.安装zoookeeper 1.加入zookeeper包,并解压tar -zxvf zookeeper-3.4.9.tar.gz 2.去/etc/pr ...

  7. 杭电OJ2010---水仙花数(c++)(方法:输出几个数之间用空格隔开,最后一个数没有空格)

    水仙花数 Problem Description 春天是鲜花的季节,水仙花就是其中最迷人的代表,数学上有个水仙花数,他是这样定义的: "水仙花数"是指一个三位数,它的各位数字的立方 ...

  8. nginx 重写去掉index.php

    if (!-e $request_filename) { rewrite ^/(.*)$ /index.php?s=$1 last; }

  9. Leetcode53. 最大子序列和

    问题 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和. 代码 贪心算法 核心思想就是检查之前 i-1 的元素和,如果小于零就舍弃--对应下面第六行 ...

  10. ctfhub技能树—信息泄露—备份文件下载—.DS_Store

    打开靶机 查看页面信息 使用dirsearch进行扫描 访问该网页,下载文件 使用Linux系统打开文件 发现一个特殊文件,使用浏览器打开 拿到flag 二.使用Python-dsstore工具查看该 ...