hive导入hbase批量入库----单条put 、批量put 、Mapreduce、 bluckload

1、概述

hive数据导入到hbase的方式:

我们经常面临向 HBase 中导入大量数据的情景,往HBase 中批量加载数据的方式有很多种,

1、hive和hbase建映射表 直接操作hive表就是操作hbase表 --关系数据导入hbase,进行数据初始化, 但是这种会hive的分区和hbase的预分区不友好,如果只是一般hive表可以使用。

2、使用sparksql操作完hive处理好之后,调用HBase 的API用put方法插入数据;单条put、批量put

3、是用MapReduce的方式从hdfs上加载数据,调用TableOutputFormat 类在reduce中直接生成put对象写入HBase(这种方式可以看作多线程的调用hbase API方式);

但是这两种方式效率都不是很高,因为HBase会频繁的进行flush、compact、split操作,需要消耗较大的 CPU 和网络资源,并且region Server压力也比较大。

4、另一种是通过BulkLoad的方式生成HFile文件然后加载到HBase中,BulkLoad 方式调用MapReduce的job直接将数据输出成HBase table内部的存储格式的文件HFile,然后将生成的StoreFiles 加载到集群的相应节点。这种方式无需进行flush、compact、split等过程,不占用region资源,不会产生巨量的写入 I/O,所以需要较少的 CPU 和网络资源。在首次数据加载时,能极大的提高写入效率,并降低对Region Server节点的写入压力。

2、HBase API往Hbase中插入数据的流程。

client端写入操作实际上都是RPC请求,数据传到Region Server中,默认首先会写入到WAL(Write Ahead Log)中,也就是HLog中,然后才将数据写入到对应region的memStore中,memStore满了之后,flush到HFile中,这种情况的flush操作会引起瞬间堵塞用户的写操作。

当StoreFile数量达到一定的阈值,会触发compact合并操作,将多个storeFile合并成一个大的StoreFile,这一过程包含大量的硬盘I/O操作以及网络数据通信。

单个StoreFile过大超过阈值之后会触发region的split操作,并将大的StoreFile一分为二。

该方式在大数据量写入时效率低下(频繁进行flush,split,compact耗费磁盘I/O),还会对影响HBase节点的稳定性造(GC时间过长,响应变慢,导致节点超时退出,并引起一系列连锁反应)。

进行批量入库之前,首先要连接到正确的连接到hbase

static{
        conf=HBaseConfiguration.create();
        //可以连接hbase
        //zookeeper给客户端的端口
        conf.set("hbase.zookeeper.property.clientPort", "2181");
        conf.set("hbase.zookeeper.quorum", "192.168.137.138,192.168.137.139");
        conf.set("hbase.master", "192.168.10.138:60000");        
    }

然后开始建立我们的表结构:

    public static void createTable(String tableName){
        try {
            ha = new HBaseAdmin(conf);
            if(ha.tableExists(tableName)){
                ha.disableTable(tableName);
                ha.deleteTable(tableName);
            }
            //建立表结构
            HTableDescriptor hd =new HTableDescriptor(tableName);
            //添加列族
            hd.addFamily(new HColumnDescriptor("family1".getBytes()));
            hd.addFamily(new HColumnDescriptor("family2".getBytes()));
            ha.createTable(hd);
        } catch (Exception e) {
            System.out.println(e);
        }
    }


有了上面的基础后,可以正式开始进行数据的插入

1、单条put

// 插入内容,行键,列族,列名,值,插入的表名
    public static void insertData(String rowkey, String cf,  String clomun, String content, String tableName)
            throws IOException {
        htable = new HTable(conf, tableName);
        Put put = new Put(rowkey.getBytes());
        put.add(cf.getBytes(), clomun.getBytes(), content.getBytes());
        htable.put(put);
    }

这种方式是批量插入数据最慢的方式,它更合适的应用场景是一般是线上业务运行时,记录单条插入,如报文记录,处理记录,写入后htable对象即释放。每次提交就是一次rpc请求.

