HBase 入门学习

        HBase 是一种分布式、可扩展、支持海量数据存储的NoSQL数据库。

一、HBase 逻辑结构

        逻辑上,HBase 的数据模型同关系型数据库很类似,数据存储在一张表中,有行有列。但从 HBase 的底层物理存储结构(K-V)来看,HBase更像是一个 multi-dimensional map。

        将多列分为列簇(不同的列簇存放在不同的文件夹中),列簇中可以只包含一个列,列簇中列的数量可以动态增加。

        Row key(行键,其值唯一),按字典序排列(按位比较),必须存在。

        Region是大表横向的一个切片,上图中划分了三个region,按数据量切分(根据Row key进行分类),不同的region存放在不同的文件夹。

        store,是存储的数据,上图中划分了六个store,最终存放在HDFS中。

        同一个Region下,不同store一定对应不同的列簇。

        同一列簇下不同store,一定属于不同Region。

二、HBase 物理存储结构

        查询数据时,返回时间戳(TimeStamp)最大的版本。

三、HBase 数据模型

 (1)Name Space

        命名空间,类似于关系型数据库的 database概念,每个命名空间下有多个表。Hbase 有两个自带的命名空间,分别是 hbase和 default;hbase 中存放的是HBase 内置的表,default表是用户默认使用的命名空间。

(2)Region

        类似于关系型数据库的表概念。不同的是,HBase定义表时只需要声明列族即可,不需要声明具体的列。这意味着,往 HBase写入数据时,字段可以动态、按需指定。因此,和关系型数据库相比,HBase能够轻松应对字段变更的场景。

(3)Row

        HBase 表中的每行数据都由一个RowKey和多个 Column(列)组成,数据是按照 RowKey
字典顺序存储的,并且查询数据时只能根据 RowKey 进行检索,所以 RowKey 的设计十分重要。

(4)Column

        HBase 中的每个列都由 Column Family(列族)和Column Qualifier(列限定符)进行限定,例如 info:name,info:age。建表时,只需指明列族,而列限定符无需预先定义。

(5)Time Stamp

        用于标识数据的不同版本(version),每条数据写入时,如果不指定时间戳,系统会自动为其加上该字段,其值为写入 HBase 的时间。

(6)Cell

        由 {rowkey, column Family:column Qualifier,time Stamp} 唯一确定的单元。cell 中的数据是没有类型的,全部是字节码形式存储。

四、HBase 基本知识

4.1 HBase 安装

        (1)安装部署zookeeper

        (2)安装部署Hadoop

        (3)获取并解压HBase安装包

        (4)分别修改配置文件hbase-env.sh、hbase-site.xml、regionservers

                1)在regionservers文件中写入集群地址

                2)在hbase-env.sh文件中配置 JAVA_HOME地址,并注释掉以下内容

                将以下变量设置为false,表示不使用HBase自身带有的zookeeper

                3)修改hbase-site.xml文件内容

                4)软连接hadoop 配置文件到HBase

ln -s /opt/module/hadoop-2.7.2/etc/hadoop/core-site.xml /opt/module/hbase/conf/core-site.xml

ln -s /opt/module/hadoop-2.7.2/etc/hadoop/hdfs-site.xml /opt/module/hbase/conf/hdfs-site.xml

                5)远程发送HBase文件到集群其他节点

集群的启动及关闭

        单节点启动

        bin/hbase-daemon.sh start master

        bin/hbase-daemon.sh start regionserver

        集群启动/关闭

        bin/start-hbase.sh

        bin/stop-hbase.sh

4.2 HBase 基本架构

        Master 用于管理表结构。可以启用多个Master 保证集群的高可用。

        RregionServer 用于管理数据的增删改查,一个RregionServer管理多个Rregion。

        Rregion管理多个列簇(其物理结构为Store,不同列簇存储是分离的)

        数据刚开始存储在Mem Store(内存)中,之后会刷写在磁盘文件中。

        zookeeper帮助Master分担了客户端请求操作数据的工作。

        HLog,预写入日志,记录操作,防止内存数据在落盘前丢失。

