- BlukLoad 定义:
- 它是一种Hbase的批处理方式,可以提高效率,可作为优化的一部分。 在实际开发中,我们可能处理的数据量比较大,利用普通的
Put
来想Hbase
中插入数据会降低程序的运行效率,所以Hbase
为我们提供了批处理,向Hbase
批量写入数据提高效率,在Hbase
交互式命令行中,Hbase也提供了将数据批量插入到Hbase
数据库中,命令行的批量插入原理就是先将文件转换成HFile
文件,然后再将HFile
文件导入到Hbase
数据库中,那么利用API的方式的原理跟命令行的原理是相同的,以下就是利用API
实现Spark
处理后的数据批量导入到Hbase
中。
- BlukLoad 优点:
- 消除对
Hbase
集群的写操作的压力,因为导入过程中不占用RegionServer
的资源 - 能够快速导入海量数据
- 节省内存
- BlukLoad 原理:
- 利用HBase数据按照
HFile
格式存储在HDFS
的原理, - 使用
Mapreduce
直接生成HFile
格式文件后,RegionServers
再将HFile
文件移动到相应的Region
目录下
- BlukLoad 两种方式:
- 单列
package com.lyz.hbase
import org.apache.hadoop.fs.Path
import org.apache.spark._
import org.apache.hadoop.hbase.client.{ConnectionFactory, HBaseAdmin}
import org.apache.hadoop.hbase.io.ImmutableBytesWritable
import org.apache.hadoop.hbase.mapreduce._
import org.apache.hadoop.hbase.util.Bytes
import org.apache.hadoop.hbase.{HBaseConfiguration, KeyValue, TableName}
import org.apache.hadoop.mapreduce.Job
import org.apache.spark.sql.SparkSession
object HbaseBatch {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder().appName("DataInput").master("local[2]").getOrCreate()
val sc = spark.sparkContext
val tableNameStr = "user"
//获取配置文件的属性,为连接Hbase提供参数
val configuraton = HBaseConfiguration.create
//连接与Hbase建立连接
val conn = ConnectionFactory.createConnection(configuraton)
//获取Hbase的表的名字
val tableName = TableName.valueOf(tableNameStr)
//根据表的名字来得到表
val table = conn.getTable(tableName)
//设定HBase的输入表
configuraton.set(TableInputFormat.INPUT_TABLE, tableNameStr)
//初始化一个Job
val job = Job.getInstance(configuraton)
//设置输出的Key类型
job.setMapOutputKeyClass(classOf[ImmutableBytesWritable])
//设置输出值的类型
job.setMapOutputValueClass(classOf[KeyValue])
val regionLocator = conn.getRegionLocator(tableName)
HFileOutputFormat2.configureIncrementalLoad(job, table, regionLocator)
val array = Array(1 to 10: _*)
val value = sc.parallelize(array)
val rdd = value.map(x => {
//KeyValue 是Hbase数据存储的单元
val kv: KeyValue = new KeyValue(Bytes.toBytes(x), "info".getBytes(), "c1".getBytes(), "value_xxx".getBytes())
(new ImmutableBytesWritable(Bytes.toBytes(x)), kv)
})
//数据转换成HFile,导入到HDFS目录中
rdd.saveAsNewAPIHadoopFile("/hbase/data1", classOf[ImmutableBytesWritable], classOf[KeyValue], classOf[HFileOutputFormat2], configuraton)
//将HDFS上的HFile导入到Hbase的表中
val bulkLoader = new LoadIncrementalHFiles(configuraton)
bulkLoader.doBulkLoad(new Path("/hbase/data1"), conn.getAdmin, table, regionLocator)
}
}
- 多列,在这里有一个比较重要的隐式转换来增强RDD的功能
import org.apache.hadoop.hbase.spark.HBaseRDDFunctions._
package com.lyz.hbase.domian
/**
* 类的注释
*
* @Package com.lyz.hbase.domian
* @ClassName HbaseDomain
* @Description ${TODO}
* @Author liyuzhi
* @Date 2018-07-04 9:35
*/
case class HbaseDomain(id: String, name: String, age: Int)
package com.lyz.hbase.HbaseService
import java.util.UUID
import com.lyz.hbase.domian.HbaseDomain
import org.apache.avro.SchemaBuilder.ArrayBuilder
import org.apache.hadoop.hbase.util.Bytes
import scala.collection.mutable.ListBuffer
object HbaseService {
def getHbaseDomain: ListBuffer[HbaseDomain] = {
val hbaseDomainList = new ListBuffer[HbaseDomain]
for (i <- 1 to 10) {
val uuid = UUID.randomUUID().toString
val hbaseDomain = new HbaseDomain(uuid, "liyuzhi" + i, i)
hbaseDomainList.append(hbaseDomain)
}
hbaseDomainList
}
def getHbaseDomainArray: ListBuffer[(Array[Byte], Array[(Array[Byte], Array[Byte], Array[Byte])])] = {
val domains = getHbaseDomain
val array = new ListBuffer[(Array[Byte], Array[(Array[Byte], Array[Byte], Array[Byte])])]
domains.foreach(domain => {
array.append((Bytes.toBytes(domain.id), Array((Bytes.toBytes("info"), Bytes.toBytes("name"), Bytes.toBytes(domain.name)))))
array.append((Bytes.toBytes(domain.id), Array((Bytes.toBytes("info"), Bytes.toBytes("age"), Bytes.toBytes(domain.age)))))
})
array
}
}
package com.lyz.hbase
import org.apache.hadoop.fs.Path
import org.apache.hadoop.hbase.client.ConnectionFactory
import org.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles
import org.apache.hadoop.hbase.spark.HBaseRDDFunctions._
import org.apache.hadoop.hbase.spark.{HBaseContext, KeyFamilyQualifier}
import org.apache.hadoop.hbase.util.Bytes
import org.apache.hadoop.hbase.{HBaseConfiguration, TableName}
import org.apache.spark.SparkContext
object HbaseBatch1 {
def main(args: Array[String]): Unit = {
val sc = new SparkContext("local", "test")
//获取配置文件的属性,为连接Hbase提供参数
val configuraton = HBaseConfiguration.create
val hbaseContext = new HBaseContext(sc, configuraton)
val tableNameStr = "user"
val loadPathStr = "/hbase/data1"
//连接与Hbase建立连接
val conn = ConnectionFactory.createConnection(configuraton)
//获取Hbase表
val table = conn.getTable(TableName.valueOf(tableNameStr))
val rdd = sc.parallelize(HbaseService.HbaseService.getHbaseDomainArray)
rdd.hbaseBulkLoad(hbaseContext, TableName.valueOf(tableNameStr),
t => {
val rowKey = t._1
val family: Array[Byte] = t._2(0)._1
val qualifier = t._2(0)._2
val value = t._2(0)._3
val keyFamilyQualifier = new KeyFamilyQualifier(rowKey, family, qualifier)
Seq((keyFamilyQualifier, value)).iterator
},
loadPathStr)
val load = new LoadIncrementalHFiles(configuraton)
load.doBulkLoad(new Path(loadPathStr), conn.getAdmin, table, conn.getRegionLocator(TableName.valueOf(tableNameStr)))
}
}
- 命令行终端方式
- (利用importtsv方法迁徙tsv数据,这个缺点是数据只保存在内存中,并没有保存磁盘中)
HADOOP_CLASSPATH=`${HBASE_HOME}/bin/hbase classpath` \
${HADOOP_HOME}/bin/hadoop jar ${HBASE_HOME}/lib/hbase-server-1.2.0-cdh5.7.0.jar \
importtsv -Dimporttsv.columns=HBASE_ROW_KEY,info:name,info:age,info:address,info:phone \
student hdfs://hadoop001:8020/opt/testfile/importtsv
- 最常用的方法,而且能够大批量的迁徙数据,必会的
- 先将数据变成Hfile
HADOOP_CLASSPATH=`${HBASE_HOME}/bin/hbase classpath` ${HADOOP_HOME}/bin/hadoop jar \
${HBASE_HOME}/lib/hbase-server-1.2.0-cdh5.7.0.jar importtsv -Dimporttsv.columns=HBASE_ROW_KEY,info:name,info:age,info:address,info:phone \
-Dimporttsv.bulk.output=hdfs://hadoop001:8020/opt/testfile/hfileoutput \
student2 hdfs://hadoop001:8020/opt/testfile/importtsv
- 将Hfile同步到Hbase数据库里
HADOOP_CLASSPATH=`${HBASE_HOME}/bin/hbase classpath` ${HADOOP_HOME}/bin/hadoop jar ${HBASE_HOME}/lib/hbase-server-1.2.0-cdh5.7.0.jar \
completebulkload hdfs://hadoop001:8020/opt/testfile/hfileoutput student2
- 注意:当我们在向
Hbase
迁徙大量数据的时候,在创建表的时候需要创建多个Region
,因为默认创建一张表值创建一个Region
,那么当我们向表中插入大量数据的时候,由于只有一个Region
,而Region
是被RegionServer
管理的,由此会增加单个服务器的内存压力,有可能宕机。所以我们在创建表的时候应该多创建Region
,根据预估rowkey
来创建。