Spark参数spark.hadoopRDD.ignoreEmptySplits对于HBase 2.3的影响

背景

Spark在3.2 release(SPARK-34809)中正式将spark.hadoopRDD.ignoreEmptySplits的参数默认值设置为TRUE。当线上的HBase版本小于等于2.3的时候,将导致的一个问题是,在执行如下代码,即Spark读取HBase表进行count的时候,可能出现读不到数据,返回结果为0的情况。下面将对这部分涉及到的源代码进行分析。

val sparkConf = new SparkConf()
sparkConf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
sparkConf.registerKryoClasses(Array(classOf[org.apache.hadoop.hbase.client.Result]))

val spark = SparkSession.builder()
  .config(sparkConf)
  .getOrCreate()

val hbaseConf = getHbaseConf
hbaseConf.set(TableInputFormat.INPUT_TABLE, test)
hbaseConf.set(TableInputFormat.SCAN, getScanString)
val rawRDD = spark.sparkContext.newAPIHadoopRDD(
  hbaseConf, classOf[TableInputFormat], classOf[ImmutableBytesWritable], classOf[Result]
    )
println(rawRDD.count())		// 当HBase表里有数据的时候,返回的结果可能为0

  def getScanString: String = {
    val scan = new Scan()
      .setCacheBlocks(false)
      .addFamily(Bytes.toBytes("test_columnfamily"))
   val proto = ProtobufUtil.toScan(scan)
   Base64.encodeBytes(proto.toByteArray())
}
    

spark.hadoopRDD.ignoreEmptySplits的来源

根据SPARK-22233描述,Hive有时候会创建一个空表,表中有很多空文件,Spark会使用存储在Hive Meta Store中的InputFormat,而不会合并这些空文件,因此会产生很多任务来处理这些空文件。为了节省Spark的资源,Spark引入了spark.hadoopRDD.ignoreEmptySplits这一参数来解决这一问题。具体的代码实现就是在NewHadoopRDD文件中,当获得Splits的时候进行一次filter。

val allInputSplits = getInputFormat(jobConf).getSplits(jobConf, minPartitions)
val inputSplits = if (ignoreEmptySplits) {
  allInputSplits.filter(_.getLength > 0)
} else {
  allInputSplits
}

HBase getSplits函数

TableInputFormat中的getSplits函数,调用了父类TableInputFormatBase的getSplits函数来获取split list,其中具体的实现是在oneInputSplitPerRegion函数中进行的。该函数首先初始化了一个sizeCalculator来计算HBase给定表和给定column family的region大小。

/**
* Create one InputSplit per region
*
* @return The list of InputSplit for all the regions
* @throws IOException throws IOException
*/
private List<InputSplit> oneInputSplitPerRegion() throws IOException {
 RegionSizeCalculator sizeCalculator =
     createRegionSizeCalculator(getRegionLocator(), getAdmin());
 .....
}

// init sizeCalculator
private void init(RegionLocator regionLocator, Admin admin)
      throws IOException {
	......
	for (ServerName tableServerName : tableServers) {
      for (RegionMetrics regionLoad : admin.getRegionMetrics(
        tableServerName,regionLocator.getName())) {

        byte[] regionId = regionLoad.getRegionName();
        long regionSizeBytes
          = ((long) regionLoad.getStoreFileSize().get(Size.Unit.MEGABYTE)) * MEGABYTE;

        sizeMap.put(regionId, regionSizeBytes);

        if (LOG.isDebugEnabled()) {
          LOG.debug("Region " + regionLoad.getNameAsString() + " has size " + regionSizeBytes);
        }
      }
    }
    .....
 }

long regionSizeBytes = ((long) regionLoad.getStoreFileSize().get(Size.Unit.MEGABYTE)) * MEGABYTE 这行代码将导致的一个问题是,当region size小于1MB的时候,HBase返回的region size就是0。因此,当Spark enable spark.hadoopRDD.ignoreEmptySplits配置的时候,一些实际上不是empty的小region将被过滤出去,得到预期之外的结果。

问题解决

这个bug,HBase社区通过 jira issue HBASE-26340 进行在HBase 2.4.10以后的版本了修复,具体的修复思路是将 storefile的单位从MB变为B,解决了小于1MB的region返回size为0的错误结果。使用HBase 2.4.10之前的用户可以通过backport HBase代码或者disable Spark spark.hadoopRDD.ignoreEmptySplits来避免错误的结果。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值