spark hiveUDF transient的重要性

背景

最近在写hiveUDF的时候,遇到了一些反序列的问题,具体的报错如下:

Caused by: org.apache.spark.SparkException: Job aborted due to stage failure: Task 11 in stage 6.0 failed 4 times, most recent failure: Lost task 11.3 in stage 6.0 (TID 105) (dw-csprd-bigdata-athena-dn-096.shizhuang-inc.com executor 3): com.esotericsoftware.kryo.KryoException: Unable to find class: scala.collection.immutable.HashMap$$$Lambda$9/282828951
Serialization trace:
mergef$1 (scala.collection.immutable.HashMap$$anon$1)
defaultMerger (scala.collection.immutable.HashMap$)
_2 (scala.Tuple2)
head (scala.collection.immutable.$colon$colon)
factories (com.fasterxml.jackson.module.scala.deser.UnsortedMapDeserializerModule$$anon$1)
_additionalDeserializers (com.fasterxml.jackson.databind.cfg.DeserializerFactoryConfig)
_factoryConfig (com.fasterxml.jackson.databind.deser.BeanDeserializerFactory)
_factory (com.fasterxml.jackson.databind.deser.DefaultDeserializationContext$Impl)
_deserializationContext (com.fasterxml.jackson.databind.ObjectMapper)
objectMapper (xxx.xxxUDF)
	at com.esotericsoftware.kryo.util.DefaultClassResolver.readName(DefaultClassResolver.java:160)
	at com.esotericsoftware.kryo.util.DefaultClassResolver.readClass(DefaultClassResolver.java:133)
	at com.esotericsoftware.kryo.Kryo.readClass(Kryo.java:693)
	at org.apache.hadoop.hive.ql.exec.SerializationUtilities$KryoWithHooks.readClass(SerializationUtilities.java:181)
	at com.esotericsoftware.kryo.serializers.ObjectField.read(ObjectField.java:118)
	at com.esotericsoftware.kryo.serializers.FieldSerializer.read(FieldSerializer.java:543)
	at com.esotericsoftware.kryo.Kryo.readObject(Kryo.java:731)

分析

我们的代码类似如下:

class xxxUDF extends GenericUDF {
  @transient
  var argumentOIs: Array[ObjectInspector] = _

  val objectMapper = new ObjectMapper()
  objectMapper.registerModule(DefaultScalaModule)
  objectMapper.configure(Feature.ALLOW_UNQUOTED_FIELD_NAMES, true)
  objectMapper.configure(Feature.ALLOW_SINGLE_QUOTES, true)

  val result: Text = new Text()

其中spark的配置是使用kryo序列化,spark.serializer=org.apache.spark.serializer.KryoSerializer

可以看到是objectMapper类在driver端在把UDF传给executor的时候,需要做UDF的序列化,而序列化的时候,就会把objectMapper字段进行序列化,
这样在executor端进行task.run的时候会把 objectMapper反序列化出来,这个时候如果对应的类的成员方法如果没有进行kryo的注册,就会直接报序列化的错误,
而spark目前的默认注册的kryo类在KryoSerializer.scala中,如下:

...
 kryo.register(None.getClass)
 kryo.register(Nil.getClass)
 kryo.register(Utils.classForName("scala.collection.immutable.$colon$colon"))
 kryo.register(Utils.classForName("scala.collection.immutable.Map$EmptyMap$"))
 kryo.register(classOf[ArrayBuffer[Any]])
...

解决

在objectMapper加上@transient注解,使该对象不被序列化,这样在反序列化的时候,就不会反序列化该对象

### Hive UDF 和 UDAF 函数的用法及区别 #### 定义与功能差异 在Hive中,用户定义函数(User Defined Functions, UDF)允许开发者创建自定义逻辑来处理单个输入行并返回单一输出值。而用户聚合函数(User Defined Aggregation Function, UDAF),则用于执行多行数据上的累积计算操作,最终产生一个汇总结果[^1]。 对于Generic UDF和UDAF而言,ObjectInspector是一个核心概念,它提供了访问复杂类型字段的方法以及转换不同类型的对象的能力。 #### 使用场景对比 - **UDF** - 主要应用于需要对每一行的数据单独应用某种变换的情况。 - 实现相对简单,只需继承`org.apache.hadoop.hive.ql.udf.generic.GenericUDF`类,并重写evaluate方法即可实现特定业务逻辑。 - **UDAF** - 更适合于统计分析等场合下,比如求平均数、总和之类的运算。 - 需要实现更复杂的接口,通常涉及初始化(`init`)、迭代更新状态(`iterate`)、合并部分结果(`merge`)以及终止返回最终结果(`terminate`)四个阶段的操作。 #### 示例代码展示 ##### 创建简单的字符串长度计算UDF ```java public class StringLength extends GenericUDF { @Override public ObjectInspector initialize(ObjectInspector[] args) throws UDFArgumentException { return PrimitiveObjectInspectorFactory.writableIntObjectInspector; } @Override public DeferredObject[] forward(DeferredObject[] args) throws UDFArgumentLengthException { Text str = (Text)args[0].get(); int length = str.getLength(); return new DeferredObject[]{new DeferredJavaObject(new IntWritable(length))}; } } ``` ##### 构建基本的最大值查找UDAF ```java @Description(name="max", value="_FUNC_(col) - Returns the maximum value of col") public final class Max extends AbstractAggregator<DoubleWritable> implements Serializable { private transient Double max; protected void reset() { this.max = null; } protected void iterate(DoubleWritable val){ double v = val.get(); if(max==null || v > max.doubleValue()){ max=new Double(v); } } protected DoubleWritable terminate(){ return new DoubleWritable((max!=null)?max.doubleValue():Double.NaN); } // Merge function omitted for brevity... } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值