在大数据与AI融合的时代,HBase作为高可靠、高性能的列式数据库,常与MapReduce、Hive协同工作,构建从数据采集到机器学习的数据流水线。本文将深入解析HBase与MR、Hive的三种整合模式,并提供可落地的实践指南。

HBase与MR整合:三种数据流转场景

HBase与MapReduce的整合可归纳为三种典型场景,每种场景对应不同的数据流向和业务需求。理解这些模式,有助于在设计数据管道时做出合理选择。

  • HDFS → MR → HBase:从HDFS读取原始数据,经MR计算后写入HBase,适合离线批处理结果存储。
  • HBase → MR → HDFS:从HBase读取数据,MR分析后输出到HDFS,适合大规模数据导出与分析。
  • HBase → MR → HBase:读取HBase数据,计算后回写HBase,适合数据清洗与转换。

下面以实际案例逐一演示。

场景一:HDFS → MR → HBase

需求:从HDFS读取 /user/local/hello.txt,经MR计算后将单词统计结果写入HBase的 wordcount 表。

步骤1:在HBase中创建表并上传源数据到HDFS。

hbase(main):001:0> create 'wordcount', 'cf'
#Hdfs上输出准备
[root@node3 ~]# for i in `seq  100000`; do echo "hello bjsxt $i" >>hello.txt;done
[root@node3 ~]# hdfs dfs -mkdir /user/local
[root@node3 ~]# hdfs dfs -put hello.txt

步骤2:创建Maven项目,在 pom.xml 中添加HBase和Hadoop依赖。

  
    UTF-8
    3.1.3
    3.1.3
    2.0.5
  
  
    
    
      org.apache.hadoop
      hadoop-common
      ${hadoop.version}
    
    
      org.apache.hadoop
      hadoop-hdfs
      ${hadoop.version}
    
    
      org.apache.hadoop
      hadoop-hdfs-client
      ${hadoop.version}
    
    
    
    org.apache.hadoop
    hadoop-mapreduce-client-core
    ${mapreduce.version}
    
    
      org.apache.hadoop
      hadoop-mapreduce-client-jobclient
      ${mapreduce.version} provided
    
    
      org.apache.hadoop
      hadoop-mapreduce-client-common
      ${mapreduce.version}
    
    
    
      org.apache.hbase
      hbase-client
      ${hbase.version}
    
    
      org.apache.hbase
      hbase-common
      ${hbase.version}
    
    
      org.apache.hbase
      hbase
      ${hbase.version}
      pom
    
    
      org.apache.hbase
      hbase-server
      ${hbase.version}
    
    
      org.apache.hbase
      hbase-mapreduce
      ${hbase.version}
    
    
      junit
      junit
      4.13.2
      compile
    
  

步骤3:将Hadoop配置目录下的四个核心配置文件拷贝到项目 resources 目录,确保程序能访问集群。

步骤4:编写Mapper类 Hdfs2HbaseMapper,负责读取HDFS文本并输出单词及计数。

package com.wusen.hdfs2hbase;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
public class Hdfs2HbaseMapper extends Mapper {
    //定义输出的key
    private Text outKey = new Text();
    //定义输出的value
    private IntWritable outVal = new IntWritable(1);
    @Override
    protected void map(LongWritable key, Text value, Mapper.Context context) throws IOException, InterruptedException {
        //将读取的内容安装空格进行拆分
        String[] words = value.toString().split(" ");
        //遍历words,执行向外输出
        for(String word:words) {
            //将word封装到outKey中
            outKey.set(word);
            //输出
            context.write(outKey, outVal);
        }
    }
}

步骤5:编写Reducer类 Hdfs2HbaseReducer,汇总单词频率并通过 TableReducer 写入HBase。