2、多条Put

也就是将每一个put对象,放入List集合里面,然后对这个List集合进行入库,相比于单条Put,这种方式在入库效率上明显会有所提升. 应用场景一般在数据量稍多的环境下,通过批量提交减少请求次数
  在主方法里面调用该方法并且输入相关参数就可以实现用put方式对数据的批量插入了

public static void insertData(String rowkey, String cf, String clomun, String content, String tableName)
            throws IOException {
        htable = new HTable(conf, tableName);
        List<Put> list =new ArrayList<Put>(); 
        Put put = new Put(rowkey.getBytes());
        put.add(cf.getBytes(), clomun.getBytes(), content.getBytes());
        list.add(put);
        htable.put(list);
    }

public static void main(String[] args) throws IOException {
        createTable("insertTest");
        try {
            htable = new HTable(conf, tableName);
            List<Put> list = new ArrayList<Put>(); 
            for (int i = 0; i < 10; i++) {
                String rowkey = UUID.randomUUID().toString();
                // 因为不能动态增加列簇,所以只能动态添加列
                Put put = new Put(rowkey.getBytes());
                //insertData(rowkey, "family1", "column",new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()), "insertTest");
                   
                put.add(cf.getBytes(), clomun.getBytes(), content.getBytes());  
                list.add(put);
            }
          htable.put(list);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


 以上的put插入数据,因为不适合处理大批量的数据,所以都是在自己搭建的集群上进行的测试,接下来介绍的两种方式用的是公司的集群.

2.3.1 普通插入

val rdd = sc.textFile("/data/produce/2015/2015-03-01.log")
val data = rdd.map(_.split("@")).map {
	x=>(x(0)+x(1),x(2))
}
val result = data.foreachPartition {
	x => {
	val conf= HBaseConfiguration.create();
	conf.set(TableInputFormat.INPUT_TABLE,"data");
	conf.set("hbase.zookeeper.quorum","slave5,slave6,slave7");
	conf.set("hbase.zookeeper.property.clientPort","2181");
	conf.addResource("/home/hadoop/data/lib/hbase-site.xml");
	val table = new HTable(conf,"data");
	table.setAutoFlush(false,false);
	table.setWriteBufferSize(3*1024*1024);
	x.foreach {
	y => {
	var put= new Put(Bytes.toBytes(y._1));
	put.add(Bytes.toBytes("v"),Bytes.toBytes("value"),Bytes.toBytes(y._2));
	table.put(put)
}
;table.flushCommits}}}
2)执行时间如下:7.6 min

2.3.2 Bulkload

val conf = HBaseConfiguration.create();
	val tableName = "data1"
val table = new HTable(conf,tableName)
conf.set(TableOutputFormat.OUTPUT_TABLE,tableName)

val job = Job.getInstance(conf)
job.setMapOutputKeyClass(classOf[ImmutableBytesWritable])
job.setMapOutputValueClass(classOf[KeyValue])
HFileOutputFormat.configureIncrementalLoad(job,table)

val rdd = sc.textFile("/data/produce/2015/2015-03-01.log")
.map(_.split("@"))
.map {
	x => (DigestUtils.md5Hex(x(0)+x(1)).substring(0,3)+x(0)+x(1),x(2))
}
.sortBy(x =>x._1)
.map {
	x=> {
	val kv:KeyValue = new KeyValue(Bytes.toBytes(x._1),Bytes.toBytes("v"),Bytes.toBytes("value"),Bytes.toBytes(x._2+""));
	(new ImmutableBytesWritable(kv.getKey),kv)
}
}rdd.saveAsNewAPIHadoopFile("/tmp/data1",classOf[ImmutableBytesWritable],classOf[KeyValue],classOf[HFileOutputFormat],job.getConfiguration())
val bulkLoader = new LoadIncrementalHFiles(conf)
bulkLoader.doBulkLoad(new Path("/tmp/data1"),table)

2) 执行时间:7s

