HBase 相关API操练(三):MapReduce操作HBase

MapReduce 操作 HBase

        在 HBase 系统上运行批处理运算,最方便和实用的模型依然是 MapReduce,如下图所示。

  HBase Table 和 Region 的关系类似 HDFS File 和 Block 的关系,HBase提供配套的 TableInputFormat 和 TableOutputFormat API,可以方便地将 HBase Table 作为 Hadoop MapReduce 的Source 和 Sink。对于 MapReduce Job 应用开发人员来说,基本不需要关注 HBase 系统本身的细节。

   org.apache.hadoop.hbase.mapreduce 包中的类和工具用来将 HBase 作为 MapReduce 作业的输出

1、 HBaseConfiguration

        org.apache.hadoop.hbase.HbaseConfiguration

继承了 org.apache.hadoop.conf.Configuration 类, 创建一个 HBaseConfiguration 对象实例,会返回读入 CLASSPATH 下 hbase-site.xml 文件和hbase-default.xml 文件中 HBase 配置信息的一个 Configuration,该Configuration 接下来会用于 创建 HBaseAdmin 和 HTable 实例。

        HBaseAdmin 和 HTable 两个类在 org.apache.hadoop.hbase.client 包中,HBaseAdmin 用于管理 HBase 集群、添加和删除表,HTable 用于访问指定的表,Configuration 实例指向了执行这些代码的集群上的这些类。

 

2、 HtableDescriptor 类和 HColumnDescriptor

        HBase 中表结构由 HTableDescriptor 描述(包括 HColumnDescriptor),对表的新增、修改、删除操作在接口 HMasterInterface 中定义,而该接口由 HMaster 实现。

        HTabledescriptor包含:

        1)表名、byte[] 和 String 格式。

        2)表的元信息,以Key-Value形式存储。存储信息包括文件最大的大小(默认256MB)、是否只读、Flush 时内存占用大小(默认64MB)、是否 root 或 Meta Region、DEFERRED_LOG_FLUSH。

        3)表的各 Family 描述 HColumnDescriptor。

        HColumnDescriptor 描述 Column Family 的信息,包括:

        1)压缩格式(不压缩或仅压缩 Value、压缩 Block 中的一系列记录)。

        2)数据的版本数量。

        3)Block 的大小。

        4)是否在内存中。

        5)是否 Cache Block。

        6)是否使用 bloomfilter。

        7)Cell 内容的存活时间。

        8)是否复制。

        当一个 Column Family 创建后,其参数不能修改,除非删除该 Column Family 后新建一个, 但删除 Column Family 也会删除该 Column Family 下的数据。另外,HTableDescriptor 中包含 ROOT_TABLEDESC 和 META_TABLEDESC 两个实例以描述-ROOT- 和 .META 表。

        1)ROOT_TABLEDESC 包含一个 info 的 Column Family。

        2)META_TABLEDESC 包含一个 info 和 historian,两个 Column Family。

 

3、 CreateTable 方法

        如果指定 Split Key 为该 Table 按指定键初始创建多个 Region,否则仅创建一个 Region,过程如下。

        1) 为 Table 创建 HRegionInfo

        2) 判断是不是所有的 Meta Region 都 online(由 RegionManager 的 MetaScanner 扫描线程分配 Meta Region)。

        3) 判断 ServerManager 是否有足够 Region Server 来创建 table。

        从 RegionManager 的 online MetaRegion 查找该 HRegionInfo 应放入哪一个 MetaRegion 中,在 onlineMetaRegion 中查找仅比RegionName 小的 MetaRegion,而RegionName 由 tableName、起始 Key 和 regionId(root 为0,meta 为1,user 当前时间)组成,同时 Master 的 ServerConnection 获取 HRegionInterface 代理连接到该 MetaRegion,并查找对应该 Table 为 Key 的记录是否存在,若存在则报错该表已经存在,由 RegionManager 根据 HRegionInfo 创建新的 user。Region 在 rootDir 目录下 新建以 tableName 为名的目录,在 tableName 目录下新建一个 Region 的目录(经编码后的 RegionName),并新建一个 HRegion 对象。

        disable、enable 和 delete 等操作封装在继承自 TableOperation 的类中,该类先获得要操作表的所有 MetaRegion,扫描这些 MetaRegion 中 所有该表的 user Region 信息并做相应处理,最后处理 MetaRegion。

 

