Spqrk-SQL DataFrame foreachPartition 丢失数据,代码不执行,究竟是什么神仙操作

发现了一个非常诡异的问题,正在解决当中,把诡异问题记录下来,

有问题的是(代码一)示例,我的table.put(putList)这段代从效果上看没执行,从日志上我能看出来putList里的数据越来越多。putList.size()一直在增加

但是我如果把 val table: Table = HBaseConnectionManager.getConnection().getTable(TableName.valueOf(tableName))这个table的声明放在iterator.foreach的前边,就像代码示例二里的那么写,代码就可以执行,只是我每个Partition里都是一个独立的table,这问题就很大,我不是每个Partition里都有数据这导致我99%的table都是废操作,

除了浪费资源和浪费效率之外,时间长了还会出现异常:org.apache.hadoop.hbase.RegionTooBusyException,意思是我put次数太多了。所以这段代码必须优化,但是目前还没想到办法。

我这里有一个猜想,应该是每个Partition必须都提交之后才能提交,所以我代码2示例里每个数据table都创建了一个实例就会被执行,而放下边因为iterator.foreach不是每个Partition里都有数据所以,剩下的Partition相当于没执行或没执行完,而我有数据的Partition就一直在等其他的Partition有数据,所以没往下执行。

我的猜想好像是对了,昨天加班做了个实验,为此我决定简单的画一个图用来表达我的想法。图跟实现放下边了,代码三。

代码一:

df.foreachPartition(iterator => {
      var putList = new util.ArrayList[Put]

      iterator.foreach(message => {
        val key= message.getAs[String]("key")
        val requestMessage = message.getAs[Map[String, String]]("realtime")
        var put: Put = new Put(key.getBytes())
        requestMessage.keys.foreach {
          key => put.addColumn("data".getBytes(), key.getBytes(), requestMessage(key).getBytes())
        }
       
        putList.add(put)
      })
      println("list:" + putList.hashCode() + ":" + putList.size())
//      if (putList.size() > 0) {
      val table: Table = HBaseConnectionManager.getConnection().getTable(TableName.valueOf(tableName))
      table.put(putList)
      println("table:" + table.hashCode() + ":" + putList.size())
      table.close()
//        }
    }

代码二:

df.foreachPartition(iterator => {
      var putList = new util.ArrayList[Put]
      val table: Table = HBaseConnectionManager.getConnection().getTable(TableName.valueOf(tableName))
      iterator.foreach(message => {
        val key= message.getAs[String]("key")
        val requestMessage = message.getAs[Map[String, String]]("realtime")
        var put: Put = new Put(key.getBytes())
        requestMessage.keys.foreach {
          key => put.addColumn("data".getBytes(), key.getBytes(), requestMessage(key).getBytes())
        }
       
        putList.add(put)
      })
      println("list:" + putList.hashCode() + ":" + putList.size())
      //      if (putList.size() > 0) {
     
      table.put(putList)
      println("table:" + table.hashCode() + ":" + putList.size())
      table.close()
      //      }
    }

针对我的猜想我又做了个实验,实验成功了一半,我会把我的分析写下来,毕竟官方给的资料太少了,而且我也没找到我想看的部分。

下边首先是我的实验代码:if (putList.size() > 0)这个判断在代码一和二里都屏蔽了,但是开与不开的效果是一样的。

这里说一下我的猜想以及我对应的解决方案,但是这个方案只解决了一半,因为实现结果只成功了一半,数据只保存了一半。针对这个实验结果我下边会画一张图来解释我的理解。

先说一下我实验的方式,既然我的猜想是Partition必须全部执行,否则就都不执行,导致数据没进去,那我让Partition执行不就可以了吗,所以我先在iterator.foreach这行的下边执行了putList.clear(),毕竟putList是在iterator.foreach之前声明的。我在之后进行一下操作看看是否有效果。

另外我在iterator.foreach执行完成之后,执行了一行df.show(1),我的猜想是spark懒加载机制,那么我在进行一个action操作不就可以了,而我又不可能随便写个文件或者是别的输出,所以我用了个.show(1)显示一行数据。

实时证明我的实验成功了,代码三示例是可以保存数据的,但是我做了600条数据的实验,我发现确实有数据了,但是丢了一部分数据,没什么规律,数据条数越多,丢的越多。真对这个实验结果的猜测见下边图一:

代码三:

df.foreachPartition(iterator => {
      var putList = new util.ArrayList[Put]
      val table: Table = HBaseConnectionManager.getConnection().getTable(TableName.valueOf(tableName))
      iterator.foreach(message => {
        val key= message.getAs[String]("key")
        val requestMessage = message.getAs[Map[String, String]]("realtime")
        var put: Put = new Put(key.getBytes())
        requestMessage.keys.foreach {
          key => put.addColumn("data".getBytes(), key.getBytes(), requestMessage(key).getBytes())
        }
       
        putList.add(put)
     if (putList.size() > 0) {
        val table: Table = HBaseConnectionManager.getConnection().getTable(TableName.valueOf(tableName))
        table.put(putList)
        println("table:" + table.hashCode() + ":" + putList.size())
        table.close()
      }
     putList.clear()
    })
   df.show(1)

猜想图如下,根据猜想,df.show(1)基本每次都是从partition1里查出来一条数据就完事了,所以partition2里的数据还是等于没有action操作,所以一直没被执行,从效果上看这部分数据就丢失了。

图一:

针对这个结果,我想到的对应办法有几个,并做了实验,结果都成功了:

1.我可以减小partition的数量,因为我的df在foreachPartition之前,有一个join操作,这个会触发shuffle操作,我可以在join之后加一个.repartition(1)

把我的分区数量改为1.但是这跟df.foreach和df.rdd.collect()就没有区别了,在一个分区里计算,效率太低,还容易内存溢出。(实验成功,1条数据没丢)

2.第2个方案,我可以让每个partition都执行一下一下show,比如我现在是设置的10秒的trigger时间,一共会有10条数据,但是我每次都show(100),这样明显查过并发量的输出,应该就会从所有partition里取数据,就会触发之前iterator.foreach里的操作了。但是这个办法还是不好,因为最终我只希望存入HBASE,其他的输出是在浪费我的CPU,内存,网络和硬盘一系列资源,这样不太好。(实验成功,1条数据没丢)

终极方案:

也是我最后暂时采用的写法,先初始化table,foreach后边判断putList大小进行put,最后table.close(),我做了一下测试,table并不会因为创建过多报错,所以浪费就浪费吧,而且在真是环境里实际上是不会浪费的,因为实际环境数据量很大所以每个Partition里肯定都是有数据的:

代码四:

def batchInsertHbase(df: Dataset[Row],df2: Dataset[Row],  partitionNum: Int): Unit = {
    val data = df.join(df2, df.col("id") === df2.col("id"), "left_outer").repartition(partitionNum)
    data.foreachPartition(iterator => {
      var putList = new util.ArrayList[Put]
//table初始化放在foreach的上边,因为我发现初始化table并关闭好像并不太浪费资源,我测试了循环初始化1万个table并关闭,并没有报错,getConnection返回的是一个静态连接,未来会优化,防止HBASE挂掉导致程序异常
      val table: Table = HBaseConnectionManager.getConnection().getTable(TableName.valueOf(tableName))
      iterator.foreach(message => {
        val key= message.getAs[String]("key")
        val requestMessage = message.getAs[Map[String, String]]("realtime")
        var put: Put = new Put(key.getBytes())
        requestMessage.keys.foreach {
          key => put.addColumn("data".getBytes(), key.getBytes(), requestMessage(key).getBytes())
        }
        putList.add(put)
      })
      println("list:" + putList.hashCode() + ":" + putList.size())
      if (putList.size() > 0) {
        table.put(putList)
        println("table:" + table.hashCode() + ":" + putList.size())
      }
      table.close()
    })
    println("批量插入HBASE:data_table")
  }

后续更新来了:更新与2022-05-23

这个代码不执行的现象后来分析可能是停止了Spark任务之后,Kafka的Producer程序并没有关闭,这段时间会进入大量的数据几十万条,Spark正常启动的时候会一次性把增量全部加载进内存,导致看似程序压根没执行,其实是卡在数据加载那块了,程序其实压根加载不过来,会持续好久最后来个内存溢出什么的。

解决方式:

设置spark-kafka的一个属性maxOffsetsPerTrigger最大加载数据量,具体的量多少根据自己往kafka里放的数据大小来定就可以了。这样kafka启动的时候就只会一次性加载一部分增量不会卡死,后续也是每次加载那么多,所以你停SPARK任务的时候会有一部分数据堆积,但是只要你的任务写的没毛病,可能过几分钟之后数据就追上来了,依然是实时行,或者就是在停Spark任务之前先把Kafka的Producer程序停了,不过我觉得凡事还是靠自己比较好。

var reader = spark
  .readStream
  .format("kafka")
  .option("kafka.bootstrap.servers", props.getProperty("kafka.bootstrap.servers"))
  .option("subscribe", topic)
  .option("maxOffsetsPerTrigger", maxOffsetsPerTrigger)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

其实我是真性情

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值