Bulk Load——Spark 批量导入多列数据到HBase(scala/Java)

本文介绍了使用Spark批量导入HBase的流程,包括数据预处理、Put写入与BulkLoad的优势。通过Java和Scala尝试多列KeyValue的BulkLoad,探讨了预分区、优化策略与遇到的错误及其解决方案。最终实现了Spark的BulkLoad优化,提升了数据导入效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、最终流程

数据量预估,预分裂 ——> 准备HBase表 ——> Spark加载HDFS上的数据 ——> 数据清洗及排序 ——> 数据以HFile的形式写入HDFS ——> BulkLoad ——> 优化

未优化时,大概1200万条数据/h (10G数据)

二、使用Put写入

参考了很多资料后,猜测使用Put写入,也是Bulk Load,并不只是KeyValue才能批量加载
参考连接
只是Put是以行为单位,KeyValue以列为单位,按理说,应该是KeyValue更快
补充:该问题与我想法相似,追溯源码,到底层仍未看出究竟
在这里插入图片描述

import util.HdfsUtils
import org.apache.log4j.Logger
import org.slf4j.LoggerFactory
import org.apache.hadoop.hbase.client.{
   
   Put, Result}
import org.apache.hadoop.hbase.io.ImmutableBytesWritable
import org.apache.hadoop.hbase.util.Bytes
import org.apache.hadoop.hbase.mapreduce.TableOutputFormat
import org.apache.hadoop.mapreduce.Job
import org.apache.log4j.Level
import org.apache.spark.{
   
   SparkConf, SparkContext}

import scala.util.matching.Regex

/**
  * write data to HBase by 'Put'
  * Date: 2019-04-29
  * Time: 17:13
  * Author: wh
  * Version: V1.0.0
  */
class HBaseTest {
   
   

}

object CleanToHBase {
   
   
  private val PARTTERN: Regex = """。。。。。。""".r
  private val LOG = LoggerFactory.getLogger(classOf[HBaseTest])
  private val HdfsFilePath = HdfsUtils.HDFS_SCHEME + "。。。。。。"
  private final val NULL_FIELDS = Array("-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-")
  private val NUM_FIELDS: Int = 17


  /**
    * 解析输入的日志数据
    *
    * @param line logline
    * @return
    */
  def logLineSplit(line: String): Array[String] = {
   
   
    val options = PARTTERN.findFirstMatchIn(line)
    var fileds = new Array[String](NUM_FIELDS)
    // 。。。。。。清洗逻辑
    fileds
  }

  def main(args: Array[String]): Unit = {
   
   
    Logger.getLogger("org.apache.hadoop").setLevel(Level.WARN)
    Logger.getLogger("org.apache.spark").setLevel(Level.WARN)

    LOG.info("Start.")
    val startTime: Long = System.currentTimeMillis()

    // 1. Spark清洗
    val sparkConf = new SparkConf().setAppName("Put to HBase test").setMaster("local")
    val sc = new SparkContext(sparkConf)
    var logRDD = sc.textFile(HdfsFilePath, 12)
    val splitRDD = logRDD.map(line => logLineSplit(line))


    // 2. HBase 信息
    val tableName = "bdTest2"
    val familyName = Bytes.toBytes("infos")

    // 3. HBase MapReduce Bulk Job
    sc.hadoopConfiguration.set("hbase.zookeeper.quorum", "cluster")
    sc.hadoopConfiguration.set("hbase.zookeeper.property.clientPort", "2181")
    sc.hadoopConfiguration.set(TableOutputFormat.OUTPUT_TABLE, tableName)

    val hbaseBulkJob = Job.getInstance(sc.hadoopConfiguration)
    hbaseBulkJob.setOutputKeyClass(classOf[ImmutableBytesWritable])
    hbaseBulkJob.setOutputValueClass(classOf[Result])
    hbaseBulkJob.setOutputFormatClass(classOf[TableOutputFormat[ImmutableBytesWritable]])

    var i = 0
    // 4. write data to HBase
    val hbasePuts = splitRDD.map{
   
    line =>
      val put = new Put(Bytes.toBytes("row-" + System.nanoTime())) // 测试用
      put.addColumn(familyName, Bytes.toBytes("column name"), Bytes.toBytes(line(1)))
      put.addColumn(familyName, Bytes.toBytes("column name"), Bytes.toBytes(line(2)))
      // 。。。。。。other column
      (new ImmutableBytesWritable(), put)
    }
    hbasePuts.saveAsNewAPIHadoopDataset(hbaseBulkJob.getConfiguration)
    LOG.info("Done.")
    LOG.info("Time elapsed {} seconds.", (System.currentTimeMillis() - startTime) / 1000)
    sc.stop()
  }
}

注意以下区别,网上很多人,set了class又在save的时候传class,想想就知道,肯定有多余的啊:
在这里插入图片描述

三、批量写入,BulkLoad

找了大量资料,看了国内外很多文章,几乎都是KeyValue一个Cell写来写去
批量写入的优势:

  • 数据可立即供HBase使用
  • 不使用预写日志(WAL),不会出现flush和split(未验证)
  • 更少的垃圾回收
  • The bulk load feature uses a MapReduce job to output table data in HBase’s internal data format, and then directly loads the generated StoreFiles into a running cluster. Using bulk load will use less CPU and network resources than simply using the HBase API.

BulkLoad操作则是在外部以MapReduce作业的方式写HFile格式的文件,然后放入HDFS,再通知“HBase”来管理这些数据**
参考1
参考2
参考3
参考4

