我们本章来探索海量数据使用importtsv和Bulk load导入hbase最详细实践。
我们之前已经尝试了使用kettle把mongodb的数据导入hbase,但是发现速度比较慢。
单机kettle导入hbase,6700万条数据使用了27个小时,4亿多条数据需要的时间就更多了。
HBase本身提供了很多种数据导入的方式,目前常用的有三种常用方式:
1、使用HBase原生Client API
2、使用HBase提供的TableOutputFormat,原理是通过一个Mapreduce作业将数据导入HBase
3、使用Bulk Load方式直接生成HFile,分别分发到regionServer中
第一第二种方式由于需要频繁的与数据所存储的RegionServer通信,一次性入库大量数据时,可能占用大量Regionserver资源,影响存储在该Regionserver上其他表的查询。
了解过HBase底层原理的应该都知道,HBase在HDFS中是以HFile文件结构存储的,一个比较高效便捷的方法就是使用 Bulk Load方式直接生成HFile,即使用HBase提供的HFileOutputFormat类(目前已经有改良版本的HFileOutputFormat2类)或者ImportTSV工具。
BulkLoad是一种高效写入HBase的方式,适用于将数据批量迁移到HBase。
BulkLoad使用MapReduce作业直接生成HBase的StoreFile,并将生成的StoreFile直接装载入正在运行的HBase集群。较之使用HBase的API,使用BulkLoad耗费的CPU和网络资源都相对较少。
因为BulkLoad绕过了正常写数据的路径(WAL、MemStore、flush),尤其是WAL,通过WAL进行的Cluster Replication就不会处理BulkLoad装载的数据。
我们在第一次建立Hbase表的时候,我们可能需要往里面一次性导入大量的初始化数据。我们很自然地想到将数据一条条插入到Hbase中,或者通过MR方式等。但是这些方式不是慢就是在导入的过程的占用Region资源导致效率低下,所以很不适合一次性导入大量数据。我们将针对这个问题介绍如何通过Hbase的BulkLoad方法来快速将海量数据导入到Hbase中。
总的来说,使用 Bulk Load 方式由于利用了 HBase 的数据信息是按照特定格式存储在 HDFS 里的这一特性,直接在 HDFS 中生成持久化的 HFile 数据格式文件,然后完成巨量数据快速入库的操作,配合 MapReduce 完成这样的操作,不占用 Region 资源,不会产生巨量的写入 I/O,所以需要较少的 CPU 和网络资源。Bulk Load 的实现原理是通过一个 MapReduce Job 来实现的,通过 Job 直接生成一个 HBase 的内部 HFile 格式文件,用来形成一个特殊的 HBase 数据表,然后直接将数据文件加载到运行的集群中。与使用HBase API相比,使用Bulkload导入数据占用更少的CPU和网络资源。
要完成Bulk Load分成三步操作。
从数据源(通常是文本文件或其他的数据库)提取数据并上传到HDFS。抽取数据到HDFS和Hbase以及Bulk Load并没有关系,所以大家可以选用自己擅长的方式进行,本文就不介绍了。一般使用sqoop或者kettle抽取数据保存到HDFS中。
BulkLoad的第一步是利用MapReduce作业处理准备好的数据转化为HFile 。
这个步骤也有两种操作方式
一、使用HFileOutputFormat2类生成HBase数据文件(StoreFile)。
这需要一个MapReduce作业,可以自己来实现Map方法来,HBase来完成后面的Reducer操作。最后,每一个region的HFile将会在输出目录被创建出来。
该Map作业需要使用rowkey(行键)作为输出Key;KeyValue、Put或者Delete作为输出Value。MapReduce作业需要使用HFileOutputFormat2来生成HBase数据文件。为了有效的导入数据,需要配置HFileOutputFormat2使得每一个输出文件都在一个合适的区域中。为了达到这个目的,MapReduce作业会使用Hadoop的TotalOrderPartitioner类根据表的key值将输出分割开来。HFileOutputFormat2的方法configureIncrementalLoad()会自动的完成上面的工作。
为了使最终生成的每个HFile都能对应一个Region,需要在MapReduce作业中使用TotalOrderPartitioner类对map的输出结果进行partition,使之与Region的RowKey范围达到一致。幸运的是HFileOutputFormat2类的configureIncrementalLoad()已经做了这个工作,它会根据HBase表中现有的Region边界自动配置TotalOrderPartitioner。
生成HFile程序说明:
1、最终输出结果,无论是map还是reduce,输出部分key和value的类型必须是: < ImmutableBytesWritable, KeyValue>或者< ImmutableBytesWritable, Put>。
2、最终输出部分,Value类型是KeyValue 或Put,对应的Sorter分别是KeyValueSortReducer或PutSortReducer。
3、MR例子中job.setOutputFormatClass(HFileOutputFormat.class); HFileOutputFormat只适合一次对单列族组织成HFile文件。
4、MR例子中HFileOutputFormat.configureIncrementalLoad(job, table);自动对job进行配置。SimpleTotalOrderPartitioner是需要先对key进行整体排序,然后划分到每个reduce中,保证每一个reducer中的的key最小最大值区间范围,是不会有交集的。因为入库到HBase的时候,作为一个整体的Region,key是绝对有序的。
5、MR例子中最后生成HFile存储在HDFS上,输出路径下的子目录是各个列族。如果对HFile进行入库HBase,相当于move HFile到HBase的Region中,HFile子目录的列族内容没有了
二、使用importtsv命令生成HBase数据文件HFile
ImportTsv是Hbase提供的一个命令行工具,可以将存储在HDFS上的自定义分隔符(默认\t)的数据文件(TSV,CSV等等),通过一条命令方便的导入到HBase表的工具。它的原理跟HFileOutputFormat2类方式是类似的,只是有相应的封装。
ImportTsv相关源码
ImportTsv.java
TsvImporterMapper.java
两种方式的选择
importtsv工具可以满足大多数场景,用户有时希望自己编程生成数据,或以其他格式导入数据,比如importtsv需要在导入前确定每条数据column维度,一旦我们的数据的维度是根据数据内容本身的,importtsv就无法满足需求,这时就需要对工具改造,可以查看ImportTsv.java和HFileOutputFormat的javaDoc。
修改源码进行实现。
将生成的HFile加载到HBase中,并在所有的regionserver上注册它们,即完成Complete Bulkload阶段。
在准备好数据文件后,可以在命令行中使用completebulkload工具或者LoadIncrementalHFiles完成Complete BulkLoad。
告诉RegionServers数据的位置并导入数据。这一步是最简单的,通常需要使用LoadIncrementalHFiles(更为人所熟知是completebulkload工具),将文件在HDFS上的位置传递给它,它就会利用RegionServer将数据导入到相应的区域。
importtsv其实也支持两种方式的导入。
第一种是使用TableOutputformat在reduce中插入数据;
第二种是先生成HFile格式的文件,再执行一个叫做CompleteBulkLoad的命令,将文件move到HBase表空间目录下,同时提供给client查询。
查看ImportTsv的入口类是org.apache.hadoop.hbase.mapreduce.ImportTsv源码如下:
String hfileOutPath = conf.get(BULK_OUTPUT_CONF_KEY);
String columns[] = conf.getStrings(COLUMNS_CONF_KEY);
if (hfileOutPath != null) {
if (!admin.tableExists(tableName)) {
LOG.warn(format("Table '%s' does not exist.", tableName));
// TODO: this is backwards. Instead of depending on the existence of a table,
// create a sane splits file for HFileOutputFormat based on data sampling.
createTable(admin, tableName, columns);
}
HTable table = new HTable(conf, tableName);
job.setReducerClass(PutSortReducer.class);
Path outputDir = new Path(hfileOutPath);
FileOutputFormat.setOutputPath(job, outputDir);
job.setMapOutputKeyClass(ImmutableBytesWritable.class);
if (mapperClass.equals(TsvImporterTextMapper.class)) {
job.setMapOutputValueClass(Text.class);
job.setReducerClass(TextSortReducer.class);
} else {
job.setMapOutputValueClass(Put.class);
job.setCombinerClass(PutCombiner.class);
}
HFileOutputFormat.configureIncrementalLoad(job, table);
} else {
if (mapperClass.equals(TsvImporterTextMapper.class)) {
usage(TsvImporterTextMapper.class.toString()
+ " should not be used for non bulkloading case. use "
+ TsvImporterMapper.class.toString()
+ " or custom mapper whose value type is Put.");
System.exit(-1);
}
// No reducers. Just write straight to table. Call initTableReducerJob
// to set up the TableOutputFormat.
TableMapReduceUtil.initTableReducerJob(tableName, null, job);
job.setNumReduceTasks(0);
}
可以看到ImportTsv.createSubmittableJob方法中判断参数BULK_OUTPUT_CONF_KEY直接影响ImportTsv的Mapreduce作业最终以哪种方式入HBase库
如果不为空并且用户没有自定义Mapper实现类(参数importtsv.mapper.class)时,则使用PutSortReducer,其中会对Put排序,如果每行记录有很多column,则会占用Reducer大量的内存资源进行排序。
Configuration conf = job.getConfiguration();
HBaseConfiguration.merge(conf, HBaseConfiguration.create(conf));
job.setOutputFormatClass(TableOutputFormat.class);
如果为空,调用TableMapReduceUtil.initTableReducerJob初始化TableOutputformat的Reducer输出,此方式不需要使用Reducer,因为直接在mapper的Outputformat中会批量的调用Put API将数据提交到Regionserver上(相当于并行的执行HBase Put API)。
我们知道TableOutputformat方式会直接往hbase中写数据,的是比较耗费资源的。
所以需要注意的是在使用ImportTsv时,一定要注意参数importtsv.bulk.output的配置,通常来说使用Bulk output的方式对Regionserver来说更加友好一些,这种方式加载数据几乎不占用Regionserver的计算资源,因为只是在HDFS上移动了HFile文件。
两种方式的命令区别如下:
TableOutputformat
hbase org.apache.hadoop.hbase.mapreduce.ImportTsv -Dimporttsv.columns=a,b,c
bulkoutput方式
hbase org.apache.hadoop.hbase.mapreduce.ImportTsv -Dimporttsv.columns=a,b,c -Dimporttsv.bulk.output=hdfs://storefile-outputdir
ImportTsv参数
-Dimporttsv.columns=HBASE_ROW_KEY,temp:in,temp:out,vibration,pressure:in,pressure:out 输入行的格式,注意顺序temp 和vibration,pressure都是列族,in和out是列名,我们根据自己的表格进行修改。
-Dimporttsv.skip.bad.lines=false - 若遇到无效行则失败
-Dimporttsv.separator=, - 使用特定分隔符,默认是tab也就是\t
-Dimporttsv.timestamp=currentTimeAsLong - 使用导入时的时间戳
-Dimporttsv.mapper.class=my.Mapper - 使用用户自定义Mapper类替换TsvImporterMapper
-Dmapreduce.job.name=jobName - 对导入使用特定mapreduce作业名
-Dcreate.table=no - 避免创建表,注:如设为为no,目标表必须存在于HBase中
-Dno.strict=true - 忽略HBase表列族检查。默认为false
-Dimporttsv.bulk.output=/user/yarn/output 作业的输出目录
先创建出输出目录,再上传数据
我们使用importtsv和Bulk load来进行详细的操作示例。
首先需要确定我们的表格需要什么格式的数据。
在服务器上进入hbase shell中创建表格如下:
create 'posFrequency', 'info'
新建实验数据集
使用命令
vim test.txt
输入内容如下
86743654:2,rs123,2,86743654
312312:3,rs4543,3,312312
7666554:5,rs655,5,7666554
9787778:6,rs988,6,9787778
创建目录上传文件到HDFS使用命令
hdfs dfs -mkdir -p /user/hbase/test
hadoop fs -put test.txt /user/hbase/test
hadoop fs -ls /user/hbase/test
hadoop fs -chmod -R 777 /user/hbase/test
使用命令如下:
hbase org.apache.hadoop.hbase.mapreduce.ImportTsv '-Dimporttsv.separator=,' -Dimporttsv.columns='HBASE_ROW_KEY,info:rs,info:chr,info:pos' -Dimporttsv.bulk.output=/user/hbase/test/output posFrequency /user/hbase/test
作业执行完成后,我们查看一下输出目录,相应列簇的HFile已经创建。
hdfs dfs -ls /user/hbase/test
HFile文件已生成如下图:
LoadIncrementalHFiles方式
使用命令
hbase org.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles /user/hbase/test/output posFrequency
执行时,如果卡死在提示“INFO mapreduce.LoadIncrementalHFiles: Trying to load hfile=hdfs”处
例如执行:hbase org.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles /tmp/output tab1,出现上述卡死现象。查看Region Server日志时,发现提示权限拒绝之类的异常。所以修改命令为:sudo -u hbase hbase org.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles /tmp/output tab1 即可解决此问题。
如果原因不一样则需要根据Region Server日志排查。
如图
如果是目录权限问题
Caused by: org.apache.hadoop.security.AccessControlException: Permission denied: user=hbase, access=WRITE, inode=”/user/hbase/test/output/info”:zzq:supergroup:drwxr-xr-x
则使用命令
hadoop fs -chmod -R 777 /user/hbase/test/output/info
导入完成后如图
completebulkload方式
使用命令
hadoop jar lib/hbase-server-0.96.0.jar completebulkload hfile_tmp3 hbase-tbl-003
注意:执行该命令时,hadoop很可能会找不到相关的hbase依赖包,出现ClassNotFoundException的异常。一个简单的解决方法是将hbase相关的jar包都放到${HADOOP_HOME}/share/hadoop/common/lib下面,这样hadoop就能在运行前加载相关的hbase jar files。
zookeeperException
解决办法:zookeeper没有找到,尝试:1、关闭防火墙;2、hadoop的hadoop-env.sh 里面配置HADOOP_CLASSPATH包含hbase的一些jar,core-site.xml里面配置好zookeeper的几个server。重启hadoop、hbase、zookeeper。
进入hbase shell,检查posFrequency表格的数据
使用命令
scan 'posFrequency'
数据量文件大小27G
使用命令查看
hdfs dfs -ls -h /user/hbase/frequency
3亿条数据抽取到hdfs花费了13小时
importTsv命令执行花费了2小时
LoadIncrementalHFiles命令花费了2秒,把HFile文件注册到hbase速度还是很快的
scan查询数据已经成功导入了如图:
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FsShell;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.client.HTable;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.mapreduce.HFileOutputFormat2;
import org.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
public class BulkLoadJob {
static Logger logger = LoggerFactory.getLogger(BulkLoadJob.class);
public static class BulkLoadMap extends
Mapper {
public void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
String[] valueStrSplit = value.toString().split("\t");
String hkey = valueStrSplit[0];
String family = valueStrSplit[1].split(":")[0];
String column = valueStrSplit[1].split(":")[1];
String hvalue = valueStrSplit[2];
final byte[] rowKey = Bytes.toBytes(hkey);
final ImmutableBytesWritable HKey = new ImmutableBytesWritable(rowKey);
Put HPut = new Put(rowKey);
byte[] cell = Bytes.toBytes(hvalue);
HPut.add(Bytes.toBytes(family), Bytes.toBytes(column), cell);
context.write(HKey, HPut);
}
}
public static void main(String[] args) throws Exception {
Configuration conf = HBaseConfiguration.create();
String inputPath = args[0];
String outputPath = args[1];
HTable hTable = null;
try {
Job job = Job.getInstance(conf, "ExampleRead");
job.setJarByClass(BulkLoadJob.class);
job.setMapperClass(BulkLoadJob.BulkLoadMap.class);
job.setMapOutputKeyClass(ImmutableBytesWritable.class);
job.setMapOutputValueClass(Put.class);
// speculation
job.setSpeculativeExecution(false);
job.setReduceSpeculativeExecution(false);
// in/out format
job.setInputFormatClass(TextInputFormat.class);
job.setOutputFormatClass(HFileOutputFormat2.class);
FileInputFormat.setInputPaths(job, inputPath);
FileOutputFormat.setOutputPath(job, new Path(outputPath));
hTable = new HTable(conf, args[2]);
HFileOutputFormat2.configureIncrementalLoad(job, hTable);
if (job.waitForCompletion(true)) {
FsShell shell = new FsShell(conf);
try {
shell.run(new String[]{"-chmod", "-R", "777", args[1]});
} catch (Exception e) {
logger.error("Couldnt change the file permissions ", e);
throw new IOException(e);
}
//加载到hbase表
LoadIncrementalHFiles loader = new LoadIncrementalHFiles(conf);
loader.doBulkLoad(new Path(outputPath), hTable);
} else {
logger.error("loading failed.");
System.exit(1);
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} finally {
if (hTable != null) {
hTable.close();
}
}
}
}
ImportTsv-HBase数据导入工具
http://blog.csdn.net/opensure/article/details/47111705
[HBase] 使用ImportTsv命令导入数
http://blog.sina.com.cn/s/blog_72ef7bea0102vlvd.html
使用importtsv命令加载数据
http://debugo.com/importtsv-load-data/
Import CSV data into HBase using importtsv
https://community.hortonworks.com/articles/4942/import-csv-data-into-hbase-using-importtsv.html
ImportTSV Data from HDFS into HBase
https://acadgild.com/blog/importtsv-data-from-hdfs-into-hbase/
通过BulkLoad快速将海量数据导入到Hbase[Hadoop篇]
https://www.iteblog.com/archives/1889.html
Bulk Loading
http://hbase.apache.org/0.94/book/arch.bulk.load.html
HBase Bulk Load
http://www.zhyea.com/2017/02/09/hbase-bulk-load.html
使用 Bulk Load 快速向 HBase 中导入数据
http://blog.jrwang.me/2015/import-data-to-hbase-using-bulk-loding/
How-to: Use HBase Bulk Loading, and Why
https://blog.cloudera.com/blog/2013/09/how-to-use-hbase-bulk-loading-and-why/
Bulk Loading Data to HBase
http://milinda.pathirage.org/2016/12/11/hbase-bulk-load.html
HBase BulkLoad
http://zqhxuyuan.github.io/2015/12/19/2015-12-19-HBase-BulkLoad/
Bulk Load-HBase数据导入最佳实践
http://blog.csdn.net/opensure/article/details/47054861