08-Hbase

08-HBase

1.目标概述

  • 了解HBase基本概念
  • 了解HBase架构
  • 掌握HBase集群环境搭建
  • 掌握HBase中shell操作
  • 掌握HBase中java API操作
  • 掌握rowKey的设计原则
  • 【延伸】MapReduce操作HBase
  • 【延伸】spark操作HBase

2.HBase基础

2.1.基本概念

2.1.1.介绍

官网地址:http://hbase.apache.org/

HBase在Hadoop之上提供了类似于Bigtable的能力,是BigTable的开源实现版本,通过java编程语言实现 。HBase不同于关系数据库,它是一个适合于非结构化数据存储的数据库

HBase是一个分布式的、面向列的开源数据库 ,是一个NoSQL分布式数据存储系统,实现对大型数据的实时、随机读写访问

HBase利用Hadoop HDFS作为文件存储系统,利用Hadoop MapReduce来处理HBase中的海量数据 。

  • HBase依赖于hdfs做底层的数据存储
  • HBase依赖于mapReduce做数据计算
  • HBase依赖于zookeeper做服务协调

在这里插入图片描述

2.1.2.特点
  • 提供高可靠、高性能、列存储、可伸缩、实时读写的nosql数据库系统

    • 介于NoSQL和RDBMS之间,仅能通过主键(row key)和主键的范围(range)来检索数据
    • 主要用来存储非结构化和半结构化的松散数据
    • HBase中底层数据存储都是字节数组:byte[]
  • HBase中表的特点

    • 大:一个表可以有上十亿行,上百万列

    • 面向列:面向**列(族)**的存储和权限控制,列(簇)独立检索

    • 稀疏:空(null)列不占用存储空间,表可以设计的非常稀疏

    • 无模式:每行都有一个可排序的主键和任意多的列,列可以动态增加,不同的行可以有不同的列

  • 和行式数据库的区别

    • 行式数据库在分析的时候,操作整行数据。比如将字段id,name,age,sex,score等完整的信息读入内存,造成大量的内存和IO资源浪费
    • 列式数据库是把行式数据库全部拆开,按照列的方式重新组合存储,一列所有的行的数据存放在一起。带来的好处就是,比如要分析男女信息,就直接访问所有的男女信息;要分析销售额,就直接访问消费额相关的数据。

    在这里插入图片描述

2.2.HBase表结构

