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
    评论
要将数据写入HBase,您需要使用HBase提供的API或者使用已有的HBase连接器。在Spark中,您可以使用HBase Connector for Spark来实现将数据写入HBase。以下是一些步骤,希望能帮助您解决问题: 1. 下载HBase Connector for Spark并将其添加到您的Spark应用程序中。 2. 使用以下代码创建HBase Configuration: ``` val hbaseConfig = HBaseConfiguration.create() hbaseConfig.set("hbase.zookeeper.quorum", "<quorum>") hbaseConfig.set("hbase.zookeeper.property.clientPort", "<port>") ``` 其中,`<quorum>`是HBase的ZooKeeper集合,`<port>`是ZooKeeper的客户端端口。 3. 创建一个HBase表,并使用以下代码将DataFrame写入HBase表: ``` import org.apache.spark.sql.execution.datasources.hbase._ val hbaseTable = "tablename" val hbaseNamespace = "namespace" df.write.options( Map(HBaseTableCatalog.tableCatalog -> s"""{"table":{"namespace":"$hbaseNamespace", "name":"$hbaseTable"},"rowkey":"key","columns":{"col0":{"cf":"rowkey", "col":"key", "type":"string"},"col1":{"cf":"cf1", "col":"col1", "type":"string"},"col2":{"cf":"cf2", "col":"col2", "type":"string"}}}""", HBaseTableCatalog.newTable -> "5" )).format("org.apache.spark.sql.execution.datasources.hbase").save() ``` 在代码中,`tablename`是HBase表的名称,`namespace`是HBase表所在的命名空间。`rowkey`是HBase表的主键列。`col0`,`col1`和`col2`是表中的列,其中`cf1`和`cf2`是列族。 希望这些步骤可以帮助您将数据成功写入HBase
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值