package com.wusen.hdfs2hbase;
import org.apache.hadoop.hbase.client.Mutation;
import org.apache.hadoop.hbase.client.Put;
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.Reducer;
import java.io.IOException;
public class Hdfs2HbaseReducer extends TableReducer {
    @Override
    protected void reduce(Text key, Iterable values, Reducer.Context context) throws IOException, InterruptedException {
        //定义变量sum,表示当前单词出现的总次数
        int sum = 0;
        //遍历values
        for (IntWritable value : values) {
            sum += value.get();
        }
        //创建Put类的对象 ,单词左右rowkey
        //Put put = new Put(Bytes.toBytes(key.toString()));
        Put put = new Put(key.toString().getBytes());
        //为put指定列
        put.addColumn("cf".getBytes(), "count".getBytes(), Bytes.toBytes(sum));
        //输出
        context.write(key, put);
    }
}

步骤6:编写主入口类 Hdfs2HbaseMain,配置Job并指定输出表。

package com.wusen.hdfs2hbase;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import java.io.IOException;
public class Hdfs2HbaseMain
{
    public static void main( String[] args ) throws IOException, InterruptedException, ClassNotFoundException {
        Configuration conf = new Configuration(true);
        //设置本地运行
        conf.set("mapreduce.framework.name" ,"local");
        //指定hbase的zk集群
        conf.set("hbase.zookeeper.quorum" ,"node2,node3 ,node4");
        //创建job对象
        Job job = Job.getInstance(conf, "hdfs2hbase demo");
        //指定入口类
        job.setJarByClass(Hdfs2HbaseMain.class);
        //指定输入文件路径
        FileInputFormat.addInputPath(job,new Path("/user/local/hello.txt"));
        //指定Mapper相关属性
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(IntWritable.class);
        job.setMapperClass(Hdfs2HbaseMapper.class);
        //指定Reducer类,以及处理后的数据放入到Hbase的哪种表中
        TableMapReduceUtil.initTableReducerJob( "wordcount",//表名称
                Hdfs2HbaseReducer.class,//指定Reducer类
                job,//指定作业的job对象
                null,null,null,null,
                false//false表示不需要将依赖的jar上传到集群
        );
        //提交作业
        job.waitForCompletion(true);
    }
}

步骤7:运行程序,在HBase中查看结果。

提示:此模式常用于将离线分析结果导入HBase,供实时查询使用。

场景二:HBase → MR → HDFS

需求:读取HBase sentence 表数据,经MR统计单词数量后保存到HDFS。

步骤1:在HBase中创建 sentence 表。

hbase(main):002:0> create 'sentence','cf'

步骤2:hello.txt 下载到本地并添加到项目目录。

步骤3:编写 InsertSentence 类,将文本内容插入HBase的 sentence 表。

package com.wusen.hbase2hdfs;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.util.Bytes;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class InsertSentence {
    //定义连接对象
    private Connection connection;
    //定义Table对象
    private Table table;
    @Before
    public void before() throws IOException {
        //构造conf对象
        Configuration conf = HBaseConfiguration.create();
        //设置hbase用到的zk集群
        conf.set("hbase.zookeeper.quorum" ,"node2,node3,node4");
        //获取连接对象
        connection = ConnectionFactory.createConnection(conf);
        //获取表的DML对象
        table = connection.getTable(TableName.valueOf("sentence"));
    }
    @After
    public void close() throws IOException {
        if(table!=null){
        table.close();
        }
        if(connection!=null){
            connection.close();
        }
    }
    //优化:1000行数据插入一次
    @Test
    public void insertData() throws Exception {
        //从本地读取hello.txt
        BufferedReader bufferedReader =new BufferedReader(new FileReader(System.getProperty("user.dir")+ File.separator +"hello.txt"));
        //定义变量,表示读取到的当前行的内容
        String line = null;
        //定义rowkey
        int index = 1;
        //定义一个Put集合
        List putList = new ArrayList<>();
        //逐一读取文本中的内容,并写入到Hbase的sentence表中
        while((line = bufferedReader.readLine()) !=null){
            Put put = new Put(Bytes.toBytes(index));
            put.addColumn("cf".getBytes(),"line".getBytes( ),line.getBytes());
            //将put对象添加到putList中
            putList.add(put);
            //当index是1000的整数倍时执行一次批量插入
            if(index%1000==0){
                table.put(putList);
                //清空putList
                putList.clear();
            }
            index++;
        }
        if(!putList.isEmpty()){
            table.put(putList);}
        //关闭本地输入流对象
        bufferedReader.close();
    }
}