4.3 HBase 写数据流程

        HBase的读比写要慢。

        HBase中有两个系统表,meta和namespace,meta表存储了用户表的信息,namespace表存储用户定义的命名空间名称。

        zookeeper中存储了HBase的meta所在的节点位置。HBase老版本0.9x中,zookeeper存储的是-root-表所在节点位置,-root-表内存储了meta表位置信息,这是为了防止meta表进行分割,产生多个meta表。

        源码中第7步写入操作日志是异步执行,第7步提交任务后,接着进行第8步写入内存操作,第8步执行完会判断第7步是否执行成功,如果失败则进行回滚(将内存中的数据进行删除)。

        第9步之后,hbase等到MemStore的刷写时机后,将数据刷写到HDFS上。

        Flush流程

        刷写时机(以内存大小为依据):

        1、regionserver.global.memstore.size(默认值为regionserver堆内存的0.4)

        当 region server中memstore 的总大小超过该值时,进行flush操作,此时会阻塞客户端的读写操作。

        2、hbase.regionserver.global.memstore.size.lower.limit(默认值上述值的0.95)

即计算:java.heapsize * regionserver.global.memstore.size * hbase.regionserver.global.memstore.size.lower.limit

        当 region server中memstore 的总大小超过该值时,会按照其所有 memstore 的大小顺序(由大到小)依次进行刷写,且不会阻塞客户端的读写操作。如果客户端写入操作速度大于刷写速度,使memstore的总大小超过第一个计算值时,会阻塞客户端的读写操作。

        3、hbase.hregion.memstore.flush.size(默认值为128M)当单个region的memstore大小超过该值时进行刷写。

        4、以WAL日志文件大小为依据(新版本已废除)

        当单个region的数据大小超过该值进行刷写操作,此时只刷写该region内的数据。

          刷写时机(以在内存中停留的时间为依据):

        1、hbase.regionserver.optionalcacheflushinterval(默认值为1个小时)

        当内存中文件最后一次编辑的时间超过该值时,进行刷写操作(即最后一条数据存在超过一小时)。

        flush刷写数据时,对内存中未落盘的重复数据会进行删除,如果刷写时有delete标记的数据,同内存的该数据会被删除,但该数据的delete标记数据会保存。

4.4 HBase 读数据流程

        第七步读取数据时,分别在BlockCache(读缓存),MemStore和StoreFile(HFile)中查询目标数据,并将查到的所有数据进行合并。此处所有数据是指同一条数据的不同版本(timestamp)或者不同的类型(PutDelete)。

        然后,将从文件中查询到的数据块(Block,HFile数据存储单元,默认大小为64KB)缓存到Block Cache。
        最后,将合并后的最终结果返回给客户端。

4.5 Compact 流程

        由于 memstore每次刷写都会生成一个新的HFile,且同一个字段的不同版本(timestamp)和不同类型(Put/Delete)有可能会分布在不同的 HFile 中,因此査询时需要遍历所有的 HFile。为了减少 HFile 的个数,以及清理掉过期和删除的数据,会进行 StoreFile Compaction。

        Compaction 分为两种,分别是 Minor Compaction 和 Major Compaction。Minor Compaction会将临近的若干个较小的 HFile 合并成一个较大的 HFile,但不会清理过期和删除的数据。Major Compaction 会将一个 Store 下的所有的 HFile 合并成一个大 HFile,并且会清理掉过期和删除数据。

        当HFile文件数超过三个时,触发Minor Compaction后,也会执行Major Compaction。

        hbase.hregion.majorcompaction设置Major Compaction执行周期,默认为7天,Major Compaction非常耗资源,建议生产关闭(设置为0),在应用空闲时间手动触发。

        hbase.hstore.compactionThreshold参数默认为3,当HFile文件数超过该值时触发Minor Compaction。

4.6 Split 流程

        默认情况下,每个 Table 起初只有一个 Region,随着数据的不断写入,Region 会自动进行拆分。刚拆分时,两个子 Region 都位于当前的 Region Server,但处于负载均衡的考虑HMaster 有可能会将某个 Region 转移给其他的 Region Server。

        Region Split 时机:

        1、当1个region中的某个Store 下所有 StoreFile的总大小超过hbase.hregion.max.filesize
该 Region 就会进行拆分(0.94 版本之前)。

        2、当1 个 region 中的某个 Store 下所有 StoreFile 的总大小超过 Min(R^2 * hbase.hregion.memstore.flush.size, hbase.hregion.max.filesize),该Region 就会进行拆分,其中R为当前 Region Server 中属于该 Table 的个数(0.94 版本之后)。

        根据第二条规则切分,如果row_key一直追加,则文件数据分布不均匀,前期切分的文件较小且不再增加,后期切分的数据文件较大,即数据倾斜问题。可以通过预分区来解决

        官方建议最好使用一个列簇,为什么?

        因为如果列簇设计不合理,会导致列簇数据分布倾斜,一些列簇数据较多,而一些列簇数据较少。在进行Region切分时,本来较少数据的列簇,数据会变更少,这时如果触发全局flush,这些少数据的列簇就会产生很多小文件。

