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

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
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值