SparkSQL列数量比较多引发的Too many arguments in method signature in class file问题

1. 问题描述

我在写一个Spark程序的时候,做两个表的关联,其中一个表为feature表,一共有96个特征,我使用下面的 代码片 的时候

val geoCols = geoVec.columns.filterNot(c => Seq("geohash", "province", "zone_id").contains(c))

    val aggCols = geoCols.map(colName => sum(col(colName) * col("num_dwells")) as colName)
    val result = userPoi
      .join(broadcast(geoVec), Seq("geohash","zone_id","province"), "inner")
      .groupBy("user_id", "date_dt")
      .agg(aggCols.head, aggCols.tail: _*)

在运行的时候会报错

java.lang.ClassFormatError: org/apache/spark/sql/catalyst/expressions/GeneratedClass$GeneratedIteratorForCodegenStage5
	at org.apache.spark.sql.catalyst.expressions.GeneratedClass.generate(Unknown Source)
	at org.apache.spark.sql.execution.WholeStageCodegenExec$$anonfun$10.apply(WholeStageCodegenExec.scala:610)
	at org.apache.spark.sql.execution.WholeStageCodegenExec$$anonfun$10.apply(WholeStageCodegenExec.scala:608)
	at org.apache.spark.rdd.RDD$$anonfun$mapPartitionsWithIndex$1$$anonfun$apply$26.apply(RDD.scala:847)
	....

在运行日志里面还有下面的错误提示

java.lang.ClassFormatError: Too many arguments in method signature in class file org/apache/spark/sql/catalyst/expressions/GeneratedClass$GeneratedIteratorForCodegenStage5

2. 解决办法

增加一行:
.config("spark.sql.codegen.wholeStage", value = false)

初始化Spark环境的时候
val spark = SparkSession.builder()
      .appName("xxx")
      .config("spark.sql.codegen.wholeStage", value = false)
      .enableHiveSupport()
      .getOrCreate()

3. 原因简单剖析

这个错误一般是由于 JVM 的方法参数限制所导致的。JVM 对于方法的参数数量有一定的限制,通常情况下,方法的参数数量不应该超过 255 个。如果一个方法的参数数量超过了这个限制,JVM 就会抛出类似于 “Too many arguments in method signature” 的错误。
在 Spark 中,这个错误通常是由于代码生成器生成的代码包含了过多的参数所导致的。这个问题可以通过减少参数数量来解决。你可以尝试以下几种方法来解决这个问题:
减少参数数量:可以尝试将参数数量减少到 JVM 可以接受的范围内。如果你的代码中包含了过多的参数,可以考虑将一部分参数合并为一个对象,或者使用 Spark 的 Tuple 类型来代替。
禁用代码生成器:你可以尝试在 SparkSession 上设置 spark.sql.codegen.wholeStage 参数为 false,以禁用 Spark 的代码生成器,并在运行时使用解释器执行查询。这样可以避免生成的代码过于复杂,从而导致参数数量过多。
我的程序并没有超过255个参数,或许是中间过程产生了临时参数比较多,加起来超过了255个,但是并没有足够的时间去验证。

询问GPT得到的另外一个解释是:
如果在 Spark 2.x 中传递的参数数量超过了 JVM 的参数数量限制(约为 64KB),就会出现 Too many arguments in method signature 错误。这个错误通常是由于聚合表达式太多而导致的。也可能我的命名太长了,但是感觉很难超过64KB,需要打印相关的解析参数才可以确定,但是基本上可以确定的是参数数量较多导致的。

4. 其他解决方案

GPT给的方案:

为了避免这个问题,你可以尝试将聚合表达式分成多个小组,并分批执行聚合操作。具体来说,你可以将聚合表达式分成若干组,每组包含 10 到 20 个聚合表达式,然后分别使用 agg 函数执行聚合操作,并将结果合并起来。示例代码如下:

val aggCols = geoCols.map(colName => sum(col(colName) * col("num_dwells")) as colName)

val numGroups = (aggCols.length + 10 - 1) / 10 // 每组包含 10 个聚合表达式
val groupIndexes = (0 until numGroups).map(i => i * 10)
val groupCols = groupIndexes.map(i => aggCols.slice(i, i + 10))

val results = groupCols.map(cols => {
  userPoi
    .join(broadcast(geoVec), Seq("geohash","zone_id","province"), "inner")
    .groupBy("user_id", "date_dt")
    .agg(cols.head, cols.tail: _*)
})

val result = results.reduce((df1, df2) => df1.join(df2, Seq("user_id", "date_dt"), "inner"))

这个代码将聚合表达式分成了多个小组,并分别使用 agg 函数执行聚合操作。最后,将结果使用 join 函数合并起来。这样可以避免参数数量过多而导致的编译器错误。

由于在搜索引擎和stackoverflow上并不能很容易搜到解决方案,所以写篇日志记录下来,希望对遇到该问题的人有所帮助。

Logo

大数据从业者之家,一起探索大数据的无限可能!

更多推荐