4、 TableInputFormat

        通过设置 conf.set(TableInputFormat.INPUT_TABLE,"tableName") 设定 HBase 的输入表,tableName 为表名。

        设置 conf.set(TableInputFormat.SCAN,TableMRUtil.convertScanToString(scan)),设定对 HBase 输入表的 scan 方式。

setTable(new HTable(new Configuration(conf),tableName));

   通过 TableInputFormat.setConf(Configuration conf) 方法初始化 scan 对象;scan 对象是从 Job 中设置的对象,以字符串的形式传给 TableInputFormat,在 TableInputFormat 内部将 scan 字符串转换为 scan 对象,操作如下。

scan = TableMapReduceUtil.convertStringtoScan(conf.get(SCAN))
  TableInputFormat 继承 TableInputFormatBase 实现了 InputFormat 抽象类的两个抽象方法 getSplits() 和 createRecordReader()。

        getSplits() 判定输入对象的切分原则,原则是对于 TableInputFormatBase,会遍历HBase 相应表的所有 HRegion,每一个 HRegion 都会被切分成一个 Split,所以切分的块数与表中 HRegion 的数目是相同的,代码如下。

InputSplit split = new TableSplit(table.getTableName(),splitStart,splitStop,regionLocation); 

   在 Split 中只会记载 HRegion 的起始 Row Key 和 结束Row Key,具体去读取这片区域的数据是 createRecordReader() 实现的。

   对于一个 Map 任务,JobTracker 会考虑 TaskTracker 的网络位置,并选取一个距离其输入分片文件的最近的 TaskTracker。在理想情况下,任务是数据本地化的(data-local), 也就是任务运行在输入分片所在的节点上。同样,任务也可能是机器本地化的;任务和输入分片在同一个机架,但不在同一个节点上。

   对于Reduce 任务,JobTracker 简单地从待运行的 Reduce 任务列表中选取下一个来运行,用不着考虑数据段本地化。

   createRecordReader() 按照必然格式读取响应数据,接受 Split 块,返回读取记录的结果,操作代码如下。

public RecordReader< ImmutableBytesWritable,Result> createRecordReader(InputSplit split,TaskAttemptContext context){
  Scan scan = new Scan(this.scan);
  scan.setStartRow(tableSplit.getStartRow());
  scan.setStopRow(tableSplit.getEndRow());
  tableRecorderReader.setScan(scan);
  tableRecorderReader.setHTable(table);
  tableRecorderReader.init();
  return tableRecorderReader;
}

    tableRecorderReader.init() 返回的是这个分块的起始 Row Key 的记录;RecordReader 将一个 Split 解析成 < Key, Value> 的形式 提供给 map 函数,Key 就是Row Key,Value 就是对应的一行数据。

        RecorderReader 用于在划分中读取 < Key,Value> 对。RecorderReader 有5 个虚方法,下面分别进行介绍。

        1、initialize:初始化,输入参数包括该 Reader 工作的数据划分 InputSplit 和 Job 的上下文 context。

        2、nextKey:得到输入的下一个 Key,如果数据划分已经没有新的记录,返回空。

        3、nextValue:得到 Key 对应的 Value,必须在调用 nextKey 后调用。

        4、 getProgress:得到现在的进度。

        5、close:来自 java.io 的Closeable 接口,用于清理 RecorderReader。

        在 MapReduce 驱动中调用 TableInputFormat 的类:

 job.setInputFormatClass(TableInputFormat.class);

  使用以下方法就不需要再单独定义。

 tableMapReduceUtil.initTableReducerJob("daily_result", DailyReduce.class, job);

  initTableReducerJob() 方法完成一系列操作。

1) job.setOutputFormatClass(TableOutputFormat.class);设置输出格式。
2)conf.set(TableOutputFormat.OUTPUT_TABLE,table);设置输出表。
3) 初始化 partition。

 

向 HBase 中写入数据