通过对比我们可以发现bulkload批量导入所用时间远远少于普通导入,速度提升了60多倍,当然我没有使用更大的数据量测试,但是我相信导入速度的提升是非常显著的,强烈建议使用BulkLoad批量导入数据到HBase中。 

3、采用bulkLoad方法批量入库

BulkLoad涉及两个过程:

1. Transform阶段:使用MapReduce将HDFS上的数据生成成HBase的底层Hfile数据。

2.  Load阶段:根据生成的目标HFile,利用HBase提供的BulkLoad工具将HFile Load到HBase的region中。

在这里需要注意,在bulkLoading执行之前要提前把数据导入到hdfs上,因为mapreduce只能读取HDFS上的数据;如果原始数据在hdfs上占用100G大小的空间,那么hdfs上的预留的空间大小要大于200G,因为数据要首先生成hfile也是放在hdfs临时目录下。

4 bulkload和put适合的场景:

• bulkload适合的场景:

–            大量数据一次性加载到HBase。

–            对数据加载到HBase可靠性要求不高,不需要生成WAL文件。

–            使用put加载大量数据到HBase速度变慢,且查询速度变慢时。

–            加载到HBase新生成的单个HFile文件大小接近HDFS block大小。

•   put适合的场景:

–            每次加载到单个Region的数据大小小于HDFS block大小的一半。

–            数据需要实时加载。

–            加载数据过程不会造成用户查询速度急剧下降。

5、Bulkload批量导入数据shell操作步骤:

1.将数据导入到HDFS中

2.建表并创建导入模板文件

3.执行命令,生成HFile文件

4.执行命令将HFile导入HBase

5.总结

1.本篇文章是使用hbase-spark包中提供的bulkload方法生成HFile文件,然后将生成的文件导入到HBase表中。