步骤4:执行 insertData() 方法,并在HBase中验证数据插入成功。

步骤5:编写Mapper类 Hbase2HdfsMapper,从HBase读取数据并拆分单词。

package com.wusen.hbase2hdfs;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.mapreduce.TableMapper;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
public class Hbase2HdfsMapper extends TableMapper {
    //定义输出的key value对象
    private Text keyOut = new Text();
    private IntWritable valOut = new IntWritable(1);
    @Override
    protected void map(ImmutableBytesWritable key, Result value, Mapper.Context context) throws IOException, InterruptedException {
        //key:对应的就是HBase表当前行数据的rowkey
        System.out.println("key:"+key.toString()) ;
        //value就是从hbase读取到的一行数据的Result对象
        // 读取当前行数据中的cf:line单元格中的数据
        byte[] data = value.getValue("cf".getBytes(), "line".getBytes());
        //将数据转换为字符串
        String line = Bytes.toString(data);
        //按照空格拆分
        String[] words = line.split(" ");
        //遍历输出,去掉最后那个行数
        for(int i = 0;i< words.length-1;i++){
            String word = words[i];
            keyOut.set(word);
            context.write(keyOut,valOut);
        }
    }
}

步骤6:编写Reducer类 Hbase2HdfsReducer,统计词频并输出到HDFS。

package com.wusen.hbase2hdfs;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class Hbase2HdfsReducer extends Reducer {
    //定义输出的value对象 ,不在reduce方法中定义的原因是可以减少垃圾对象的产生
    private IntWritable valOut = new IntWritable();
    @Override
    protected void reduce(Text key, Iterable values, Reducer.Context context) throws IOException, InterruptedException {
        //定义当前key代表的单词出现的总次数
        int sum = 0;
        //遍历values
        for(IntWritable value:values){
            sum += value.get();
        }
        //将sum的值封装到valOut对象中
        valOut.set(sum);
        //输出:hello 100000
        context.write(key,valOut);
    }
}

步骤7:编写主类 HBase2HdfsMain,配置输入表与输出路径。

package com.wusen.hbase2hdfs;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.io.IOException;
public class HBase2HdfsMain {
    public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException {
        Configuration conf = new Configuration(true);
        conf.set("mapreduce.framework.name" ,"local");
        conf.set("hbase.zookeeper.quorum" ,"node2, node3,node4");
        Job job = Job.getInstance(conf, "hbase2hdfs demo");
        job.setJarByClass(HBase2HdfsMain.class);
        //从HBase中的sentence表中读取数据
        // 可以通过该对象设置查询的列族、列、过滤行等
        Scan scan = new Scan();
        TableMapReduceUtil.initTableMapperJob( "sentence",//表名
                scan,
                Hbase2HdfsMapper.class, //指定Mapper类
                Text.class, IntWritable.class, //Mapper类输出的key\value的类型
                job,
                false
        );
        //设置Reducer相关属性
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class );
        job.setReducerClass(Hbase2HdfsReducer.class);
        //设置输出路径
        Path path = new Path("/user/local/wcout");
        //获取HDFS文件系统的对象
        FileSystem fileSystem = path.getFileSystem(conf);
        //判断输出路径是否存在
        if(fileSystem.exists(path)){
            //如果存在则删除
            fileSystem.delete(path,true);
        }
        FileOutputFormat.setOutputPath(job,path);
        //提交作业
        job.waitForCompletion(true);
    }
}

步骤8:运行程序,在HDFS上查看输出结果。

⚠️ 注意:使用 TableInputFormat 时需指定扫描的列族,避免全表扫描影响性能。

场景三:HBase → MR → HBase

需求:读取 sentence 表数据,MR计算后写入 wordcount 表。

步骤1:编写Mapper类 Hbase2HbaseMapper