五、HBase 命令行操作

5.1 基本操作   

        进入 HBase 客户端命令行         bin/hbase shell

        查看帮助命令        help

        查看当前数据库中有哪些表        list(只能查用户表,看不到系统表)

5.2 表的操作

        查看命名空间        list_namespace

        创建命名空间        create_namespace <命名空间名称>

        删除命名空间(删除时,命名空间内不能有表存在)       drop_namespace <命名空间名称>

        创建表 (至少要有一个列簇)       

        create <表名>, <列簇名>,...,<列簇名>(create 'stu', 'info1', 'info2')

        查看表结构        describe <表名>

        变更表信息(必须对某一列簇进行修改),将info1 列簇中的数据存放三个版本:

        alter <表名>, {NAME=>'info1', VERSIONS=>3}

        删除表(删除表之前,必须先将表下线)

        1)下线表        disable <表名>

        2)删除表        drop <表名>

5.3 数据的操作

        向表中添加数据/修改数据

        put <命名空间名:表名>, <Row_key>, <列簇名:列名>, <值>

        put 'stu', '1001', 'info1:name', 'zhangsan'

        修改的数据没有真正的删除,只显示时间戳最大的数据

        查看数据

        scan <命名空间名:表名>, <过滤条件>

        get <命名空间名:表名>, <Row_key>, <过滤条件>

        查看表中旧数据 scan <命名空间名:表名>, {RAW => true, VERSIONS => 10}

        删除数据

        delete <命名空间名:表名>, <Row_key>, <列簇名:列名>, <时间戳>(至少三个参数)

        删除整行数据(按照列簇依次删除)        deleteall <命名空间名:表名>, <Row_key>

        删除整张表的数据        truncate <命名空间名:表名>

六、HBase API操作

        引入hbase-client的maven依赖

创建Admin对象来对表进行管理

// 1.获取配置文件信息
// 旧版本
// HBaseConfiguration configuration =new HBaseConfiguration();
  
Configuration configuration = HBaseConfiguration.create();
configuration.set("hbase.zookeeper.quorum","hadoop102, hadoop103, hadoop104");

// 2.获取管理员对象
// 旧版本
// HBaseAdmin admin =new HBaseAdmin(configuration);
 
Connection connection = ConnectionFactory.createConnection(configuration);
Admin admin = connection.getAdmin();

// 关闭资源
admin.close();
connection.close();

判断表是否存在

boolean exists = admin.tableExists(TableName.value0f(tableName));

创建表

public static void createTable(String tableName, String... cfs)throws I0Exception{

    // 1.判断是否存在列族信息
    if(cfs.length <= 0){
        System. out.println("请设置列族信息!");
        return;
    }

    // 2.判断表是否存在
    if(isTableExist(tableName)){
        System.out.println(tableName +"表已存在!");
        return;
    }
    
    // 3.创建表描述器
    HTableDescriptor hTableDescriptor = new HTableDescriptor(TableName.value0f(tableName));

    // 4.循环添加列簇信息
    for(String cf : cfs){

        // 5.创建列簇描述器
        HColumnDescriptor hColumnDescriptor = new HColumnDescriptor(cf);
        // 可以设置最大版本数
        // hColumnDescriptor.setMaxVersions( num ); 
        
        //添加具体的列簇信息
        hTableDescriptor.addFamily(hColumnDescriptor);

    }

    // 7.创建表
    admin.createTable(hTableDescriptor);
}

删除表

public static void dropTable(String tableName) throws IOException{

    // 1.判断表是否存在
    if(!isTableExist(tableName)){
        System.out.println(tableName + "表不存在!!!");
        return;
    }

    // 2.使表下线
    admin.disableTable(TableName.valueOf(tableName));

    // 3.删除表
    admin.deleteTable(TableName.value0f(tableName));

}

创建命名空间

public static void createNameSpace(String ns){

    // 1.创建命名空间描述器
    NamespaceDescriptor namespaceDescriptor = NamespaceDescriptor.create(ns).build();

    // 2.创建命令空间
    try{
        admin.createNamespace(namespaceDegcriptor);
    }catch(NamespaceExistException e){
        System.out.println(ns +"命名空间已存在!")
    }
    catch(IException e){
        e.printStackTrace();
    }

}