2.使用bulkload的方式导入数据到HBase表时,在load HFile文件到表过程中会有短暂的时间导致该表停止服务(在load文件过程中需要先disable表,load完成后在enable表。

3.需要使用hbase用户提交Spark作业

eg 1: hdfs2Hbase

object hdfsBulkLoad2Hbase {
  /**
    * 批量插入多列
    */
  def insertWithBulkLoadWithMulti(): Unit = {

    val sparkSession = SparkSession.builder().appName("insertWithBulkLoad").master("local[4]").getOrCreate()
    val sc = sparkSession.sparkContext
    val rdd = sc.textFile("hdfs://node1:9000/v2120/a.txt")
      .map(_.split(","))
      .map(x => (DigestUtils.md5Hex(x(0)).substring(0, 3) + x(0), x(1), x(2)))
      .sortBy(_._1)
      .flatMap(x => {
        val listBuffer = new ListBuffer[(ImmutableBytesWritable, KeyValue)]
        val kv1: KeyValue = new KeyValue(Bytes.toBytes(x._1), Bytes.toBytes("cf1"), Bytes.toBytes("name"), Bytes.toBytes(x._2 + ""))
        val kv2: KeyValue = new KeyValue(Bytes.toBytes(x._1), Bytes.toBytes("cf1"), Bytes.toBytes("age"), Bytes.toBytes(x._3 + ""))
        listBuffer.append((new ImmutableBytesWritable, kv2))
        listBuffer.append((new ImmutableBytesWritable, kv1))
        listBuffer
      }
      )
    val tableName = "test"
    val hbaseConf = HBaseConfiguration.create()
    hbaseConf.addResource("hbase-site.xml") //k可以不用下面了
    hbaseConf.set(HConstants.ZOOKEEPER_QUORUM, "192.168.187.201")
    hbaseConf.set(HConstants.ZOOKEEPER_CLIENT_PORT, "2181")
    hbaseConf.set(TableOutputFormat.OUTPUT_TABLE, tableName)

    val conn = ConnectionFactory.createConnection(hbaseConf)
    val admin = conn.getAdmin
    val table = conn.getTable(TableName.valueOf(tableName))
    //获取hbase表的region分布
    //val regionLocator = conn.getRegionLocator(TableName.valueOf(tableName))

    val job = Job.getInstance(hbaseConf)
    //设置job的输出格式. //输出文件的内容KeyValue
    //此处最重要,需要设置文件输出的key,因为我们要生成HFil,所以outkey要用ImmutableBytesWritable
    job.setMapOutputKeyClass(classOf[ImmutableBytesWritable])
    job.setMapOutputValueClass(classOf[KeyValue])
    job.setOutputFormatClass(classOf[HFileOutputFormat2])
    //HFileOutputFormat2.configureIncrementalLoad(job, table, conn.getRegionLocator(TableName.valueOf(tableName)))
    HFileOutputFormat2.configureIncrementalLoadMap(job, table)

    //多列的排序,要按照列名字母表大小来
    val hFileOutput = "hdfs://node1:9000/test"
    isFileExist(hFileOutput, sc)

    rdd.saveAsNewAPIHadoopFile(hFileOutput,
      classOf[ImmutableBytesWritable],
      classOf[KeyValue],
      classOf[HFileOutputFormat2],
      job.getConfiguration)
    val bulkLoader = new LoadIncrementalHFiles(hbaseConf)
    //bulkLoader.doBulkLoad(new Path(hFileOutput), admin, table, regionLocator)
    bulkLoader.doBulkLoad(new Path(hFileOutput), table.asInstanceOf[HTable])
  }

  /**
    * 判断hdfs上文件是否存在,存在则删除
    */
  def isFileExist(filePath: String, sc: SparkContext): Unit = {
    val output = new Path(filePath)
    val hdfs = FileSystem.get(new URI(filePath), new Configuration)
    if (hdfs.exists(output)) {
      hdfs.delete(output, true)
    }
  }
spark-submit --master yarn 
--conf spark.yarn.tokens.hbase.enabled=true 
--class com.dounine.hbase.BulkLoad 
--executor-memory 2G 
--num-executors 2G 
--driver-memory 2G    
--executor-cores 2 build/libs/hbase-data-insert-1.0.0-SNAPSHOT-all.jar

eg2:Hive2Hbase

https://www.aboutyun.com/forum.php?mod=viewthread&tid=25037

/**
  * package: com.cloudera.hbase
  * describe: 使用BulkLoad的方式将Hive数据导入HBase
  */
object Hive2HBase {

  def main(args: Array[String]) {

    //库名、表名、rowKey对应的字段名、批次时间、需要删除表的时间参数
    val rowKeyField = "id"
    val quorum = "cdh01.fayson.com,cdh02.fayson.com,cdh03.fayson.com"
    val clientPort = "2181"
    val hBaseTempTable = "ods_user_hbase"

    val sparkConf = new SparkConf().setAppName("Hive2HBase")
    val sc = new SparkContext(sparkConf)

    val hiveContext = new HiveContext(sc)
    //从hive表读取数据
    val datahiveDF = hiveContext.sql(s"select * from ods_user")

    //表结构字段
    var fields = datahiveDF.columns

    //去掉rowKey字段
    fields = fields.dropWhile(_ == rowKeyField)

    val hBaseConf = HBaseConfiguration.create()
    hBaseConf.set("hbase.zookeeper.quorum", quorum)
    hBaseConf.set("hbase.zookeeper.property.clientPort", clientPort)

    //表不存在则建Hbase临时表
    creteHTable(hBaseTempTable, hBaseConf)

    val hbaseContext = new HBaseContext(sc, hBaseConf)

    //将DataFrame转换bulkload需要的RDD格式
    val rddnew = datahiveDF.rdd.map(row => {
      val rowKey = row.getAs[String](rowKeyField)

      fields.map(field => {
        val fieldValue = row.getAs[String](field)
        (Bytes.toBytes(rowKey), Array((Bytes.toBytes("info"), Bytes.toBytes(field), Bytes.toBytes(fieldValue))))
      })
    }).flatMap(array => {
      (array)
    })

    //使用HBaseContext的bulkload生成HFile文件
    hbaseContext.bulkLoad[Put](rddnew.map(record => {
      val put = new Put(record._1)
      record._2.foreach((putValue) => put.addColumn(putValue._1, putValue._2, putValue._3))
      put
    }), TableName.valueOf(hBaseTempTable), (t : Put) => putForLoad(t), "/tmp/bulkload")

    val conn = ConnectionFactory.createConnection(hBaseConf)
    val hbTableName = TableName.valueOf(hBaseTempTable.getBytes())
    val regionLocator = new HRegionLocator(hbTableName, classOf[ClusterConnection].cast(conn))
    val realTable = conn.getTable(hbTableName)
    HFileOutputFormat2.configureIncrementalLoad(Job.getInstance(), realTable, regionLocator)

    // bulk load start
    val loader = new LoadIncrementalHFiles(hBaseConf)
    val admin = conn.getAdmin()
    loader.doBulkLoad(new Path("/tmp/bulkload"),admin,realTable,regionLocator)

    sc.stop()
  }

  /**
    * 创建HBase表
    * @param tableName 表名
    */
  def creteHTable(tableName: String, hBaseConf : Configuration) = {
    val connection = ConnectionFactory.createConnection(hBaseConf)
    val hBaseTableName = TableName.valueOf(tableName)
    val admin = connection.getAdmin
    if (!admin.tableExists(hBaseTableName)) {
      val tableDesc = new HTableDescriptor(hBaseTableName)
      tableDesc.addFamily(new HColumnDescriptor("info".getBytes))
      admin.createTable(tableDesc)
    }
    connection.close()
  }

  /**
    * Prepare the Put object for bulkload function.
  */
  @throws(classOf[IOException])
  @throws(classOf[InterruptedException])
  def putForLoad(put: Put): Iterator[(KeyFamilyQualifier, Array[Byte])] = {
    val ret: mutable.MutableList[(KeyFamilyQualifier, Array[Byte])] = mutable.MutableList()
    import scala.collection.JavaConversions._
    for (cells <- put.getFamilyCellMap.entrySet().iterator()) {
      val family = cells.getKey
      for (value <- cells.getValue) {
        val kfq = new KeyFamilyQualifier(CellUtil.cloneRow(value), family, CellUtil.cloneQualifier(value))
        ret.+=((kfq, CellUtil.cloneValue(value)))
      }
    }
    ret.iterator
  }
}

1.将编译好的spark-demo-1.0-SNAPSHOT.jar包上传至服务器,使用spark-submit提交

export HADOOP_USER_NAME=hbase
spark-submit --class com.cloudera.hbase.Hive2HBase \
--master yarn-client \
--driver-cores 1 \
--driver-memory 2g \
--executor-cores 1 \
--executor-memory 2g \
spark-demo-1.0-SNAPSHOT.jar

eg3:file2Hbase

object file2Hbase{
  def main(args: Array[String]) = {
    //创建sparkcontext,用默认的配置
    //val sc = new SparkContext(new SparkConf())
    val sc = new SparkContext("local", "app name")
    //hbase的列族
    val columnFamily1 = "f1"
    val conf = HBaseConfiguration.create()
    conf.set("hbase.zookeeper.property.clientPort", "2181")
    conf.set("hbase.zookeeper.quorum", "120.27.111.55")
    val res1=sc.textFile("file:///E:/BaiduYunDownload/data1").map(x =>
      x.replaceAll("<|>", "")
    ).distinct();
    val res2=res1.filter(x=>
      x.contains("REC")
    )
   val sourceRDD= res2.flatMap(x=>
    {
      val arg0=x.split(",")
      val arg1=arg0.map(y=>
      y.replaceFirst("=",",")
      ).filter(s=>
        s.split(",").length>1
        )
      //arg0(10).replaceFirst("=",",").split(",")(0).contat(arg0(10).replaceFirst("=",",").split(",")(0))
     // val key1=Bytes.toBytes(arg0(11).replaceFirst("=",",").split(",")(0).concat(arg0(17).replaceFirst("=",",").split(",")(1)));
      val sdf = new SimpleDateFormat("yyyyMMdd")
      val date=(Long.MaxValue-sdf.parse(arg0(11).replaceFirst("=",",").split(",")(1)).getTime).toString
      val key=DigestUtils.md5Hex(date).concat(arg0(17).replaceFirst("=",",").split(",")(1));
      // println(arg0(11).replaceFirst("=",",").split(",")(1).concat(arg0(17).replaceFirst("=",",").split(",")(1)))
     val arg2=arg1.map(z=>
        (key,(columnFamily1,z.split(",")(0), z.split(",")(1)))
      ).sorted
      arg2
     // arg0.
    }
    )
    val source=sourceRDD.sortBy(_._1)
    source.foreach(println)
    val date = new Date().getTime
    val rdd = source.map(x => {
      //将rdd转换成HFile需要的格式,我们上面定义了Hfile的key是ImmutableBytesWritable,那么我们定义的RDD也是要以ImmutableBytesWritable的实例为key
      //KeyValue的实例为value
      //rowkey
      val rowKey = x._1
      val family = x._2._1
      val colum = x._2._2
      val value = x._2._3
      (new ImmutableBytesWritable(Bytes.toBytes(rowKey)), new KeyValue(Bytes.toBytes(rowKey), Bytes.toBytes(family), Bytes.toBytes(colum), date,Bytes.toBytes(value)))
    })
    rdd.foreach(print)
    //生成的HFile的临时保存路径
    val stagingFolder = "file:///E:/BaiduYunDownload/data12"
    //将日志保存到指定目录
    rdd.saveAsNewAPIHadoopFile(stagingFolder,
      classOf[ImmutableBytesWritable],
      classOf[KeyValue],
      classOf[HFileOutputFormat2],
      conf)
    //此处运行完成之后,在stagingFolder会有我们生成的Hfile文件
    //开始即那个HFile导入到Hbase,此处都是hbase的api操作
    val load = new LoadIncrementalHFiles(conf)
    //hbase的表名
    val tableName = "output_table"
    //创建hbase的链接,利用默认的配置文件,实际上读取的hbase的master地址
    val conn = ConnectionFactory.createConnection(conf)
    //根据表名获取表
    val table: Table = conn.getTable(TableName.valueOf(tableName))
    //print(table.getTableDescriptor()+"eeeeeeeeeeeeeeeeeeeeeeeeeeeeee")
    try {
      //获取hbase表的region分布
      // val regionLocator = conn.getRegionLocator(TableName.valueOf(tableName))
      //创建一个hadoop的mapreduce的job
      val job = Job.getInstance(conf)
      //设置job名称
      job.setJobName("DumpFile")
      //此处最重要,需要设置文件输出的key,因为我们要生成HFil,所以outkey要用ImmutableBytesWritable
      job.setMapOutputKeyClass(classOf[ImmutableBytesWritable])
      //输出文件的内容KeyValue
      job.setMapOutputValueClass(classOf[KeyValue])
      //配置HFileOutputFormat2的信息
      //HFileOutputFormat2.configureIncrementalLoad(job, table, regionLocator)
      HFileOutputFormat2.configureIncrementalLoadMap(job, table)
      //开始导入
      load.doBulkLoad(new Path(stagingFolder), table.asInstanceOf[HTable])
    } finally {
      table.close()
      conn.close()
    }
}

https://www.jianshu.com/p/61afd6031887https://forum.huawei.com/enterprise/zh/thread-512009.html

https://blog.csdn.net/weixin_40861707/article/details/79105753

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

四月天03

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

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

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

打赏作者

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

抵扣说明:

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

余额充值