package com.wusen.hbase2hbase;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.mapreduce.TableMapper;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
public class Hbase2HbaseMapper extends TableMapper {
    //定义输出的key和value对象
    private Text keyOut = new Text();
    private IntWritable valOut = new IntWritable(1);
    @Override
    protected void map(ImmutableBytesWritable key, Result value, Mapper.Context context) throws IOException, InterruptedException {
        //ImmutableBytesWritable key:表示当前行数据的rowkey,Result value:表示封装当前行数据的Result对象
        //从vlaue中获取cf:line值
        String line = Bytes.toString(value.getValue("cf".getBytes(), "line".getBytes()));
        //按照空格进行拆分
        String[] words = line.split(" ");
        //遍历单词数组
        for(String word:words){
            //将word封装到keyOut中
            keyOut.set(word);
            //输出
            context.write(keyOut,valOut); }
    }
}

步骤2:编写Reducer类 Hbase2HbaseReducer

package com.wusen.hbase2hbase;
import org.apache.hadoop.hbase.client.Mutation;
import org.apache.hadoop.hbase.client.Put;
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.Reducer;
import java.io.IOException;
//
public class Hbase2HbaseReducer extends TableReducer {
    @Override
    protected void reduce(Text key, Iterable values, Reducer.Context context) throws IOException, InterruptedException {
        //当前key对应的单词出现的总次数
        int sum = 0;
        //遍历values,计算当前单词出现的总次数
        for(IntWritable value:values){
            //累加操作
            sum += value.get();
        }
        //创建Put对象
        Put put = new Put(key.toString().getBytes());
        //添加列以及数据
        put.addColumn("cf".getBytes(),"count".getBytes(), Bytes.toBytes(sum));
        //输出
        context.write(key,put);
    }
}

步骤3:编写主类 Hbase2HbaseMain

package com.wusen.hbase2hbase;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil;
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 java.io.IOException;
public class Hbase2HbaseMain {
    public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException {
        Configuration conf = new Configuration(true);
        conf.set("mapreduce.framework.name" ,"local");
        conf.set("hbase.zookeeper.quorum" ,"node2, node3,node4");
        Job job = Job.getInstance(conf,"hbase2hbase demo");
        //设置job的入口程序
        job.setJarByClass(Hbase2HbaseMain.class);
        //从hbase中去读取数据
        Scan scan = new Scan();
        //指定查询的列
        scan.addColumn("cf".getBytes(), Bytes.toBytes("line"));
        TableMapReduceUtil.initTableMapperJob(
                "sentence",//从哪张表查询数据
                scan,//表扫描器
                Hbase2HbaseMapper.class,//Mapper类
                Text.class,
                IntWritable.class,//指定输出的key和value的类型
                job,//指定作业对象
                false);//不需要上传依赖的jar
        //处理后的结果写入到Hbase的表中
        TableMapReduceUtil.initTableReducerJob(
                "wordcount",//处理后的数据写入到hbase的哪张表中
                Hbase2HbaseReducer.class,//指定使用的Reducer类
                job,//对应的job对象
                null,null,null,null,
                false//不要上传依赖的jar包
        );
        job.waitForCompletion(true);
    }
}

步骤4:清除 wordcount 表原有数据,避免结果冲突。

hbase(main):003:0> truncate 'wordcount'
hbase(main):004:0> flush 'wordcount'

步骤5:运行程序并查询结果。

hbase(main):005:0> count "wordcount"
100002 row(s)
hbase(main):007:0> get 'wordcount','hello'
COLUMN                                                CELL
 cf:count                                             timestamp=1770177646313, value=\x00\x01\x86\xA0
1 row(s)

✅ 此模式适合数据清洗、格式转换等ETL任务,是构建数据仓库的常见环节。

[AFFILIATE_SLOT_1]

HBase与Hive整合:打通SQL与NoSQL

Hive提供了类SQL接口,与HBase整合后,可通过HiveQL操作HBase数据,极大降低使用门槛。整合的核心在于列映射存储处理器

