You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
scala.None$ is not a valid external type for schema of string
at org.apache.spark.sql.catalyst.expressions.GeneratedClass$SpecificUnsafeProjection.StaticInvoke_3$(Unknown Source)
at org.apache.spark.sql.catalyst.expressions.GeneratedClass$SpecificUnsafeProjection.writeFields_0_2$(Unknown Source)
at org.apache.spark.sql.catalyst.expressions.GeneratedClass$SpecificUnsafeProjection.apply(Unknown Source)
at org.apache.spark.sql.catalyst.encoders.ExpressionEncoder$Serializer.apply(ExpressionEncoder.scala:211)
... 36 common frames omitted
然后,我们就找到那个有问题的字段,并且看一下这个字段和其他字段的区别,这里略过排查过程,给出现象,那就是有问题的字段是包含空值,而没有问题的字段是不包含空值的,这也能印证我们刚看到的异常信息 scala.None$ is not a valid external type for schema of string
privatelazyvalerrMsg=s" is not a valid external type for schema of ${expected.simpleString}"privatelazyvalcheckType: (Any) =>Boolean= expected match {
case_: DecimalType=>
(value: Any) => {
value.isInstanceOf[java.math.BigDecimal] || value.isInstanceOf[scala.math.BigDecimal] ||
value.isInstanceOf[Decimal]
}
case_: ArrayType=>
(value: Any) => {
value.getClass.isArray || value.isInstanceOf[Seq[_]]
}
case _ =>// 这里抛出了异常,因为valdataTypeClazz=ScalaReflection.javaBoxedType(dataType)
(value: Any) => {
dataTypeClazz.isInstance(value)
}
}
overridedefeval(input: InternalRow):Any= {
valresult= child.eval(input)
if (checkType(result)) {
result
} else {
thrownewRuntimeException(s"${result.getClass.getName}$errMsg")
}
}
RowEncoder代码:
valconvertedField=if (field.nullable) {
If(
Invoke(inputObject, "isNullAt", BooleanType, Literal(index) ::Nil),
// Because we strip UDTs, `field.dataType` can be different from `fieldValue.dataType`.// We should use `fieldValue.dataType` here.Literal.create(null, fieldValue.dataType),
fieldValue
)
} else {
fieldValue
}
private[this] deftoCatalystRDD(
relation: LogicalRelation,
output: Seq[Attribute],
rdd: RDD[Row]):RDD[InternalRow] = {
if (relation.relation.needConversion) {
execution.RDDConversions.rowToRowRdd(rdd, output.map(_.dataType))
} else {
rdd.asInstanceOf[RDD[InternalRow]]
}
}
/** * Convert the objects inside Row into the types Catalyst expected.*/defrowToRowRdd(data: RDD[Row], outputTypes: Seq[DataType]):RDD[InternalRow] = {
data.mapPartitions { iterator =>valnumColumns= outputTypes.length
valmutableRow=newGenericInternalRow(numColumns)
valconverters= outputTypes.map(CatalystTypeConverters.createToCatalystConverter)
iterator.map { r =>vari=0while (i < numColumns) {
mutableRow(i) = converters(i)(r(i))
i +=1
}
mutableRow
}
}
}
defcreateToCatalystConverter(dataType: DataType):Any=>Any= {
if (isPrimitive(dataType)) {
// Although the `else` branch here is capable of handling inbound conversion of primitives,// we add some special-case handling for those types here. The motivation for this relates to// Java method invocation costs: if we have rows that consist entirely of primitive columns,// then returning the same conversion function for all of the columns means that the call site// will be monomorphic instead of polymorphic. In microbenchmarks, this actually resulted in// a measurable performance impact. Note that this optimization will be unnecessary if we// use code generation to construct Scala Row -> Catalyst Row converters.defconvert(maybeScalaValue: Any):Any= {
if (maybeScalaValue.isInstanceOf[Option[Any]]) {
maybeScalaValue.asInstanceOf[Option[Any]].orNull
} else {
maybeScalaValue
}
}
convert
} else {
getConverterForType(dataType).toCatalyst
}
}
背景
最近在忙着升级spark3,我们自己改的代码基本都已经搞定了,但是外部数据源es还有些问题,这篇文章主要说一下存在的问题和如何修复
现象
我们升级spark3之后,集成测试有些索引是能正常工作的,有些索引却不能读取了,主要的异常信息如下:
分析
对于一个问题的分析有多种方式,一种是正向跟踪源代码,找到完全出问题的地方,这种方式更加严谨,但是如果链路很长的时候,跟起来比较累,所以正向跟踪的我等下会在最后补充一下,这里我们说另一种排查的方式,那就是多次实验,观察现象,大胆假设,小心验证,接下来我们先用这种方式排查解决下该问题
大胆假设,小心验证
首先,我们从测试结果看到,并不是所有的索引都不能读了,而是部分索引不能读,我们的第一感觉就是是不是某种类型不支持?如果我们能找到那个不能读的类型,就能针对性的修复了
接下来,我们就来找一下相关代码,首先,我们看看能不能让es少读几个字段,这样我们就可以通过二分,很快的找到有问题的字段了
我们知道,如果用户不指定schema的情况下,es会通过index的mapping信息获取到index的schema
这部分逻辑具体可以看下
ElasticsearchRelation
的lazySchema
,这里不展开,而如果用户提供了schema就会用用户的schema,所以我们只要把schema塞到es的dataframe里即可,至于塞schema的逻辑,也非常简单,我们只需要copy出
EsSparkSQL
的esDF
代码,然后增加schema即可然后,我们就找到那个有问题的字段,并且看一下这个字段和其他字段的区别,这里略过排查过程,给出现象,那就是有问题的字段是包含空值,而没有问题的字段是不包含空值的,这也能印证我们刚看到的异常信息
scala.None$ is not a valid external type for schema of string
接着,我们找一下es-spark里什么时候会用到None,这里直接文本搜索一下,基本就可以找到了,es-spark包里用None的地方一共有五处,其中
ScalaValueWriter
和RowValueReader
以及DefaultSource
里都是用在条件判断上,真正赋予None的是ScalaValueReader
的nullValue
方法至于调用的地方,主要是
ScalaValueReader
的checkNull
方法这也符合我们刚看到的现象,当出现空值的时候,es-spark会给他赋值成None,但是None在spark3上不能用来填充那些空值,所以出现了上述异常,那么我们就把这一行代码改成
然后编译发包,验证通过
更近一步,正向分析
到这里,问题是解决了,但是有好奇心的小伙伴肯定想到了,为什么这份代码能在spark2.4正常运行,到了spark3就不行了呢,我们还是希望知道到底spark哪里改了,导致这部分代码的行为改变了
spark3
首先,我们找到errorMsg的地方:
ValidateExternalType
,这里可以通过文本搜索找到RowEncoder代码:
最终生成的代码是
也就是如果数据是null的话,则返回null,否则的话,当成UTF8String去处理,进入校验环节,然后抛出了我们刚才看到异常
调用的入口在
DataSourceStrategy
我们这里梳理下3的逻辑
spark2.4
但是spark2.4没有报错,那么我们从入口看一下spark2.4的逻辑
关键就是
createToCatalystConverter
中的toCatalyst
在这里成功的处理了Option的情况
找了下git log,应该是23262
出于性能的考虑,把rowToRowRdd换成了RowEncoder,感兴趣的小伙伴可以自行跟踪下相关逻辑
The text was updated successfully, but these errors were encountered: