spark sql中踩到的一个坑,自定义Udf会执行多次,即使在已经cache table的情况下

来源:互联网 发布:虚拟机上装linux 编辑:程序博客网 时间:2024/04/29 20:15

spark sql中踩到的一个坑,自定义Udf会执行多次,即使在已经cache table的情况下:  https://issues.apache.org/jira/browse/SPARK-15282

以前也没发现这个问题,因为如果一个UDF是无状态的话,其实执行多次并不会导致结果的最终一致性被打破,说来也巧啊,最近写了一个UDF里面封装了redis的hsetnx操作

  val hsetnx_expire = (key: String, field: String, value: String, expire: Int) => {      if (field != null && field.trim.length > 0) {        CommonRedisPool.withJedis(jedis => {          try {            val ret = jedis.hsetnx(key, field, StringUtils.defaultString(value, ""))            if (expire > 0) {              jedis.expire(key, expire)            }            if (ret == 1) true else false          } catch {            case e: Exception =>              throw new RuntimeException(s"===================${e.getMessage},key:$key,field:$field,value:$value")          }        })      } else {        false      }    }

这个udf要是被执行了多次那就要了命了,会多次尝试往redis里插入数据,如果插入一个不存在的field,那么第一次返回为true,再执行返回的是false。

而我恰好要用到这个返回结果来判断是不是一个新设备ID。

举个例子:

 cache table my_test as  select  *,                                    hsetnx_expire_day(concat('sdk_stat_pay_dt.' , dt),pay_uid,channel) as is_pay_dt,                                    hsetnx_expire_day(concat('sdk_stat_pay_dt.',dt,'.',hr),pay_uid,channel) as is_pay_hr                            from cache_xyzs_sdk_stat                            where type='order'  and isnotnull(pay_uid)
我这里讲每天的新增充值uid保存进redis,然后将执行结果做出来一个新的字段is_pay_dt(注意我上面已经是cache table my_test了),然后我再去执行查询这个字段的时候:

select * from my_test。我曹,返回的是is_pay_dt返回的是false。根据在udf中打日志追踪,发现UDF被执行了多次,虽然cache了,但是还是会重新计算。

这应该算是dataframe上的一个bug,因为执行dataframe.rdd.cache(),在将rdd重新注册成表,再去查询,是不会再重新计算新字段了。


目前我尝试了两种方式来绕过这个问题,

1:就是上面说的,把dataframe.rdd.cache(),在将rdd重新注册成表来进行查询,这种方式有个问题,没法uncache table。只能使用前面注册的rdd.unpersist

2: 简单粗暴一点,将结果collect回来,重新注册表,后面不用的时候可以uncache table。缺点就是collect到driver上如果数据量大的话其实有一些影响。


另外在使用有状态的UDF的时候要特别的谨慎小心,连嵌套子查询都不能有:

select * from
select  hsetnx_expire_day(concat('sdk_stat_pay_dt.' , dt),pay_uid,channel) as is_pay_dt
from cache_xyzs_sdk_stat
) a

这种也会导致里面的hsetnx_expire_day被执行两次,然后最终的is_pay_dt也会返回个false。

所以要有状态, 一定保证一层查询,然后collect结果回来重新注册表,这也是我现在用的方法,虽然有点粗糙。

0 0
原创粉丝点击