hadoop组件---hbase--海量数据使用importtsv和Bulk load导入hbase最详细实践

我们本章来探索海量数据使用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工具。

Bulk Load介绍

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中。

把数据转化为HFile

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。
修改源码进行实现。

载入数据到HBase集群

将生成的HFile加载到HBase中,并在所有的regionserver上注册它们,即完成Complete Bulkload阶段。
在准备好数据文件后,可以在命令行中使用completebulkload工具或者LoadIncrementalHFiles完成Complete BulkLoad。
告诉RegionServers数据的位置并导入数据。这一步是最简单的,通常需要使用LoadIncrementalHFiles(更为人所熟知是completebulkload工具),将文件在HDFS上的位置传递给它,它就会利用RegionServer将数据导入到相应的区域。

importtsv命令说明

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实际操作示例

我们使用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

使用importtsv生成HFile

使用命令如下:

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文件已生成如下图:

把HFile注册到hbase中

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查询数据已经成功导入了如图:

自己编写Mapreduce把数据转化为HFile示例

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

你可能感兴趣的:(hbase)