向表中插入数据

public static void putData(String tableName, String rowKey, String cf, String cn, String value) throws IOException{

    // 1.获取表对象
    Table table = connection.getTable(TableName.valueof(tableName));

    // 2.创建Put对象
    Put put = new Put(Bytes.toBytes(rowKey));

    // 3.给Put对象赋值
    put.addColumn(Bytes.toBytes(cf), Bytes.toBytes(cn), Bytes.toBytes(value));

    // 4.插入数据
    table.put (put);

    // 5.关闭表连接
    table.close();

}

获取单行数据

public static void getData(String tableName, String rowkey, String cf, String cn) throws IOException{

    // 1. 获取表对象
    Table table = connection.getTable(TableName.valueof(tableName));

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

    // 2.1 通过 get.setMaxVersions( num ) 方法,可以返回指定的版本数(不超过表元数据版本设置的值)
    // 2.2 通过 get.addFamily(Bytes.toBytes(cf))方法,可以获取指定的列簇的所有值
    // 2.3 通过 get.addColumn(Bytes.toBytes(cf), Bytes.toBytes(cn))方法,可以获取指定的列簇中列的值
    
    // 3.获取数据
    Result result = table.get(get);

    //4.解析result,获取一行中多列数据
    for(Cell cell : result.rawCells()){
        // 5. 打印数据
        System.out.println("CF:"+ Bytes.toString(CellUtil.cloneFamily(cell)) +
                          ",CN:"+ Bytes.toString(CellUtil.cloneQualifier(cell)) +
                          ",Value:"+ Bytes.toString(CellUtil.cloneValue(cell)));

        }
    
    }

    // 6.关闭表连接
    table.close(); 

} 

获取多行数据(Scan)

public static void scanTable(String tableName)throws IOException{

    // 1.获取表对象
    Table table = connection.getTable(TableName.valueof(tableName));

    // 2.构建Scan对象(空参对象表示扫描全表)
    Scan scan = new Scan();
    
    // 2.1设置扫描的起始和结束的RowKey,[startRowKey, endRowKey),左闭右开
    // Scan scan = new Scan(Bytes.toBytes("1001"), Bytes.toBytes("1003"))

    // 2.2 构建过滤器,常用的过滤器有CompareFilter(ValueFilter、QualifierFilter、FamilyFilter、RowFilter)、SingleColumnValueFilter
    // RowFilter rowFilter = new RowFilter(编写具体的过滤规则);
    // scan.setFilter(rowFilter);

    // 3.扫描表
    ResultScanner resultScanner = table.getScanner(scan);

    // 4.解析resultScanner(其本质是result集合的封装,因为数据量可能太多所以进行封装)
    for(Result result : resultScanner){
    
        // 5. 解析result,并打印数据
        for(Result cell : result){
    
            System.out.println("RK:" + Bytes.toString(CellUtil.cloneRow(cell)) +
                          ",CF:"+ Bytes.toString(CellUtil.cloneFamily(cell)) +
                          ",CN:"+ Bytes.toString(CellUtil.cloneQualifier(cell)) +
                          ",Value:"+ Bytes.toString(CellUtil.cloneValue(cell)));
        }
    }

    // 6.关闭表连接
    table.close(); 
    
}

删除数据

        以RowKey为依据删除,每个列簇标记为DeleteFamily,每个版本都删除。

        以RowKey + 列簇为依据删除,命令行不可以执行,使用API可以执行,删除的列簇标记为DeleteFamily,默认删除所有版本,如果传入时间戳参数,则删除小于等于该时间戳的版本。

        以RowKey + 列簇 + 列名,delete.addColumns() 删除指定列的所有版本,如果传入时间戳参数,则会删除该列小于等于该时间戳的所有版本,删除的列标记为DeleteColumn。delete.addColumn() 删除指定列的最新版本,如果传入时间戳参数,则会删除该列的具体版本。删除的该列的版本标记为Delete。

        本质来说delete操作就是put操作,对该数据新增一个版本,其值为上述描述的各类删除标记。        