本节介绍利用 MapReduce 操作 HBase,首先介绍如何上传数据,借助最熟悉的 WordCount 案例,将 WordCount 的结果存储到 HBase 而不是 HDFS。

输入文件 test.txt 的内容为:

hello hadoop
hadoop is easy

1)      编写 Mapper 函数。

public static class WordCountMapperHbase extends Mapper< Object, Text, ImmutableBytesWritable, IntWritable> {
               private final static IntWritable one = new IntWritable(1);
               private Text word = new Text();
 
               public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
                        StringTokenizer itr = new StringTokenizer(value.toString());
                        while (itr.hasMoreTokens()) {
                                word.set(itr.nextToken());
                                //输出到hbase的key类型为ImmutableBytesWritable
                                context.write(new ImmutableBytesWritable(Bytes.toBytes(word.toString())), one);
                        }
               }
       }

 

2)      编写 Reducer 类。

public class WordCountReducerHbase extends TableReducer< ImmutableBytesWritable, IntWritable, ImmutableBytesWritable> {  
               private IntWritable result = new IntWritable();
        public void reduce(ImmutableBytesWritable key, Iterable< IntWritable> values, Context context)  
                throws IOException, InterruptedException {  
                int sum = 0;
                        for (IntWritable val : values) {
                                sum += val.get();
                        }
                        Put put = new Put(key.get());//put 实例化  key代表主键,每个单词存一行
                        //三个参数分别为  列簇为content,列修饰符为count,列值为词频
                        put.add(Bytes.toBytes("content"), Bytes.toBytes("count"), Bytes.toBytes(String.valueOf(sum)));
                        context.write(key , put);
        }  
    }

3)      编写驱动类。

import java.io.IOException;
import java.util.StringTokenizer;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.client.HBaseAdmin;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil;
import org.apache.hadoop.hbase.mapreduce.TableReducer;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.io.IntWritable;
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;
/**
 * 
 *  将hdfs中的数据导入hbase
 *
 */
public class MapReduceWriteHbaseDriver {
public static void main(String[] args)throws Exception {  
               String tableName = "wordcount";//hbase 数据库表名
        Configuration conf=HBaseConfiguration.create(); //实例化Configuration 
        conf.set("hbase.zookeeper.quorum", "dajiangtai"); 
           conf.set("hbase.zookeeper.property.clientPort", "2181");//端口号
           
           //如果表已经存在就先删除
           HBaseAdmin admin = new HBaseAdmin(conf);
           if(admin.tableExists(tableName)){
                admin.disableTable(tableName);
                admin.deleteTable(tableName);
           }
           
           HTableDescriptor htd = new HTableDescriptor(tableName);
           HColumnDescriptor hcd = new HColumnDescriptor("content");
           htd.addFamily(hcd);//创建列簇
           admin.createTable(htd);//创建表
           
       Job job=new Job(conf,"import from hdfs to hbase");  
        job.setJarByClass(MapReduceWriteHbaseDriver.class);  
        
        job.setMapperClass(WordCountMapperHbase.class);  
 
        //设置插入hbase时的相关操作
        TableMapReduceUtil.initTableReducerJob(tableName, WordCountReducerHbase.class, job, null, null, null, null, false);
 
        job.setMapOutputKeyClass(ImmutableBytesWritable.class);  
        job.setMapOutputValueClass(IntWritable.class);  
        
        job.setOutputKeyClass(ImmutableBytesWritable.class);  
       job.setOutputValueClass(Put.class);
        
        FileInputFormat.addInputPaths(job, "hdfs://master:9000/handoop/test/test.txt");  
        System.exit(job.waitForCompletion(true) ? 0 : 1);  
  
    }
}

从hbase读取数据,输出到hdfs的数据如下所示。

easy    1
hadoop  2
hello   1
is      1

以上就是博主为大家介绍的这一板块的主要内容,这都是博主自己的学习过程,希望能给大家带来一定的指导作用,有用的还望大家点个支持,如果对你没用也望包涵,有错误烦请指出。如有期待可关注博主以第一时间获取更新哦,谢谢!

 

猜你喜欢

转载自www.cnblogs.com/zimo-jing/p/9164190.html