准备工作

hive-site.xml 中添加ZooKeeper配置,确保Hive能访问HBase集群。


hive.zookeeper.quorum
node2,node3,node4 

hive.zookeeper.client.port 2181

启动Hadoop、HBase、Hive服务。

[root@node1 ~]# start-hbase.sh
[root@node3 ~]# hive --service metastore &
[root@node4 ~]# hive

内部表整合

使用 external 关键字创建外部表,不使用时创建内部表。必须指定 hbase.columns.mappingstored by

create[external] tablehbase_table_1(keystring,valuestring)

storedby 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'

with serdeproperties("hbase.columns.mapping" =":key,cf1:val")

tblproperties("hbase.table.name" = "xyz","hbase.mapred.output.outputtable" = "xyz");

在Hive中执行建表脚本:

[root@node4 ~]# hive
hive> CREATE TABLE hbasetbl(key int, value string)
STORED BY
'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES
("hbase.columns.mapping" = ":key,cf1:val")
TBLPROPERTIES ("hbase.table.name" = "xyz", "hbase.mapred.output.outputtable" ="xyz");

在HBase中查看表是否自动创建:

⚠️ 注意:创建内部表时,HBase中不能存在对应表,否则报错。

向HBase添加有映射关系的数据:

hbase(main):003:0> put 'xyz','1111','cf1:val','java'

在Hive中查询验证:

若添加无映射关系的数据,Hive中无法查询到,因为映射仅覆盖指定列。

put 'xyz','1111','cf1:name','zhangsan'

通过Hive INSERT 插入数据:

hive> insert into hbasetbl values(2222,'bigdata');

在HBase中查看数据变化:

但逐条插入效率低,推荐使用 LOAD DATA 批量加载,需借助临时中间表。

[root@node4 ~]# cd data/
[root@node4 data]# vim tbl.txt
333^Aphp
444^Ajsp
555^Azookeeper
hive> create temporary table tbl2(key int, value string);
hive> load  data local inpath '/root/data/tbl.txt' into table tbl2;
#将tbl2表中的数据插入到hbasetbl表中
hive> insert into table hbasetbl select * from tbl2;

查看HBase数据:

思考:数据到底存储在Hive还是HBase?刷新HBase后查看HDFS WebUI:

再看Hive数据目录:

结论:有映射关系的内部表数据实际存储在HBase中。

外部表整合

创建外部表要求HBase中必须已存在对应表,否则抛错。

先在HBase建表:

hbase(main):011:0> create 't_order', 'order'

在Hive中创建外部表:

hive> create external table tmp_order (key string, id string, user_id string)
stored by 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
with serdeproperties
("hbase.columns.mapping" =":key,order:order_id,order:user_id")
tblproperties ("hbase.table.name" = "t_order");

读写操作与内部表一致,不再赘述。

核心总结与AI应用延伸

HBase与MR、Hive的整合是大数据生态中的经典组合。通过MR可实现复杂计算,通过Hive可降低操作门槛。在AI场景中,这些技术常用于:

  • 特征工程:从HBase读取原始数据,MR清洗后写入Hive,供机器学习模型训练。
  • 自然语言处理:利用HBase存储文本语料,MR统计词频,为NLP任务准备数据。
  • 深度学习:将HBase作为特征存储,通过Hive SQL快速采样,加速神经网络训练。

掌握这些整合技巧,能让你在构建AI数据管道时游刃有余。

[AFFILIATE_SLOT_2]

关键要点回顾

  • Hive内部表:HBase中不能有对应表;外部表:HBase中必须有对应表。
  • 映射通过 WITH SERDEPROPERTIES ("hbase.columns.mapping" = ":key,cf:id,...") 定义。
  • STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler' 指定存储处理器。
  • TBLPROPERTIES ("hbase.table.name" = "my_table", "hbase.mapred.output.outputtable" = "my_table") 指定表名映射,若一致可省略。

✅ 希望本文能帮助你快速上手HBase与MR、Hive的整合,为AI项目打下坚实的数据基础。