public static void deleteData(String tableName, String rowKey, String cf, String cn) throws IOException {
    
    // 1.获取表对象
    Table table = connection.getTable(TableName.valueof(tableName));
    
    // 2.构建删除对象(只传rowKey,相当于命令行的deleteAll,删除整行数据)
    Delete delete = new Delete(Bytes.toBytes(rowKey));

    // 2.1 delete.addColumn() 删除指定列的最新版本,如果传入时间戳参数,则会删除该列的具体版本
    // delete.addColumn() 如果在数据刷写前删除最新版本,查询时可以查出次新版本;如果数据刷写后删除最新版本,则该列数据为空,是真正删除,因此该方法慎用。

    // 2.2 delete.addColumns() 删除指定列的所有版本,如果传入时间戳参数,则会删除该列小于等于该时间戳的所有版本

    // 2.3删除指定的列簇
    //delete.addFamily()
    
    // 3.执行删除操作
    table.delete(delete);

    // 4.关闭连接
    table.close();


}

七、与MR交互

        通过 HBase 的相关 JavaAPI,可以实现伴随 HBase 操作的 MapReduce 过程,比如使用MapReduce 将数据从本地文件系统导入到 HBase 的表中,比如从 HBase 中读取一些原始数据后使用 MapReduce 做数据分析。

        MapReduce的切片数是HBase表的Region数。