一般过程包括:

  • 估计数据的总大小,并确定HBase中的最佳region数
  • 创建于的空表,预分裂,为避免冷热数据,考虑对行键加盐
  • 在Spark中使用简单的自定义分区程序来拆分RDD,以匹配目标region拆分
  • 使用Spark和标准Hadoop库生成HFile
  • 使用标准HBase命令行批量加载工具(或代码)将数据加载到HBase中

四、Java BulkLoad 多列KeyValue(未成功-not Cell)

package core;

/*
 * It's not work, for List to Cell, see {@code BulkLoadToHBase.scala}
 * Date: 2019-04-30
 * Time: 下午3:38
 * Author: wh
 * Version: V1.0.0
 */
import util.HdfsUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.mapred.TableOutputFormat;
import org.apache.hadoop.hbase.mapreduce.HFileOutputFormat2;
import org.apache.hadoop.hbase.tool.LoadIncrementalHFiles;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.fs.Path;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.PairFunction;
### 使用 Spark 实现 HBaseHBase 的离线数据同步方法 为了实现从一个 HBase 实例到另一个 HBase 实例的数据同步,可以充分利用 Apache Spark 的分布式计算能力来处理大规模数据集。以下是具体的实现方式及相关说明: #### 数据读取阶段 在 Spark 中,可以通过 `org.apache.hadoop.hbase.spark.HBaseContext` 类提供的接口访问 HBase 表中的数据[^3]。此过程涉及将 HBase 表扫描的结果转化为 RDD 或 DataFrame 结构以便进一步操作。 ```scala import org.apache.hadoop.hbase.{HBaseConfiguration, TableName} import org.apache.hadoop.hbase.client._ import org.apache.hadoop.hbase.util.Bytes import org.apache.hadoop.hbase.mapreduce.TableInputFormat import org.apache.spark.sql.SparkSession val conf = HBaseConfiguration.create() conf.set(TableInputFormat.INPUT_TABLE, "source_hbase_table") // 初始化 Spark Session val spark = SparkSession.builder().appName("HBaseToHBaseSync").getOrCreate() // 加载 HBase 表作为 RDD val hbaseRDD = spark.sparkContext.newAPIHadoopRDD( conf, classOf[TableInputFormat], classOf[org.apache.hadoop.hbase.io.ImmutableBytesWritable], classOf[org.apache.hadoop.hbase.client.Result] ) // 将 Result 转换为键值对形式 val keyValuePairs = hbaseRDD.map { case (_, result) => val rowKey = Bytes.toString(result.getRow()) val columnValue = Bytes.toString(result.getValue(Bytes.toBytes("column_family"), Bytes.toBytes("qualifier"))) (rowKey, columnValue) } ``` 以上代码展示了如何通过 Spark 读取 HBase 表中的数据,并将其映射成 `(rowKey, value)` 形式的键值对集合[^3]。 --- #### 数据转换阶段 在此阶段可以根据业务需求对数据进行必要的过滤、清洗或转换操作。例如去除重复记录、修正字段格式等。这些操作可以直接基于 Spark 提供的 Transformation 算子完成。 假设需要保留某些特定列族下的数据并忽略其余部分,则可按如下方式进行筛选: ```scala val filteredData = keyValuePairs.filter { case (key, value) => !value.isEmpty && key.startsWith("prefix_") // 自定义逻辑 } ``` 此外还可以引入广播变量或其他外部依赖以增强灵活性[^4]。 --- #### 数据写入阶段 最后一步就是将经过加工后的数据保存回目标 HBase 实例中去。这里提供了两种常见策略可供选择: ##### 方法一:逐条插入法 利用 HBase 的 Put 对象逐一构建每一条记录并向远端提交请求。 ```scala filteredData.foreachPartition(partition => { val configTarget = HBaseConfiguration.create() configTarget.set("hbase.zookeeper.quorum", "target_zk_quorum") val connection = ConnectionFactory.createConnection(configTarget) val table = connection.getTable(TableName.valueOf("target_hbase_table")) partition.foreach { case (rowKey, value) => val put = new Put(Bytes.toBytes(rowKey)) put.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("qf"), Bytes.toBytes(value)) table.put(put) } table.close() connection.close() }) ``` 尽管易于理解且便于调试,但由于频繁建立网络连接可能会带来额外开销,因此仅适用于中小规模场景下[^1]。 ##### 方法二:批量导入法 对于海量级别的迁移任务来说,建议预先组装好一批待存储单元后再统一提交给服务器端处理。这样不仅可以减少交互次数从而提升吞吐量,而且还能更好地适配底层硬件特性达到性能优化的目的。 首先需准备一段自定义函数负责封装多个 Key-Value 组合成为单个 BulkLoad 文件组件;接着调用相应 API 完成最终上传动作即可[^2]。 ```scala def bulkLoad(data: Iterator[(String, String)], pathPrefix: String): Unit = ??? bulkLoad(filteredData.collect(), "/tmp/bulkload_data/") ``` 注意这里的具体实现细节取决于实际环境配置情况而定,可能还需要考虑权限管理等问题[^4]。 --- ### 总结 综上所述,借助于强大的 Spark 平台能够轻松应对跨集群间的大容量 HBase 数据复制挑战。无论是简单的全量拷贝还是复杂的增量更新模式都可以找到合适的解决方案加以满足。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值