在这里插入图片描述

  • 表(table):用于存储管理数据,具有稀疏的、面向列的特点。HBase中的每一张表,就是所谓的大表(Bigtable),可以有上十亿行,上百万列。对于值为空的列,并不占用存储空间,因此表可以设计的非常稀疏

  • 行键(RowKey):类似于mysql中的主键,HBase根据行键来快速检索数据,一个行键对应一条记录。

  • 列簇(ColumnFamily):是列的集合列簇在表定义时需要指定插入数据时动态指定。列中的数据都是以二进制形式存在,没有数据类型。在物理存储结构上,每个表中的每个列簇单独以一个文件存储,一个表可以有多个列簇(官方推荐小于等于3个

  • 时间戳(TimeStamp):是列的一个属性,是一个64位整数。由行键和列确定的单元格(Cell),可以存储多个数据,每个数据含有时间戳属性,数据具有版本特性。可根据版本(VERSIONS)或时间戳来指定查询历史版本数据,如果都不指定,则默认返回最新版本的数据

  • 参考案例:

    mysql数据库表:

    在这里插入图片描述

    转换对应成HBase表:

    在这里插入图片描述

2.3.HBase整体架构

在这里插入图片描述

2.3.1.Client
  • 通过 RPC机制与HMaster和HRegionServer进行通信
  • Client与HMaster进行通信进行管理类操作
  • Client与HRegionServer进行数据读写类操作
2.3.2.Zookeeper
  • 保证任何时候,集群中只有一个运行状态的 Master,避免脑裂问题
  • 存储所有Region的寻址入口,包括**-ROOT-**表地址、HMaster地址
  • 实时监控Region Server的状态,将Region server的上线和下线信息,实时通知给Master
2.3.3.HMaster
  • 可以启动多个HMaster,通过Zookeeper的Master Election机制保证有且仅有一个Master提供服务
  • 作用:
    • 为Region server分配region
    • 负责region server的负载均衡
    • 发现失效的region serve并重新分配其上的region
    • HDFS上的垃圾文件回收
2.3.4.HRegionServer
  • HBase中最核心的模块,主要负责响应用户I/O请求,向HDFS文件系统中读写数据
  • 作用:
    • 维护Master分配给它的region,处理对这些region的IO请求
    • 负责切分在运行过程中变得过大的region
    • HRegionServer管理一些列HRegion对象,每个HRegion对应Table中一个Region,HRegion由多个HStore组成,每个HStore对应Table中一个Column Family的存储,Column Family就是一个集中的存储单元集
2.3.5.HStore

HBase存储的核心,由MemStore和StoreFile组成。

用户写入数据的流程为:client写入 -> 存入MemStore,一直到MemStore满 -> Flush成一个StoreFile,直至增长到一定阈值 -> 触发Compact合并操作 -> 多个StoreFile合并成一个StoreFile,同时进行版本合并和数据删除 -> 当StoreFiles Compact后,逐步形成越来越大的StoreFile -> 单个StoreFile大小超过一定阈值后,触发Split操作,把当前Region Split成2个Region,原先的Region会下线,新Split出的2个孩子Region会被HMaster分配到相应的HRegionServer上,使得原先1个Region的压力得以分流到2个Region上。如图所示:

在这里插入图片描述

2.3.6.HRegion

一个表最开始存储的时候,是一个Region。

一个Region中会有个多个store,每个store用来存储一个列簇。如果只有一个column family,就只有一个store。Region会随着插入的数据越来越多,会进行拆分。默认大小是10G一个。

2.3.7.HLog

在分布式系统环境中,无法避免系统出错或者宕机,一旦HRegionServer意外退出,MemStore中的内存数据就会丢失,引入HLog就是防止这种情况。

工作机制:每个HRegionServer中都会有一个HLog对象,HLog是一个实现Write Ahead Log的类,每次用户操作写入Memstore的同时,也会写一份数据到HLog文件,HLog文件定期会滚动出新,并删除旧的文件(已持久化到StoreFile中的数据)。

当HRegionServer意外终止后,HMaster会通过Zookeeper感知,HMaster首先处理遗留的HLog文件,将不同Region的log数据拆分,分别放到相应Region目录下,然后再将失效的Region重新分配,领取到这些Region的HRegionServer在Load Region的过程中,会发现有历史HLog需要处理,因此会Replay HLog中的数据到MemStore中,然后flush到StoreFiles,完成数据恢复。

2.4.查询路由

HBase中存有两张特殊的表,-ROOT-.META.

  • .META.:记录了用户表的Region信息,.META.可以有多个Regoin
  • -ROOT-:记录了.META.表的Region信息,-ROOT-只有一个Region。Zookeeper中记录了-ROOT-表的location

客户端访问数据流程:

访问zookeeper->访问-ROOT-表->访问.META.表->找到用户数据的位置,再去访问数据

在这里插入图片描述

3.HBase集群部署

3.1.选择jdk版本和hadoop版本

参考地址:http://hbase.apache.org/1.2/book.html

jdk版本:

在这里插入图片描述

hadoop版本:

在这里插入图片描述

3.2.下载HBase

参考地址:http://mirrors.hust.edu.cn/apache/hbase/

在这里插入图片描述

3.3.上传HBase到linux主机,并解压

cd /export/softwares

tar -zxvf hbase-1.3.1-bin.tar.gz -C /export/servers

#解压完成后删除压缩包
rm -rf hbase-1.3.1-bin.tar.gz

#重新命名hbase
cd /export/servers
mv hbase-1.3.1 hbase131

在这里插入图片描述

3.4.修改配置文件

3.4.1.regionservers

说明:配置HRegionservers节点列表。

cd /export/servers/hbase131/conf
vi regionservers

node02
node03
3.4.2.hbase-env.sh

说明:配置jdk环境变量,配置集群通过zookeeper管理,不由hbase自己管理。

#配置jdk环境变量
export JAVA_HOME=/export/servers/jdk1.8.0_141

#配置集群交给zookeeper管理
export HBASE_MANAGES_ZK=false
3.4.3.hbase-site.xml

说明:hbase的核心配置文件。

vim hbase-site.xml

<configuration>
  <!--配置hbase在hdfs上的存储路径,
	非ha环境:hdfs://master:9000/hbase
	ha环境:hdfs://ns1/hbase
  -->
  <property>
    <name>hbase.rootdir</name>
    <value>hdfs://node01:8020/hbase</value>
  </property>
  <!--开启集群分布式模式-->
  <property>
     <name>hbase.cluster.distributed</name>
     <value>true</value>
  </property>
  <!--hbase Master端口-->
  <property>
     <name>hbase.master.port</name>
     <value>16000</value>
  </property>
  <!--配置zookeeper集群主机列表-->
  <property>
    <name>hbase.zookeeper.quorum</name>
    <value>node01,node02,node03</value>
  </property>
  <!--配置zookeeper主机端口-->
  <property>
    <name>hbase.zookeeper.property.clientPort</name>
    <value>2181</value>
  </property>
</configuration>
3.4.4. backup-masters

说明:配置备用master节点。backup-masters需要手动创建。

vim backup-masters

node02
3.4.5.拷贝hadoop中hdfs的核心配置文件到hbase
cp /export/servers/hadoop-2.6.0-cdh5.14.0/etc/hadoop/hdfs-site.xml /export/servers/hbase131/conf/
cp /export/servers/hadoop-2.6.0-cdh5.14.0/etc/hadoop/core-site.xml /export/servers/hbase131/conf/

在这里插入图片描述

3.5.把配置好的HBase远程拷贝到其它主机节点

scp -r /export/servers/hbase131 root@node02:/export/servers

scp -r /export/servers/hbase131 root@node03:/export/servers

3.6.配置环境变量

vim /etc/profile

export HBASE_HOME=/export/servers/hbase131
export PATH=$PATH:${HBASE_HOME}/bin

#让环境变量生效
source /etc/profile

3.7.启动HBase集群

3.7.1.系统时钟同步

说明:HBase对于集群时间的同步要求非常严格,要求相差不要超过30秒。启动集群前一定要同步系统时间

ntpdate -u 0.uk.pool.ntp.org
3.7.2.启动zookeeper集群
 /export/servers/zookeeper-3.4.5-cdh5.14.0/bin/zkServer.sh start

在这里插入图片描述

3.7.3.启动hdfs
3.7.4.启动hbase集群
/export/servers/hbase131/bin/start-hbase.sh 

在这里插入图片描述

3.7.5.访问hbase的web界面

地址:http://192.168.53.100:16010/master-status

在这里插入图片描述

3.7.6.如何启动单节点进程

说明:如果某些主机节点进程未启动正常,可以通过以下方式进行单独启动。

#单独启动master
hbase-daemon.sh start master

#单独启动regionserver
hbase-daemon.sh start regionserver

4.HBase shell操作

4.1.shell交互界面整体操作

4.1.1.启动shell交互界面
cd /export/servers/hbase131/bin
#启动shell交互界面
hbase shell

#查看集群状态信息
status

#查看版本信息
version
4.1.2.退出shell交互界面
exit

在这里插入图片描述

4.1.3.查看帮助信息
#查看全部帮助信息
help

#查看指定的命令帮助信息
help "create"

图一:

在这里插入图片描述

图二:

在这里插入图片描述

4.2.操作名称空间namespace

说明:在hbase中,namespace相当于关系数据库的database。用于从逻辑上对表进行分组。hbase系统默认有两个缺省的名称空间:

  • hbase:系统内建表,包括namespace和meta表
  • default:创建表时,如果不指定namespace的表 ,则在default名称空间下
#列出所有名称空间
list_namespace

#查看指定名称空间
describe_namespace 'hbase'

#创建名称空间
create_namespace 'my_ns'

#删除名称空间
drop_namespace 'my_ns'

在这里插入图片描述

4.3.操作表table

4.3.1.案例

说明:将关系数据库中的表,转换成hbase中的表进行操作。

mysql数据库表

在这里插入图片描述

hbase表

在这里插入图片描述

4.3.2.命令操作
#列出所有表
list

#创建表(create '名称空间:表名称','列簇')
create 'my_ns:user','base_info'

#插入数据(put '名称空间:表名称','行键','列簇:列名','数据')
put 'my_ns:user','rowkey_10','base_info:username','张三'
put 'my_ns:user','rowkey_10','base_info:birthday','2014-07-10'
put 'my_ns:user','rowkey_10','base_info:sex','1'
put 'my_ns:user','rowkey_10','base_info:address','北京市'

put 'my_ns:user','rowkey_16','base_info:username','张小明'
put 'my_ns:user','rowkey_16','base_info:birthday','2014-07-10'
put 'my_ns:user','rowkey_16','base_info:sex','1'
put 'my_ns:user','rowkey_16','base_info:address','北京'

put 'my_ns:user','rowkey_22','base_info:username','陈小明'
put 'my_ns:user','rowkey_22','base_info:birthday','2014-07-10'
put 'my_ns:user','rowkey_22','base_info:sex','1'
put 'my_ns:user','rowkey_22','base_info:address','上海'

put 'my_ns:user','rowkey_24','base_info:username','张三丰'
put 'my_ns:user','rowkey_24','base_info:birthday','2014-07-10'
put 'my_ns:user','rowkey_24','base_info:sex','1'
put 'my_ns:user','rowkey_24','base_info:address','河南'

put 'my_ns:user','rowkey_25','base_info:username','陈大明'
put 'my_ns:user','rowkey_25','base_info:birthday','2014-07-10'
put 'my_ns:user','rowkey_25','base_info:sex','1'
put 'my_ns:user','rowkey_25','base_info:address','西安'

#查询表中的数据
scan 'my_ns:user'

#统计表中行的数量
count  'my_ns:user'

#获取指定行键的数据
get 'my_ns:user','rowkey_10'

#获取指定列簇的数据
get 'my_ns:user','rowkey_10','base_info'

#获取指定列簇中某一列的数据
get 'my_ns:user','rowkey_10','base_info:username'

#获取指定列簇中多列数据
get 'my_ns:user', 'rowkey_10', {COLUMN => ['base_info:username','base_info:sex']}

#删除指定行键,指定列的数据
delete 'my_ns:user','rowkey_10','base_info:username'

#删除指定行键的所有数据
deleteall 'my_ns:user','rowkey_10'

#清空表的数据(慎用)
truncate 'my_ns:user'

#浏览表结构
describe 'my_ns:user'

#操作列簇
alter 'my_ns:user',NAME=>'ext_info'
alter 'my_ns:user','delete'=>'ext_info'

#测试表是否存在
exists 'my_ns:user'

#删除表(首先要禁用表)
disable 'my_ns:user'
drop 'my_ns:user'

5.HBase Java API操作

5.1.搭建环境

5.1.1.创建项目

在这里插入图片描述

5.1.2.导入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.liny</groupId>
    <artifactId>spark-teach-day08-01hbase</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <!--hbase版本-->
        <hbase.version>1.3.1</hbase.version>
    </properties>

    <dependencies>
        <!--hbase依赖-->
        <dependency>
            <groupId>org.apache.hbase</groupId>
            <artifactId>hbase-client</artifactId>
            <version>${hbase.version}</version>
        </dependency>
    </dependencies>

    
</project>
5.1.3.准备hbase-site.xml配置文件

说明:在resources目录下创建hbase-site.xml,方便通过配置文件信息创建HBase连接对象。

<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
    <!--配置zookeeper集群地址,需要在hosts中配置主机域名映射,
        如果没有配置,则可以指定为ip地址方式
    -->
    <property>
        <name>hbase.zookeeper.quorum</name>
        <value>node01,node02,node03</value>
    </property>
</configuration>

5.2封装HBaseUtil工具类

package cn.liny.hbase;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.*;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.filter.Filter;
import org.apache.hadoop.hbase.util.Bytes;

import java.io.IOException;
import java.util.List;

/**
 * HBase工具类
 */
public class HBaseUtil {

    // 集群配置信息
    private static final String ZK_CONNECT_KEY = "hbase.zookeeper.quorum";
    private static final String ZK_CONNECT_VALUE = "node01:2181,node02:2181,node03:2181";

    // 操作集群对象
    private static Connection conn = null;
    private static Admin admin = null;

    //集群环境信息=============================================================================start

    /**
     * 获取连接对象Connection
     */
    public static Connection getConnection(){
        // 1.创建一个可以用来管理hbase配置信息的conf对象
        Configuration conf = new Configuration();
        conf.set(ZK_CONNECT_KEY,ZK_CONNECT_VALUE);

        // 2.创建返回Connection对象
        try {
            if(conn == null){
                conn = ConnectionFactory.createConnection(conf);
            }
        } catch (IOException e) {
            System.out.println("创建Connection连接对象发生异常:"+e.getMessage());
        }

        return conn;
    }

    /**
     * 获取管理员对象
     */
    public static Admin getAdmin(){
        try {
            admin = conn.getAdmin();
        } catch (IOException e) {
            System.out.println("获取管理员Admin对象发生异常:"+e.getMessage());
        }

        return admin;
    }

    /**
     * 释放连接对象
     */
    public static void closeConnection(){
        if(conn !=null){
            try {
                conn.close();
            } catch (IOException e) {
                System.out.println("释放Connection对象发生异常:"+e.getMessage());
            }
        }
    }

    //集群环境信息=============================================================================end

    //操作名称空间=============================================================================start
    /**
     * 创建名称空间:
     *  1.传入目标名称空间的名称
     *  2.判断名称空间是否已经存在
     *  3.若存在,不执行任何操作;若不存在,则创建新的名称空间
     */
    public static void createNameSpace(String ns_name){
        // 标识名称空间是否存在,默认
        boolean isExists = false;

        try {
            // 查询已经存在的名称空间
            NamespaceDescriptor[] list = admin.listNamespaceDescriptors();
            for(NamespaceDescriptor ns:list){
                if(ns.getName().equals(ns_name)){
                    System.out.println("目标名称空间:"+ns_name+"已经存在");
                    isExists = true;
                    break;
                }
            }

            // 名称空间不存在,则创建新的名称空间
            if(!isExists){
                System.out.println("目标名称空间:"+ns_name+"不存在,正在准备创建");
                NamespaceDescriptor newNameSpace = NamespaceDescriptor.create(ns_name).build();
                admin.createNamespace(newNameSpace);
            }

        } catch (IOException e) {
            System.out.println("创建名称空间发生异常:"+e.getMessage());
        }

    }

    /**
     * 删除名称空间
     */
    public static void deleteNameSpace(String ns_name){
        System.out.println("正在准备删除名称空间:"+ns_name);
        try {
            admin.deleteNamespace(ns_name);
        } catch (IOException e) {
            System.out.println("删除名称空间发生异常:"+e.getMessage());
        }
    }

    //操作名称空间=============================================================================end

    //表DDL操作=============================================================================start
    /**
     * 创建表:
     *  1.传递表的名称,和列簇信息
     *  2.判断表是否已经存在,如果已经存在,则不创建
     */
    public static void createTable(String t_name,String ...family){
        // 创建表,必须要指定列簇
        if(family.length<=0){
            System.out.println("创建表,必须要指定列簇");
            return;
        }

        // 定义表名称
        TableName tableName = TableName.valueOf(t_name);
        try {
            // 判断表是否存在
            if(admin.tableExists(tableName)){
                System.out.println("表"+t_name+"已经存在,不需要创建");

                return;
            }

            // 如果表不存在,则执行创建
            System.out.println("表"+t_name+"不存在,正在创建");

            // 定义表的列簇集
            HTableDescriptor htd = new HTableDescriptor(tableName);
            // 添加列簇
            HColumnDescriptor tmpColumn = null;
            for(String hc:family){
                tmpColumn= new HColumnDescriptor(hc);
                htd.addFamily(tmpColumn);
            }

            // 创建表
            admin.createTable(htd);

        } catch (IOException e) {
            System.out.println("创建表发生异常:"+e.getMessage());
        }
    }

    /**
     * 获取表的详情信息
     */
    public static void getTableInfo(String t_name) throws Exception{
        // 定义表名称对象
        TableName tableName = TableName.valueOf(t_name);

        // 判断表是否存在
        if(admin.tableExists(tableName)){
            // 获取表的列簇集
            HTableDescriptor htd = admin.getTableDescriptor(tableName);

            // 获取表的列簇
            HColumnDescriptor[] columnFamilies = htd.getColumnFamilies();

            // 打印表的详情信息
            for(HColumnDescriptor hcd:columnFamilies){
                System.out.println(hcd.getNameAsString());
            }
        }else{
            System.out.println("表"+tableName.getNameAsString()+"不存在。");
        }

    }

    /**
     * 修改表:
     *  1.addOrDelete:标识添加列簇,或者删除例簇
     *  2.true,表示添加;false:表示删除
     */
    public static void updateTable(String t_name,String family,boolean addOrDelete){

        // 定义表名称对象
        TableName tableName = TableName.valueOf(t_name);
        try{
            // 判断表是否存在
            if(admin.tableExists(tableName)){

                // 细节:对表进行修改,需要先禁用表,修改完成后再启用表
                admin.disableTable(tableName);

                // 获取表描述对象
                HTableDescriptor htd = admin.getTableDescriptor(tableName);

                // 判断是添加还是删除
                if(addOrDelete){
                    // 正在添加列簇
                    System.out.println("正在添加列簇:"+family);
                    // 定义列簇对象
                    HColumnDescriptor hcd = new HColumnDescriptor(family);
                    htd.addFamily(hcd);
                }else{
                    // 正在删除列簇
                    System.out.println("正在删除列簇:"+family);

                    htd.removeFamily(Bytes.toBytes(family));
                }

                // 修改表
                admin.modifyTable(tableName,htd);

            }else{
                System.out.println("表【"+tableName.getNameAsString()+"】不存在.");
            }
        }catch(Exception e){
            System.out.println("修改表发生异常:"+e.getMessage());
        }finally {
            // 重新启用表
            try {
                admin.enableTable(tableName);
            } catch (IOException e) {
                System.out.println("重新启用表发生异常:"+e.getMessage());
            }
        }

    }

    /**
     * 删除表
     */
    public static void deleteTable(String t_name){

        // 定义表名称对象
        TableName tableName = TableName.valueOf(t_name);

        try {
            // 判断表是否存在
            if(admin.tableExists(tableName)){

                // 禁用表
                admin.disableTable(tableName);

                // 删除表
                admin.deleteTable(tableName);

            }else{
                System.out.println("表"+tableName.getNameAsString()+"不存在.");
            }
        } catch (IOException e) {
            System.out.println("删除表发生异常:"+e.getMessage());
        }

    }

    //表DDL操作=============================================================================end

    //表DML操作=============================================================================start
    /**
     * 添加(更新)数据:
     *  1.参数说明:
     *      t_name:表名称
     *      rowKey:行键
     *      family:列簇名称
     *      ck:列名称数组
     *      cv:列值数组
     *
     */
    public static void put(String t_name,String rowKey,String family,String[] ck,String[] cv){

        // 定义表的名称对象
        TableName tableName = TableName.valueOf(t_name);


        try {
            // 获取表
            Table table = conn.getTable(tableName);

            // 根据行键创建Put对象
            /**
             * Put对象,是HBase提供用于添加或者更新数据的API
             */
            Put put = new Put(Bytes.toBytes(rowKey));

            // 准备数据
            for(int i=0;i<ck.length;i++){
                put.addColumn(Bytes.toBytes(family),
                        Bytes.toBytes(ck[i]),
                        Bytes.toBytes(cv[i]));
            }

            // 添加(更新)数据
            table.put(put);

            // 释放Table资源
            table.close();

        } catch (IOException e) {
            System.out.println("添加(更新)数据发生异常:"+e.getMessage());
        }

    }

    /**
     * 根据rowKey获取数据
     */
    public static void get(String t_name,String rowKey){
        // 定义表名称
        TableName tableName=TableName.valueOf(t_name);


        try {
            // 获取表
            Table table = conn.getTable(tableName);

            // 创建Get对象
            Get get = new Get(Bytes.toBytes(rowKey));

            // 获取数据
            Result result = table.get(get);

            // 打印数据
            List<Cell> cells = result.listCells();
            for(Cell c:cells){
                //打印rowKey,family,qualifier,value
                System.out.println(Bytes.toString(CellUtil.cloneRow(c))// 行键
                                    + "==> " + Bytes.toString(CellUtil.cloneFamily(c))// 列簇
                                    + "{" + Bytes.toString(CellUtil.cloneQualifier(c))// 列
                                    + ":" + Bytes.toString(CellUtil.cloneValue(c)) + "}");// 值

            }

            // 释放资源
            table.close();

        } catch (IOException e) {
            System.out.println("根据rowKey获取数据发生异常:"+e.getMessage());
        }

    }

    /**
     * 范围扫描数据
     */
    public static void scan(String t_name,String ... start_end_rowKey){
        // 定义表的名称
        TableName tableName = TableName.valueOf(t_name);

        try {
            // 获取表
            Table  table = conn.getTable(tableName);

            // 定义扫描器
            Scan scan = new Scan();

            // 设置扫描范围
            if(start_end_rowKey.length>1){
                scan.setStartRow(Bytes.toBytes(start_end_rowKey[0]));
                scan.setStopRow(Bytes.toBytes(start_end_rowKey[1]));
            }

            // 获取扫描结果集
            ResultScanner scanner = table.getScanner(scan);
            Result result = null;
            while ((result = scanner.next()) != null){
                // 获取单元格
                List<Cell> cells = result.listCells();

                // 打印 rowKey,family,qualifier,value
                for(Cell c:cells){
                    System.out.println(Bytes.toString(CellUtil.cloneRow(c))
                                        + "==> " + Bytes.toString(CellUtil.cloneFamily(c))
                                        + "{" + Bytes.toString(CellUtil.cloneQualifier(c))
                                        + ":" + Bytes.toString(CellUtil.cloneValue(c)) + "}");
                }

            }

            // 释放资源
            table.close();

        } catch (IOException e) {
            System.out.println("范围扫描数据发生异常:"+e.getMessage());
        }

    }

    /**
     * 删除数据
     *  1.参数说明:
     *      t_name:表名称
     *      rowKey:行键
     *      family_qualifier:可变参数,传入列簇名称,和列名称
     *
     */
    public static void del(String t_name,String rowKey,String ... family_qualifier){
        // 定义表名称
        TableName tableName=TableName.valueOf(t_name);

        try {
            // 获取表
            Table  table = conn.getTable(tableName);

            // 创建删除对象
            Delete delete = new Delete(Bytes.toBytes(rowKey));

            // 明确删除某一列
            if(family_qualifier.length>1){
                delete.addColumn(Bytes.toBytes(family_qualifier[0]),Bytes.toBytes(family_qualifier[1]));
            }

            // 执行删除
            table.delete(delete);

            // 释放资源
            table.close();

        } catch (IOException e) {
            System.out.println("删除表数据发生异常:"+e.getMessage());
        }

    }

    //表DML操作=============================================================================end

    //Filter查询===========================================================================start
    /**
     * 过滤器实现高级查询
     */
    public static void scanByFilter(String t_name,Filter filter){
        // 定义表的名称
        TableName tableName = TableName.valueOf(t_name);

        try {
            // 获取表
            Table table = conn.getTable(tableName);

            // 定义扫描器
            Scan scan = new Scan();
            scan.setFilter(filter);

            // 执行扫描
            ResultScanner scanner = table.getScanner(scan);
            Result result = null;
            while ((result = scanner.next()) != null){
                // 获取单元格
                List<Cell> cells = result.listCells();

                // 打印 rowKey,family,qualifier,value
                for(Cell c:cells){
                    System.out.println(Bytes.toString(CellUtil.cloneRow(c))
                            + "==> " + Bytes.toString(CellUtil.cloneFamily(c))
                            + "{" + Bytes.toString(CellUtil.cloneQualifier(c))
                            + ":" + Bytes.toString(CellUtil.cloneValue(c)) + "}");
                }

            }

            // 释放资源
            table.close();

        } catch (IOException e) {
            System.out.println("过滤查询数据发生异常:"+e.getMessage());
        }

    }


    //Filter查询=============================================================================end

}

5.3.操作名称空间

package cn.liny.hbase;

import org.apache.hadoop.hbase.NamespaceDescriptor;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.Connection;

/**
 * 学习HBase操作名称空间
 */
public class HbaseNameSpace {

    public static void main(String[] args) throws Exception{
       // 1.获取连接对象
        Connection conn = HBaseUtil.getConnection();

        // 2.获取管理员对象Admin,执行管理任务操作
        Admin admin = HBaseUtil.getAdmin();

        // 3.操作名称空间namespace
        // 3.1.列出所有的名称空间
        System.out.println("-----------------3.1.列出所有名称空间--------------------");
        NamespaceDescriptor[] list = admin.listNamespaceDescriptors();
        for(NamespaceDescriptor ns:list){
            System.out.println("名称空间:"+ns.getName());
        }

        // 3.2.创建名称空间
        System.out.println("-----------------3.2.创建名称空间-------------------------");
        HBaseUtil.createNameSpace("my_ns1");

        // 3.3.删除名称空间
        System.out.println("-----------------3.3.删除名称空间-------------------------");
        HBaseUtil.deleteNameSpace("my_ns2");
        
        // 2.关闭连接对象
        HBaseUtil.closeConnection();

    }
}

5.4.操作表

在这里插入图片描述

5.4.1.表DDL操作
package cn.liny.hbase;

import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.Connection;

/**
 * 学习HBase操作表:DDL
 */
public class HBaseTableDDL {

    public static void main(String[] args) throws Exception {

        // 1.获取连接对象
        Connection conn = HBaseUtil.getConnection();

        // 2.获取管理员对象Admin,执行管理任务操作
        Admin admin = HBaseUtil.getAdmin();

        // 3.表DDL操作
        // 3.1.查询所有表
        System.out.println("----------------------3.1.查询所有表------------------------");
        TableName[] tableNames = admin.listTableNames();
        for(TableName t:tableNames){
            System.out.println("表名称:"+t.getNameAsString());
        }

        // 3.2.创建表
        System.out.println("----------------------3.2.创建表-----------------------------");
        HBaseUtil.createTable("student","base_info");

        // 3.3.获取表的详情信息
        System.out.println("----------------------3.3.获取表的详情信息---------------------");
        HBaseUtil.getTableInfo("student");

        // 3.4.修改表
        System.out.println("----------------------3.4.修改表-----------------------------");
        HBaseUtil.updateTable("student","ext_info",true);

        // 3.5.删除表
        System.out.println("----------------------3.5.删除表-----------------------------");
        HBaseUtil.deleteTable("student");


        // 关闭连接对象
        HBaseUtil.closeConnection();

    }
}
5.4.2.表DML操作
package cn.liny.hbase;

import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.Connection;

/**
 * 学习HBase操作表:DML
 */
public class HBaseTableDML {

    public static void main(String[] args) throws Exception{

        // 1.获取连接对象
        Connection conn = HBaseUtil.getConnection();

        // 2.获取管理员对象Admin,执行管理任务操作
        Admin admin = HBaseUtil.getAdmin();

        // 3.表DML操作
        // 3.1.创建表
        System.out.println("----------------------3.1.创建表-----------------------------");
        HBaseUtil.createTable("my_ns:user","base_info");

        // 3.2.添加(更新)数据
        System.out.println("----------------------3.2.添加(更新)数据---------------------");
        // 准备数据
        String row_key="rowkey_10";
        String[] ck = {"username","sex","address","birthday"};
        String[] cv = {"张三","1","北京市","2014-07-10"};

        HBaseUtil.put("my_ns:user",row_key,"base_info",ck,cv);

        // 3.3.根据rowKey获取数据
        System.out.println("----------------------3.3.根据rowKey获取数据---------------------");
        HBaseUtil.get("my_ns:user",row_key);

        // 3.4.范围扫描数据
        System.out.println("----------------------3.4.范围扫描数据----------------------------");
        //HBaseUtil.scan("my_ns:user");// 全表扫描
        HBaseUtil.scan("my_ns:user","rowkey_16","rowkey_26");// 指定rowKey范围扫描

        // 3.5.删除数据
        System.out.println("----------------------3.5.删除数据----------------------------");
       // HBaseUtil.del("my_ns:user","rowkey_16");// 删除整行
        HBaseUtil.del("my_ns:user","rowkey_22","base_info","address");// 明确删除列
        
        // 关闭连接对象
        HBaseUtil.closeConnection();
    }
}

5.5.HBase过滤查询

5.5.1.关于过滤查询

基础API中的查询操作,在面对大量数据的时候是非常苍白的,Hbase提供了高级的查询方法:Filter

Filter根据簇、列、版本等更多的条件来对数据进行过滤,基于Hbase提供的三维有序(主键有序列有序版本有序),这些Filter可以高效的完成查询过滤的务。

要完成过滤的操作,至少需要两个参数:

  • 抽象操作符

    LESS :<

    LESS_OR_EQUAL: <=

    EQUAL: =

    NOT_EQUAL: <>

    GREATER :>

    GREATER_OR_EQUAL :>=

    NO_OP :排除所有

  • 比较器

    BinaryComparator :按字节索引顺序比较指定字节数组,采用 Bytes.compareTo(byte[])

    BinaryPrefixComparator: 与BinaryComparator相同,仅比较左端的数据是否相同

    NullComparator :判断给定的是否为空

    BitComparator: 按位比较

    SubstringComparator: 判断提供的子串是否出现在 value 中

5.5.2.过滤查询案例

以下案例演示了常用过滤器的使用:

  • 行键过滤器:RowFilter
  • 列簇过滤器:FamilyFilter
  • 列过滤器:QualifierFilter
  • 值过滤器:ValueFilter
  • 行键前缀过滤器:PreFixFilter
  • 列前缀过滤器:ColumnPrefixFilter
package cn.liny.hbase;

import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.filter.*;

/**
 * 学习HBase高级查询:Filter
 */
public class HBaseFilter {

    public static void main(String[] args) {

        // 1.获取连接对象
        Connection conn = HBaseUtil.getConnection();

        // 2.行键过滤器:RowFilter
        System.out.println("----------------------2.行键过滤器:RowFilter-----------------------------");
        // 定义过滤器
        Filter rowFilter = new RowFilter(CompareFilter.CompareOp.GREATER, new BinaryComparator("rowkey_10".getBytes()));
        HBaseUtil.scanByFilter("my_ns:user",rowFilter);

        //3.列簇过滤器: FamilyFilter
       System.out.println("----------------------3.列簇过滤器: FamilyFilter-----------------------------");
        // 定义过滤器
        Filter familyFilter = new FamilyFilter(CompareFilter.CompareOp.EQUAL, new BinaryComparator("ext_info".getBytes()));
        HBaseUtil.scanByFilter("my_ns:user",familyFilter);

        //4.列过滤器 :QualifierFilter
        System.out.println("----------------------4.列过滤器 :QualifierFilter-----------------------------");
        // 定义过滤器
        Filter qualifierFilter = new QualifierFilter(CompareFilter.CompareOp.EQUAL, new BinaryComparator("username".getBytes()));
        HBaseUtil.scanByFilter("my_ns:user",qualifierFilter);

        //5.值过滤器 :ValueFilter
        System.out.println("----------------------5.值过滤器 :ValueFilter-----------------------------");
        // 定义过滤器
        Filter valueFilter = new ValueFilter(CompareFilter.CompareOp.EQUAL, new SubstringComparator("张三"));
        HBaseUtil.scanByFilter("my_ns:user",valueFilter);

        // 6.前缀过滤器:PrefixFilter----针对行键
        System.out.println("----------------------6.前缀过滤器:PrefixFilter----针对行键-----------------------------");
        // 定义过滤器
        Filter prefixFilter = new PrefixFilter("rowkey_1".getBytes());
        HBaseUtil.scanByFilter("my_ns:user",prefixFilter);

        // 7.列前缀过滤器:ColumnPrefixFilter
        System.out.println("----------------------7.列前缀过滤器:ColumnPrefixFilter-----------------------------");
        // 定义过滤器
        Filter columnPrefixFilter = new ColumnPrefixFilter("a".getBytes());
        HBaseUtil.scanByFilter("my_ns:user",columnPrefixFilter);

        // 关闭连接对象
        HBaseUtil.closeConnection();
    }
}

6.HBase的rowkey设计原则

HBase是三维有序存储的,通过rowkey(行键),column key(column family和qualifier)和TimeStamp(时间戳)这个三个维度可以对HBase中的数据进行快速定位。

HBase中rowkey可以唯一标识一行记录,三种查询方式

  • 通过get方式,指定rowkey获取唯一一条记录
  • 通过scan方式,设置startRow和stopRow参数进行范围匹配
  • 全表扫描,即直接扫描整张表中所有行记录

6.1.Rowkey长度原则

rowkey是一个二进制码流,可以是任意字符串,最大长度64kb,实际应用中一般为10-100bytes,以byte[]形式保存,一般设计成定长。

建议越短越好,不要超过16个字节

案例说明:

  • 数据的持久化文件HFile中是按照Key/Value存储的,如果rowkey过长,比如超过100字节,1000w行数据,光rowkey就要占用100*1000w=10亿个字节,将近1G数据,这样会极大影响HFile的存储效率;
  • MemStore将缓存部分数据到内存,如果rowkey字段过长,内存的有效利用率就会降低,系统不能缓存更多的数据,这样会降低检索效率。

目前操作系统都是64位系统,内存8字节对齐,控制在16个字节,8字节的整数倍利用了操作系统的最佳特性。

6.2.Rowkey 散列原则

如果rowkey按照时间戳的方式递增,不要将时间放在二进制码的前面,建议将rowkey的高位作为散列字段,由程序随机生成,低位放时间字段,这样将提高数据均衡分布在每个RegionServer,以实现负载均衡的几率。

如果没有散列字段,首字段直接是时间信息,所有的数据都会集中在一个RegionServer上,这样在数据检索的时候负载会集中在个别的RegionServer上,造成热点问题,会降低查询效率。

6.3.Rowkey唯一原则

必须在设计上保证其唯一性,rowkey是按照字典顺序排序存储的,因此,设计rowkey的时候,要充分利用这个排序的特点,将经常读取的数据存储到一块,将最近可能会被访问的数据放到一块。但是这里的量不能太大,如果太大需要拆分到多个节点上去 。

6.4.具体设计参考

6.4.1.加盐

加盐指的是在rowkey的前面分配随机数,当给rowkey随机前缀后,它就能分布到不同的region中,这里的前缀应该和你想要数据分散的不同的region的数量有关。

  • 案例需求

以电信公司为例 。当我们去电信公司打印通话记录。对于通话记录来说,每个人每月可能都有很多通话记录,同时电信的用户以亿计。这样的海量信息,可以考虑存入hbase当中 。那么在hbase中如何设计这样一张表呢?

  • 确定需要保存的信息

    对于通话记录 ,有主叫和被叫 信息,有通话时长,有通话时间 ,有主叫的位置信息,和被叫的位置信息 。那么可以确定要保存的信息:主叫、被叫、时长、时间、主叫位置、被叫位置

  • 如何设计hbase表

    hbase表是依靠rowkey来定位的,我们应该尽可能多的将查询的信息编入rowkey当中。设计通话记录的rowkey时,需要将能唯一确定该条记录的数据编入rowkey当中:主叫、被叫、时间

    17766657999 18699886666 201806121502 #主叫,被叫,时间
    
  • 该rowkey合理吗

    该rowkey可能会造成热点问题。例如电话以177开头,hbase集群有100台,那么该数据可能只被存入一台或者两台机器的region中。当需要打印通话记录时,就只有一台机器提供服务。而如果数据均匀分散到100机器中,则是整个集群提供服务。两者之间效率速度差了不止一个数量级 。

    因此我们可以考虑:hbase集群有100台,可以把在0-99之间中的随机数字,添加到rowkey首部 。

    10 17766657999 18699886666 201806121502 #随机数,主叫,被叫,时间
    
6.4.2.预分区

hbase会自动处理region拆分,当region的大小到达一定阈值后,region将被拆分成两个。这个过程当中,会出现两个问题:

  • 第一点还是热点问题,数据会继续往一个region中写,出现写热点问题
  • 第二点是拆分合并风暴,当用户的region大小以恒定的速度增长,region的拆分会在同一时间发生,因为同时需要压缩region中的存储文件,这个过程会重写拆分后的region,这将会引起磁盘I/O上升 。

要解决以上问题,预分区(预先创建hbase表分区 )是一种很好的方案,可以和加盐结合起来使用。使用预分区需要明确rowkey的取值范围和构成逻辑 。

  • 案例需求:

    还是上面电信电话详单表 ,通过加盐我们得到的 rowkey构成是:随机数+主叫+被叫+时间 ,如果当前并没有100台机器,只有10台。但是预计未来将扩展到100台的规模。那么仍然设计0到99的随机数,将以主叫177开头的通话记录分配到十个region当中,将随机数均分成十个区域,范围如下:

    -10,10-20,20-30,30-40,40-50,50-60,60-70,70-80,80-90,90-
    

    将预分区存入数组当中,当插入数据时,先根据插入数据的首部随机数,判断分区位置,再进行插入数据。这样也能使得各台节点负载均衡。

6.4.3.哈希

哈希会使同一行永远用一个前缀加盐。哈希也可以使负载分散到整个集群。同时读是可以预测的。使用确定的哈希可以让客户端重构完整的rowkey,可以使用get操作准确获取某一个行数据

6.4.4.反转

对于手机号码,比较固定开头的rowkey(177,159,188),而后几位都是随机的,没有规律的。可以将手机号反转之后作为rowkey,避免热点问题 。

这就是rowkey设计的另一种方法:反转。通过反转固定长度或者数字格式的rowkey ,把rowkey中经常改变的部分(最没有意义的部分)放在前面 。可以有效的随机rowkey ,不过这样一来就牺牲了rowkey的有序性 。

7.MapReduce操作HBase

7.1.创建项目

在这里插入图片描述

7.2.导入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.liny</groupId>
    <artifactId>spark-teach-day08-02hbase-hdfs</artifactId>
    <version>1.0-SNAPSHOT</version>

    <packaging>jar</packaging>

    <properties>
        <!--hbase版本-->
        <hbase.version>1.3.1</hbase.version>
    </properties>

    <dependencies>
        <!--hbase依赖-->
        <dependency>
            <groupId>org.apache.hbase</groupId>
            <artifactId>hbase-client</artifactId>
            <version>${hbase.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hbase</groupId>
            <artifactId>hbase-server</artifactId>
            <version>${hbase.version}</version>
        </dependency>
    </dependencies>

    
</project>

7.3.MapReduce从hdfs读取数据到HBase

7.3.1.案例代码
package cn.liny.hbase.hdfs;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil;
import org.apache.hadoop.hbase.mapreduce.TableReducer;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
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 java.io.IOException;

/**
 * 学习HBase读取hdfs数据:通过MapReduce实现
 */
public class HBaseFromHDFS {

    public static void main(String[] args) throws Exception{

        // 创建配置对象
        Configuration conf = HBaseConfiguration.create();
        conf.set("hbase.zookeeper.quorum", "node01:2181,node02:2181,node03:2181");

        // 创建Job
        Job job = Job.getInstance(conf,"HBaseFromHDFS");
        job.setJarByClass(HBaseFromHDFS.class);

        // 设置map
        job.setMapperClass(HDFSToHBaseMapper.class);
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(Text.class);

        // 设置输入目录
        FileInputFormat.addInputPath(job, new Path(args[0]));
        TableMapReduceUtil.initTableReducerJob(args[1],HDFSToHBaseReducer.class, job);
        job.waitForCompletion(true);

    }

    // HDFSToHBaseMapper
    public static class HDFSToHBaseMapper extends Mapper<LongWritable, Text, Text, Text> {

        private Text outKey = new Text();
        private Text outValue = new Text();

        // map函数
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            String[] splits = value.toString().split(" ");


            outKey.set(splits[0]);
            outValue.set(splits[1]+" "+splits[2]+" "+splits[3]+" "+splits[4]);

            context.write(outKey, outValue);
        }

    }

    // HDFSToHBaseReducer
    public static class HDFSToHBaseReducer extends TableReducer<Text, Text, NullWritable> {

        // reduce函数
        protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {

            Put put = new Put(key.getBytes());

            String family="base_info";
            for (Text v : values) {
                String[] split = v.toString().split(" ");

                if(split[0]!=null && !"NULL".equals(split[0])){
                    put.addColumn(family.getBytes(),"username".getBytes(),split[0].getBytes());
                }

                if(split[1]!=null && !"NULL".equals(split[1])){
                    put.addColumn(family.getBytes(),"sex".getBytes(),split[1].getBytes());

                }

                if(split[2]!=null && !"NULL".equals(split[2])){
                    put.addColumn(family.getBytes(),"birthday".getBytes(),split[2].getBytes());

                }

                if(split[3]!=null && !"NULL".equals(split[3])){
                    put.addColumn(family.getBytes(),"address".getBytes(),split[3].getBytes());

                }

            }

            // 输出
           context.write(NullWritable.get(),put);
        }
    }
}
7.3.2.配置执行环境
  • 准备数据文件

    user.txt:

    rowkey_10 张三 1 2014-07-10 北京市
    rowkey_16 张小明 1 2014-07-10 北京
    rowkey_22 陈小明 1 2014-07-10 上海
    rowkey_24 张三丰 1 2014-07-10 河南
    rowkey_25 陈大明 1 2014-07-10 西安
    

    上传到hdfs:

    #创建hdfs目录
    hdfs dfs -mkdir -p /anan/hbase/input
    
    #上传文件
    hdfs dfs -put /usr/local/develop/testdata/user.txt /anan/hbase/input
    

    在这里插入图片描述

  • 创建hbase表

    create 'my_ns:huser','base_info'
    
  • 配置hadoop依赖hbase相关包

    vi /usr/local/develop/hadoop274/etc/hadoop/hadoop-env.sh
    
    #配置HADOOP_CLASSPATH环境变量,指定hbase依赖包目录
    export HADOOP_CLASSPATH=$HADOOP_CLASSPATH:/usr/local/develop/hbase131/lib/*
    
  • 项目打包,上传到linux服务器

    在这里插入图片描述

7.3.3.提交任务执行

hadoop提交任务:

hadoop jar /usr/local/develop/testjar/spark-teach-day08-02hbase-hdfs-1.0-SNAPSHOT.jar \
cn.itheima.hbase.hdfs.HBaseFromHDFS \
/anan/hbase/input/user.txt my_ns:huser

执行结果:

在这里插入图片描述

7.4.MapReduce从HBase读取数据到hdfs

7.4.1.案例代码
package cn.liny.hbase.hdfs;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil;
import org.apache.hadoop.hbase.mapreduce.TableMapper;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.IOException;
import java.util.List;

/**
 * 学习读取HBase保存到hdfs数据:通过MapReduce实现
 */
public class HBaseToHDFS {

    public static void main(String[] args) throws Exception{
        // 创建配置对象
        Configuration conf = HBaseConfiguration.create();
        conf.set("hbase.zookeeper.quorum", "node01:2181,node02:2181,node03:2181");

        // 创建Job
        Job job = Job.getInstance(conf,"HBaseToHDFS");
        job.setJarByClass(HBaseToHDFS.class);

        Scan scan = new Scan();
        TableMapReduceUtil.initTableMapperJob(args[0], scan, HBaseToHDFSMapper.class, Text.class, Text.class, job);

        // 设置输入目录
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(Text.class);
        FileOutputFormat.setOutputPath(job, new Path(args[1]));
        job.waitForCompletion(true);

    }

    // mapper
    public static class HBaseToHDFSMapper extends TableMapper<Text, Text> {

        Text outKey = new Text();
        Text outValue = new Text();

        /**
         *map方法:
         *     参数说明:
         *      key:hbase中的行键
         *      value:行键对应的所有数据
         */
        protected void map(ImmutableBytesWritable key, Result value, Context context) throws IOException, InterruptedException {

            StringBuilder builder =new  StringBuilder();

            boolean isContainsColumn = value.containsColumn("base_info".getBytes(),"username".getBytes());
            if(isContainsColumn) {
                List<Cell> listCells = value.getColumnCells("base_info".getBytes(), "username".getBytes());
                Cell cell = listCells.get(0);

                byte[] cloneValue = CellUtil.cloneValue(cell);
                builder.append(Bytes.toString(cloneValue)).append(" ");

            }

           isContainsColumn = value.containsColumn("base_info".getBytes(),"sex".getBytes());
            if(isContainsColumn) {
                List<Cell> listCells = value.getColumnCells("base_info".getBytes(), "sex".getBytes());
                Cell cell = listCells.get(0);

                byte[] cloneValue = CellUtil.cloneValue(cell);
                builder.append(Bytes.toString(cloneValue)).append(" ");

            }

            isContainsColumn = value.containsColumn("base_info".getBytes(),"birthday".getBytes());
            if(isContainsColumn) {
                List<Cell> listCells = value.getColumnCells("base_info".getBytes(), "birthday".getBytes());
                Cell cell = listCells.get(0);

                byte[] cloneValue = CellUtil.cloneValue(cell);
                builder.append(Bytes.toString(cloneValue)).append(" ");

            }

            isContainsColumn = value.containsColumn("base_info".getBytes(),"address".getBytes());
            if(isContainsColumn) {
                List<Cell> listCells = value.getColumnCells("base_info".getBytes(), "address".getBytes());
                Cell cell = listCells.get(0);

                byte[] cloneValue = CellUtil.cloneValue(cell);
                builder.append(Bytes.toString(cloneValue)).append(" ");

            }

            // 输出
            outKey.set(Bytes.toString(key.get()));
            outValue.set(builder.toString());
            context.write(outKey,outValue);

        }

    }

    //reducer
    public static class HBaseToHDFSReducer extends Reducer<Text, Text, Text, Text> {

        Text outValue = new Text();

        protected void reduce(Text key, Iterable<Text> values,Context context)
                throws IOException, InterruptedException {

            outValue.set(values.toString());
            context.write(key, outValue);
        }

    }
}
7.4.2.打包,提交任务执行

hadoop提交任务:

hadoop jar /usr/local/develop/testjar/spark-teach-day08-02hbase-hdfs-1.0-SNAPSHOT.jar \
cn.itheima.hbase.hdfs.HBaseToHDFS \
my_ns:huser /anan/hbase/output

执行结果:

hdfs dfs -cat /anan/hbase/output/part-r-00000

在这里插入图片描述

8.spark操作HBase

8.1.创建项目

在这里插入图片描述

8.2.导入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.liny</groupId>
    <artifactId>spark-teach-day08-03hbase-spark</artifactId>
    <version>1.0-SNAPSHOT</version>

    <packaging>jar</packaging>

    <properties>
        <scala.version>2.11.8</scala.version>
        <spark.version>2.0.2</spark.version>
        <!--hbase版本-->
        <hbase.version>1.3.1</hbase.version>
    </properties>

    <dependencies>
        <!--scala依赖-->
        <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-library</artifactId>
            <version>${scala.version}</version>
        </dependency>
        <!--spark依赖-->
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-streaming_2.11</artifactId>
            <version>${spark.version}</version>
        </dependency>
        <!--hbase依赖-->
        <dependency>
            <groupId>org.apache.hbase</groupId>
            <artifactId>hbase-client</artifactId>
            <version>${hbase.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hbase</groupId>
            <artifactId>hbase-server</artifactId>
            <version>${hbase.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!--scala编译插件-->
            <plugin>
                <groupId>org.scala-tools</groupId>
                <artifactId>maven-scala-plugin</artifactId>
                <version>2.15.2</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <!--项目打包插件-->
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>cn.itheima.hbase.spark.SparkToHBase</mainClass>
                        </manifest>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>
        </plugins>
    </build>

    
</project>

8.3.spark写入数据到HBase

8.3.1.编写案例代码
package cn.liny.hbase.spark

import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.hbase.HBaseConfiguration
import org.apache.hadoop.hbase.client.Put
import org.apache.hadoop.hbase.io.ImmutableBytesWritable
import org.apache.hadoop.hbase.mapred.TableOutputFormat
import org.apache.hadoop.hbase.util.Bytes
import org.apache.hadoop.mapred.JobConf
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

/**
  * spark将RDD数据写入HBase
  */
object SparkToHBase {

  // 执行入口
  def main(args: Array[String]): Unit = {

    // 1.创建HBase配置
    val conf: Configuration = HBaseConfiguration.create()
    conf.set("hbase.zookeeper.quorum", "node01:2181,node02:2181,node03:2181")

    // 2.指定输出格式和输出表名
    val jobConf: JobConf = new JobConf(conf,this.getClass)
    jobConf.setOutputFormat(classOf[TableOutputFormat])
    jobConf.set(TableOutputFormat.OUTPUT_TABLE,"my_ns:s_user")

    // 3.创建SparkContext对象
    // 本地测试使用
    val sparkConf: SparkConf = new SparkConf().setAppName("SparkToHBase").setMaster("local[2]")
    //打包到集群环境使用
    //val sparkConf: SparkConf = new SparkConf().setAppName("SparkToHBase")
    val sc: SparkContext = new SparkContext(sparkConf)
    sc.setLogLevel("WARN")

    // 构建RDD数据
    val dataRDD = List(("rowkey_1","lilei","16"), ("rowkey_2","hanmeimei","17"), ("rowkey_3","lucy","18"))
    val resultRDD: RDD[(ImmutableBytesWritable, Put)] = sc.parallelize(dataRDD).map(convert)

    // 写入HBase表
    resultRDD.saveAsHadoopDataset(jobConf)

  }

  // 定义元组转换函数
  // (1,"lilei",18)
  def convert(tuple: (String, String, String)) = {
    // 定义put
    val put = new Put(Bytes.toBytes(tuple._1))

    // 添加列数据
    put.addColumn(Bytes.toBytes("base_info"),Bytes.toBytes("name"),Bytes.toBytes(tuple._2))
    put.addColumn(Bytes.toBytes("base_info"),Bytes.toBytes("age"),Bytes.toBytes(tuple._3))

    // 返回
    (new ImmutableBytesWritable, put)
  }

}
8.3.2.配置执行环境
  • 创建hbase表

    create 'my_ns:s_user','base_info'
    
    #查询数据
    scan 'my_ns:s_user'
    
  • 如果要在集群环境使用,则需要启动spark集群,提交命令如下:

    spark-submit --class cn.itheima.hbase.spark.SparkToHBase \
    --master spark://hadoop01:7077 \
    --executor-memory 1g --total-executor-cores 1 \
    /usr/local/develop/testjar/spark-teach-day08-03hbase-spark-1.0-SNAPSHOT-jar-with-dependencies.jar
    
8.3.3.执行结果

在这里插入图片描述

8.4.spark从HBase读取数据

8.4.1.编写案例代码
package cn.liny.hbase.spark

import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.hbase.HBaseConfiguration
import org.apache.hadoop.hbase.client.Result
import org.apache.hadoop.hbase.io.ImmutableBytesWritable
import org.apache.hadoop.hbase.mapreduce.TableInputFormat
import org.apache.hadoop.hbase.util.Bytes
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

/**
  * spark从HBase读取数据,转换成RDD
  */
object SparkFromHBase {

  // 执行入口
  def main(args: Array[String]): Unit = {

    // 1.创建HBase配置
    val conf: Configuration = HBaseConfiguration.create()
    conf.set("hbase.zookeeper.quorum", "hadoop01:2181,hadoop02:2181,hadoop03:2181")

    // 2.设置查询的表名
    conf.set(TableInputFormat.INPUT_TABLE, "my_ns:s_user")

    // 3.创建SparkContext对象
    val sparkConf: SparkConf = new SparkConf().setAppName("SparkFromHBase").setMaster("local[2]")
    // 设置序列化
    sparkConf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
    sparkConf.registerKryoClasses(Array(classOf[org.apache.hadoop.hbase.io.ImmutableBytesWritable]))

    val sc: SparkContext = new SparkContext(sparkConf)
    sc.setLogLevel("WARN")

    // 4.通过SparkContext 提供的newAPIHadoopRDD获取内容
    val dataRDD: RDD[(ImmutableBytesWritable, Result)] = sc.newAPIHadoopRDD(conf, classOf[TableInputFormat],
      classOf[ImmutableBytesWritable],
      classOf[Result])

    //5.遍历输出
    dataRDD.foreach{ case (_,result) =>
      val key = Bytes.toString(result.getRow)
      val name = Bytes.toString(result.getValue("base_info".getBytes,"name".getBytes))
      val age = Bytes.toString(result.getValue("base_info".getBytes,"age".getBytes))

      println("Row key:"+key+",name="+name+",age="+age)
    }

  }

}
8.4.2.执行结果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值