环境配置及测试

        查看 Hbase 的 MapReduce 任务的执行        bin/hbase mapreduce

        环境变量的导入

        (1)执行环境变量的导入(临时生效,在命令行执行下述操作)

        export HBASE_HOME = /opt/module/hbase-1.3.1

        export HADOOP_HOME = /opt/module/hadoop-2.7.2

        export HADOOP CLASSPATH = '${SHBASE HOME}/bin/hbase mapreduce'

        (2)永久生效:在/etc/profile 配置

        export HBASE_HOME = /opt/module/hbase-1.3.1

        export HADOOP_HOME = /opt/module/hadoop-2.7.2

        并在 hadoop-env.sh 中配置:

        export HADOOP_CLASSPATH = $HADOOP_CLASSPATH:/opt/module/hbase/lib/*

        运行官方的MapReduce任务:

        案例一:统计 student 表中有多少行数据(在Hbase目录下运行以下命令)
        /opt/module/hadoop-2.7.2/bin/yarn jar lib/hbase-server-1.3.1.jar rowcounter student

        案例二:使用 MapReduce 将本地数据导入到 HBase

        1)在本地创建一个 tsv 格式的文件:fruit.tsv,并上传到HDFS

        2)创建 HBase 表        create 'fruit' 'info'

        3)执行MapReduce到HBase 的fuit 表中

/opt/module/hadoop-2.7.2/bin/yarn jar lib/hbase-server-1.3.1.jar importtsv -Dimporttsv.columns = HBASE_ROW_ KEY, info:name, info:color fruit hdfs://hadoop102:9000/fruit.tsv


# importtsv 是jar包(lib/hbase-server-1.3.1.jar)的主类名
# Dimporttsv.columns = HBASE_ROW_ KEY, info:name, info:color 设置文件列与表列的对应关系
# fruit 是导入的表名
# hdfs://hadoop102:9000/fruit.tsv 是输入数据路径

学习案例一:MR读取文件,将数据写入HBase表

        将 fruit.tsv文件,通过MR迁入到 fruit1 表中

        1)编写 Map类代码(主要做读取数据的工作)

public class FruitMapper extends Mapper<LongWritable, Text, LongWritable, Text>{

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException,InterruptedException{

        context.write(key, value);

    }
}

        2)编写 Reducer 代码(继承TableReducer类,VALUEOU的泛型不用自己定义)

public class FruitReducer extends TableReducer <LongWritable, Text, NullWritable>{

    @Override
    protected void reduce(LongWritable key, Iterable<Text> values, Context context)throws IOException, InterruptedException{
        
        //1.遍历values (1001    Apple    Red)
        for(Text value :values){
            
            // 2.获取每一行数据
            String[] fields = value.toString().split("\t");

            // 3.构建Put对象
            Put put = new Put(Bytes.toBytes(fields[0]));

            // 4.给Put对象赋值(如果列簇和列名想要动态获取有两种方式,1.读取配置文件 2.通过context可以获取全局的配置信息,从而读取Main方法设置的值)
            put.addColumn(Bytes.toBytes("info"), Bytes.toBytes("name"), Bytes.toBytes(fields[1]));
            put.addColumn(Bytes.toBytes("info"), Bytes.toBytes("color"), Bytes.toBytes(fields[2]));
            
            // 5.写出数据
            context.write(NullWritable.get(), put);
          
        }
    }
}

        3)编写 Driver 代码

public class FruitDriver implements Tool{

    // 定义一个Configuration
    privete Configuration configuration = null;
    
    @Override
    public int run(String[] args)throws Exception{

        // 1.获取Job对象
        Job job = Job.getInstance(configuration);
        
        // 2.设置驱动类路径
        job.setJarByClass(FruitDriver.class);

        // 3.设置Mapper&Mapper输出的KV类型
        job.setMapperClass(FruitMapper.class);
        job.setMapOutputKeyClass(LongWritable.class);
        job.setMapOutputValueClass(Text.class);

        // 4.设置Reducer类
        TableMapReduceUtil.initTableReducerJob(args[1], FruitReducer.class, job);

        // 5.设置输入参数
        FileInputFormat.setInputPaths(job, new Path(args[0]));

        // 6.提交任务
        boolean result = job.waitForCompletion(true);
        
        return result ? 0 : 1;
    } 
    
    @Override
    public void setConf(Configuration conf){
    
        configuration = conf
    }
    
    @Override
    public Configuration getConf(){
    
        return configuration;
    }
    
    public static void main(String[] args){
        
        try{
            Configuration configuration = new Configuration();
            int run = ToolRunner.run(configuration, new FruitDriver(),args);
            System.exit(run);
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

学习案例二:MR读取HBase表数据,处理后将数据写入另一个HBase表

        将 fruit1表中部分数据(name列),通过MR迁入到 fruit2 表中

        1)编写 Map类代码(继承TableMapper类,定义输出key,value的泛型,输入的K为Row_Key,输入的值为Row_Key对应的Result)

public class Fruit2Mapper extends TableMapper<ImmutableBytesWritable, Put>{

    @Override
    protected void map(ImmutableBytesWritable key, Result value, Context context)throws IOException, InterruptedException {

        // 1.构建Put对象
        Put put = new Put(key. get());

        // 2.获取数据
        for(Cell cell : value.rawCells()){

            // 3.判断当前的cell是否为“name”列
            if("name".equals(Bytes.toString(CellUtil.cloneQualifier(cel1)))){

                //4.给Put对象赋值
                put.add(cell);
            }
        }
        
        // 5.写出数据
        context.write(key, put);
    }
}

        2)编写reducer程序

public class Fruit2Reducer extends TableReducer<ImmutableBytesWritable, Put,NullWritable>{
    
    @Override
    protected void reduce(ImmutableBytesWritable key, Iterable<Put> values, Context context)throws IOException, InterruptedException{
        
        // 遍历写出
        for (Put put : values){
            context.write(NullWritable.get(), put);
        }
    }
}

        3)编写Driver程序        

public class Fruit2Driver implements Tool{

    // 定义一个Configuration
    privete Configuration configuration = null;
    
    @Override
    public int run(String[] args)throws Exception{

        // 1.获取Job对象
        Job job = Job.getInstance(configuration);
        
        // 2.设置驱动类路径
        job.setJarByClass(FruitDriver.class);

        // 3.设置Mapper&Mapper输出的KV类型
        TableMapReduceUtil.initTableMapperJob(args[0]
                    new Scan(),
                    Fruit2Mapper. class,
                    ImmutableBytesWritable.class,
                    Put.class,
                    job)
        
        // 4.设置Reducer类
        TableMapReduceUtil.initTableReducerJob(args[1], Fruit2Reducer.class, job);

        // 5.设置输入参数
        FileInputFormat.setInputPaths(job, new Path(args[0]));

        // 6.提交任务
        boolean result = job.waitForCompletion(true);
        
        return result ? 0 : 1;
    } 
    
    @Override
    public void setConf(Configuration conf){
    
        configuration = conf
    }
    
    @Override
    public Configuration getConf(){
    
        return configuration;
    }
    
    public static void main(String[] args){
        
        try{
            Configuration configuration = new Configuration();
            int run = ToolRunner.run(configuration, new Fruit2Driver(),args);
            System.exit(run);
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

八、与Hive集成

环境准备

        因为后续可能会在操作 Hive 的同时对 HBase 也会产生影响,所以 Hive 需要持有操作HBase的Jar,那么接下来拷贝 Hive 所依赖的 Jar 包(或者使用软连接的形式)。

export HBASE_HOME=/opt/module/hbase
export HIVE_HOME=/opt/module/hive

ln -s $HBASE_HOME/lib/hbase-common-1.3.1.jar $HIVE_HOME/lib/hbase-common-1.3.1.jar

ln -s $HBASE_HOME/lib/hbase-server-1.3.1.jar $HIVE_HOME/lib/hbase-server-1.3.1.jar

ln -s $HBASE_HOME/lib/hbase-client-1.3.1.jar $HIVE_HOME/lib/hbase-client-1.3.1.jar

ln -s $HBASE_HOME/lib/hbase-protocol-1.3.1.jar $HIVE_HOME/lib/hbase-protoco1-1.3.1.jar

ln -s $HBASE_HOME/lib/hbase-it-1.3.1.jar $HIVE_HOME/lib/hbase-it-1.3.1.jar

ln -s $HBASE_HOME/lib/htrace-core-3.1.0-incubating.jar $HIVE_HOME/lib/htrace-core-3.1.0-incubating.jar

ln -s $HBASE_HOME/lib/hbase-hadoop2-compat-1.3.1.jar $HIVE_HOME/lib/hbase-hadoop2-compat-1.3.1.jar

ln -s $HBASE_HOME/lib/hbase-hadoop-compat-1.3.1.jar $HIVE_HOME/lib/hbase-hadoop-compat-1.3.1.jar

        同时在hive-site.xml 中修改zookeeper的属性,如下:

学习案例一:新建 Hive 表与 HBase 表使相关联

        目标:建立Hive 表,关联HBase 表,插入数据到Hive 表的同时能够影响 HBase 表。

        分步实现:

        (1)在 Hive 中创建表同时关联 HBase

CREATE TABLE hive_hbase_emp_table(
    empno int,
    ename string,
    job string,
    mgr int,
    hiredate string,
    sal double,
    comm double,
    deptno int)
STORED BY 'org.apache,hadoop.hive.hbase.HBasStorageHandler'
WITH SERDEPROPERTIES("Hbase.columns.mapping" = ":key, info:ename, info:job, info:mgr info:hiredate, info:sal, info:comm, info:deptno")
TBLPROPERTIES("Hbase.table.name" = "hbase_emp_table");

        完成之后,可以分别进入 Hive 和HBase 查看,都生成了对应的表。

        (2)在 Hive 中创建临时中间表,用于 load 文件中的数据。
        提示:不能将数据直接load 进 Hive 所关联 HBase 的那张表中。

CREATE TABLE emp(
    empno int,
    ename string,
    job string,
    mgr int,
    hiredate string,
    sal double,
    comm double,
    deptno int)
row format delimited fields terminated by '\t';

        (3)向Hive中间表中load数据

load data local inpath 'path_data/emp.txt' into table emp;

        (4)通过 insert 命令将中间表的数据导入到Hive 关联HBase的那张表中

insert into table hive_hbase_emp_table
select * from emp;

        (5)查看 Hive 以及关联的 HBase表中都已同步插入数据

        两张表关联,数据文件存放在HBase对应的HDFS中,而Hive对应的HDFS目录下没有数据文件。

学习案例二:创建 Hive 外部表与 HBase 已存在的表相关联

        目标:在 HBase 中已经存储了某一张表 hbase_emp_table,然后在Hive 中创建一个外部表来关联 HBase 中的 hbase_emp_table 这张表,使可以借助 Hive 来分析 HBase 这张表中的数据。

        (1)在 Hive 中创建外部表

CREATE EXTERNAL TABLE relevance_hbase_emp(
    empno int,
    ename string,
    job string,
    mgr int,
    hiredate string,
    sal double,
    comm double,
    deptno int)
STORED BY 'org.apache,hadoop.hive.hbase.HBasStorageHandler'
WITH SERDEPROPERTIES("Hbase.columns.mapping" = ":key, info:ename, info:job, info:mgr info:hiredate, info:sal, info:comm, info:deptno")
TBLPROPERTIES("Hbase.table.name" = "hbase_emp_table");

        创建之后该外部表会与HBase中的hbase_emp_table表相关联,可以通过hive来对该表进行分析,如果再对HBase中的hbase_emp_table表中插入数据,该外部表也可以查询到新插入的数据。

九、HBase 优化

9.1 高可用

        在 HBase 中HMaster 负贡监控 HRegionServer 的生命周期,均衡 RegionServer 的负载,如果 HMaster 挂掉了,那么整个 HBase 集群将陷入不健康的状态,并且此时的工作状态并不会维持太久。所以HBase 支持对 IMaster 的高可用配置。

        在 hbase/conf 目录下创建 backup-masters 文件

        在 backup-masters 文件中配置高可用 HMaster 的备用节点

        如果HMaster宕机,则配置的备份节点会自动成为活跃的HMaster,选举机制与Kafka高可用一致,多个备份节点争抢向zookeeper注册。

9.2 预分区

        每一个 region 维护着 StartRow 与 EndRow,如果加入的数据符合某个 Region 维护的RowKey 范围,则该数据交给这个 Region 维护。那么依照这个原则,我们可以将数据所要投放的分区提前大致的规划好,以提高 HBase性能。

        (1)手动设定预分区(设定5个分区)

create 'table_name', 'info1', 'info2', SPLITS => ['1000', '2000', '3000', '4000']

        添加数据时,按字典序比较Row_Key,来决定分区(比如数据Row_Key为158742,会插入第二个分区)

        (2)生成16进制序列预分区

create 'table_name', 'info1', 'info2', {NUMREGIONS => 15, SPLITLGO => 'HexStringSplit'}

        (3)按照文件中设置的规则预分区

        创建 split.txt 文件,内容如下:

        然后执行:

create 'table_name', 'info1', SPLITS_FILE => 'split.txt'

        (4)使用JavaAPI创建预分区

//自定义算法,产生一系列hash 散列值存储在二维数组中。
byte[][] splitkeys = 某个散列值函数

//创建 Hbaseadmin 实例
HBaseAdmin hAdmin = new HBaseAdmin(HbaseConfiguration.create());

//创建 HrableDescriptor 实例
HTableDescriptor tableDesc = new HTableDescriptor(tableName);

//通过 HrableDescriptor 实例和散列值二维数组创建带有预分区的 Hbase 表
hAdmin.createTable(tableDesc, splitkeys);

// 设置起始和结束key,按照numRegion分区数,将[starKey,endKey]这一区间平分
// hAdmin.createTable(tableDesc,starKey,endKey, numRegion);

9.3 RowKey设计

        RowKey的设计要考虑,散列性(即数据要均匀的分布在各个预分区中)、唯一性、长度原则(生产环境中一般是70-100位)

        在考虑散列性的同时也要考虑局部数据的集中性,即常在一起用的数据放在一个分区中。

        常用的RowKey设计方案:

        (1)生成随机数、hash、散列值

        (2)字符串反转(如时间戳反转)

        (3)字符串拼接

        RowKey情景设计:使用HBase 存储每个人的通话记录(数据为:拨出手机号,接听手机号,拨号时间,通话时长)

        假设要分300个region

        设计分割键299个分别为(000|,001|,...,298|),299个分割键可以切分出300个region,每个region的RowKey以000_,001_,...,299_开头。

        考虑到散列性和部分集中性,希望同一个人的通话记录尽量在一个分区,可以将拨出手机号与300取模运算,通过余数来指定分区。即RowKey为000_手机号

        由于不同的人通话次数是不一样的,一些人的通话次数可能远超他人,因此可能会出现数据倾斜问题。

        可以在原有基础上加上时间(具体到月),即将(手机号+时间)%300取余来确定分区。即RowKey为000_手机号_时间

9.4 内存优化

        HBase 操作过程中需要大量的内存开销,毕竟Table是可以缓存在内存中的,一般会分配整个可用内存的 70%给 HBase 的 Java堆。但是不建议分配非常大的堆内存,因为 GC 过程持续太久会导致 RegionServer 处于长期不可用状态,一般16~48G内存就可以了,如果因为框架占用内存过高导致系统内存不足,框架一样会被系统服务拖死。

十、HBase 实战之谷粒微博

        表设计:

        微博内容表以用户ID+时间戳作为RowKey,每发一条微博,添加一行来存储。列簇只有一个info,列名为固定的content,值为所发的微博内容。

        用户关系表以用户ID作为RowKey,以两个列簇attends表示用户关注的人,fans表示关注该用户的人。列名为对应的关注/被关注的用户ID,值为空或者存关注的时间。

        微博收件箱表,用于展示初始化页面(内容为关注人最近发的微博内容),RowKey是用户ID,列簇只有一个info,列名是该用户对应的attends列簇下的列名,值为关注用户所发的最近微博内容,值存储多个版本(为节省存储空间,值存储为内容的RowKey)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值