Hbase使用简要分析

时间:2021-09-04 08:24:49

参考

https://blog.csdn.net/u014419512/article/details/27966957
https://www.jianshu.com/p/0f9578df7fbc
https://www.cnblogs.com/raphael5200/p/5229164.html
https://songlee24.github.io/2015/07/24/hbase-introduction/
http://www.cnblogs.com/gaopeng527/p/4967186.html
https://blog.csdn.net/hellowordlichao/article/details/27176151
http://www.rowkey.me/blog/2015/06/10/hbase-about/
https://blog.csdn.net/u010638969/article/details/53322643
https://blog.csdn.net/zhong_han_jun/article/details/45969375

Hbase简要介绍

HBase适合非结构化数据存储的数据库,是基于列的而不是基于行的模式

数据模型

Hbase使用简要分析
rowkey

决定一行数据的唯一标识;
按字典顺序排序;
最多只能存64K的字节数据;

columnfamily列族

定义表时必须定义一个列族;
列族是表的一部分,而列不是;
访问控制、磁盘和内存的使用统计都是在列族层面进行的。实际应用中,列族上的控制权限能帮助我们管理不同类型的应用:我们允许一些应用可以添加新的基本数 据、一些应用可以读取基本数据并创建继承的列族、一些应用则只允许浏览数据;

column列

列必须属于某个列族,不同列族之间可以有同名列;
name1:col1和name1:col2同属name1列族;
column在put时直接写就行;

Timestamp时间戳

用来区分版本,不同版本按时间倒序排列;
get最新的数据,scan可以看有定义版本个数的数据,版本由VERSIONS确定;
可自动赋值,也可由client赋值,但是要保证唯一性,以避免数据版本冲突;

cell单元格

由行列交叉决定value,以时间戳为版本,tableName+RowKey+ColumnKey+Timestamp=>value

存储模型

Hbase使用简要分析
数据分片存储

在HBase中,一张表的数据会被分成几份,每一份数据为一个region;
每个region内存储的key是连续范围内的,不同region存储的key范围不重合;
这些region可能被存储在同一台机器上,也可能存储在不同的机器上;
HBase作为一个分布式数据库,对数据进行分片,可以提升吞吐量。

HLog:Write-Ahead-Log,写操作先写日志

当一台regionServer crash了,可以利用HLog来恢复内存中未持久化到硬盘中的数据;
需要注意的是,同一台server上的所有region共用一个HLog实例;

MemStore
写缓存,每个store拥有独立的写缓存;
在HBase中,所有的写操作全部写到内存中,当写缓存(MemStore)写满,再刷写(flush)到磁盘中,形成一个新的文件。

HFile
磁盘文件

在存储上,HBase完全依赖HDFS,磁盘操作是直接调用HDFS的API;
同一列族的数据会被写到同一文件,因为存储特性本来就是按照列族定义的;
HBase的数据在底层文件中时以KeyValue键值对的形式存储的;
HBase没有数据类型,HFile中存储的是字节,这些字节按字典序排列。

读缓存
同一server上所有region共用;

当HBase读取磁盘上某一条数据时,HBase会将整个HFile block读到cache中;
当client请求临近的数据时,HBase的响应会更快,HBase推荐将那些相似的,会被一起查找的数据存放在一起;
全表扫描时,为了不刷走读缓存中的热数据,千万记得关闭读缓存的功能;

HBase Compaction和Region Split

a. HBase Compaction

HBase数据写入的时候,总是先写入到写缓存(MemStore)中,当写缓存写满,则flush到磁盘形成一个新的磁盘文件。为了避免小文件越来越多,HBase会做compaction,合并HFile文件,减少每次查找数据的磁盘寻道时间。compaction分为major compact和minor compact两种:

 Minor compact:将多个小文件简单合并成一个大文件
 Major compact:将同一列族的所有文件合并成一个大文件,并且删除过期无效的数据和tombstone标记

b. Region Split

client不断向HBase写入数据,region管理的数据量不断膨胀。当一个region内存储的数据量到达阈值,则会触发HBase的region split操作,将老的region拆分成两个新的子region。拆分的原则是数据量对半分。为了避免region拆分导致的IO瞬时上升,region拆分并不会立刻将拆分重写所有的磁盘文件文件,而是为每个子region创建reference文件,这些文件指向了旧的磁盘文件中对应记录的起始和终止位置。等到子region的compact操作被触发,在重写文件的时候,HBase才会为每个子region生成独立的磁盘文件。

region定位

HBase如何找到某个row key (或者某个row key的range)所在的region?使用三层类似B+树的结构来保存region位置:

第一层:Zookeeper保存了-ROOT-表的位置。
第二层:-ROOT- 表保存了.META.表所有region的位置,通过-ROOT-表,可以访问.META.表的数据。
第三层:.META.是一个特殊的表,保存了HBase中所有数据表的region位置信息。
需要注意的是:

-ROOT-表永远不会被split,保证了只需要三次跳转,就能定位到任意region
META.表每行保存一个region的位置信息,row key采用表名+表的最后一行编码而成
为了加快访问,.META.表的全部region都保存在内存中
Client会将查询过的位置信息保存缓存起来,缓存不会主动失效

物理模型

Hbase使用简要分析
HBase的架构是一个典型的master-slave模型,HBase的master节点叫HMaster,slave节点就是RegionServer。
Client
HBase Client使用HBase的RPC机制与HMaster和HRegionServer进行通信,对于管理类操作,Client与HMaster进行RPC;
对于数据读写类操作,Client与HRegionServer进行RPC;
Zookeeper
Zookeeper Quorum中除了存储了-ROOT-表的地址和HMaster的地址;
HRegionServer也会把自己以Ephemeral方式注册到Zookeeper中,使得HMaster可以随时感知到各个HRegionServer的健康状态;
Zookeeper也避免了HMaster的单点问题;
HMaster
HMaster没有单点问题,HBase中可以启动多个HMaster,通过Zookeeper的Master Election机制保证总有一个Master运行,HMaster在功能上主要负责Table和Region的管理工作:
1. 管理用户对Table的增、删、改、查操作
2. 管理HRegionServer的负载均衡,调整Region分布
3. 在Region Split后,负责新Region的分配
4. 在HRegionServer停机后,负责失效HRegionServer 上的Regions迁移
5. HDFS的垃圾文件回收
注意:Client访问HBase上数据的过程并不需要HMaster参与(寻址访问Zookeeper和HRegionServer,数据读写访问HRegioneServer),HMaster仅仅维护者table和region的元数据信息,负载很低。
HRegionServer
主要负责响应用户I/O请求,向HDFS文件系统中读写数据,是HBase中最核心的模块。

HBase访问接口

  1. Native Java API,最常规和高效的访问方式,适合Hadoop MapReduce Job并行批处理HBase表数据
  2. HBase Shell,HBase的命令行工具,最简单的接口,适合HBase管理使用
  3. Thrift Gateway,利用Thrift序列化技术,支持C++,PHP,Python等多种语言,适合其他异构系统在线访问HBase表数据
  4. REST Gateway,支持REST 风格的Http API访问HBase, 解除了语言限制
  5. Pig,可以使用Pig Latin流式编程语言来操作HBase中的数据,和Hive类似,本质最终也是编译成MapReduce Job来处理HBase表数据,适合做数据统计
  6. Hive,当前Hive的Release版本尚没有加入对HBase的支持,但在下一个版本Hive 0.7.0中将会支持HBase,可以使用类似SQL语言来访问HBase。

Habse使用

使用场景

  1. 半结构化或非结构化数据

    对于数据结构字段不够确定或杂乱无章很难按一个概念去进行抽取。当业务发展需要增加存储比如一个用户的email,phone,address信息时RDBMS需要停机维护,HBase支持动态增加.

  2. 记录稀疏表

    Hbase只存储有值的cell,对于稀疏表来说就可以节省很多空间,但是,每个cell都存储了rowkey,columnFamily,qualifier,因此cf的名字不要太长

  3. 多版本数据

    根据Row key和Column key定位到的Value可以有VERSIONS对应数量的版本值,因此对于需要存储变动历史记录的数据,用HBase就非常方便了。对于某一值,业务上一般只需要最新的值,但有时可能需要查询到历史值。

  4. 超大数据量

    当数据量越来越大,会慢慢使用读写分离策略,通过一个Master专门负责写操作,多个Slave负责读操作,服务器成本倍增。然后再分库,把关联不大的数据分开部署,一些join查询不能用了,需要借助中间层。当一个表的记录越来越大,查询变得很慢,于是又得搞分表,比如按ID取模分成多个表以减少单个表的记录数。采用HBase只需要加机器即可,HBase会自动水平切分扩展,跟Hadoop的无缝集成保障了其数据可靠性(HDFS)和海量数据分析的高性能(MapReduce)

使用建议

  1. 一张HBase表存储的列的数量可以是无限的,但是列族的数量最好控制在3个以内

    同一张表的column family数量不能超过2-3个。因为目前,flush和compaction操作是基于region进行的,当一个column family触发了MemStore flush操作,相邻的column family都会被刷写到磁盘,即使它们MemStore内的数据量还很小。因此,如果同一张表内column family的数量过多,flush和compaction将会带来更多不必要的I/O负载(当然这个问题可以通过,将flush和compaction改成列族之间互不影响来解决)。通常情况下,定义表的时候尽量使用单列族,除非列与列的查询是相对独立的,再考虑使用多个列族,比如client并不会同时请求两个列族的数据。
    当同一张内有多个列族时,注意一些列族间的数据量是否一致,假如列族A和列族B的数据量相差悬殊,列族A的大数据量会导致表数据被分片到很多个机器上,此时再对列族B的数据做扫描,效率会很低。

  2. 行键设计

    避免使用时序或者单调(递增/递减)行键,否则会导致连续到来的数据会被分配到统一region中。

  3. 尽量最小化行键和列族的大小

    每个cell都存储了rowkey,columnFamily,qualifier,因此cf的名字不要太长,避免hbase的索引过大,加重系统存储的负担

  4. 版本的数量

    避免设置过大,版本保留过多。

  5. 设置TTL

    避免后期无用数据过大

简单使用语句

Hbase shell

hbase shell
create 'user:example','name1' //create 'namespace:basename','columnFamilyname'
put 'user:example','row1','name1','row1name1'
put 'user:example','row1','name1:col1','row1name1col1'
get 'user:example','row1','name1'
get 'user:example','row1','name1:col1'
help
status //服务器状态
version //Hbase版本信息
whoami //查看连接的用户
disable 'user:example' //表失效
enable 'user:example' //失效表有效 //alter表修改,需在执行前先disable,执行后enable
alter 'user:example','name2' //新增列族
alter 'user:example',{NAME=>'name2',METHOD=>'delete'}
describe 'user:example' //查看表结构
desc 'user:example'
list //列举该namespace下所有的表
drop 'user:example'
count 'user:example'
scan 'user:example' //全表扫描
delete 'user:example','row1','name1:col1' //删除指定rowkey的指定列族的列名数据
delete 'user:example','row1','name1' //删除rowkey的制定列族数据
delete 'user:example','row1'
alter 'user:example',{NAME=>'name1',TTL=>'15768000'} //设置TTL

HBase API

//HbaseUtil
public class HbaseUtil {
    private static Connection connection = null;
    private HbaseUtil(){
    }
    public static synchronized Connection getConnection(){
        if (null == connection) {
            try {
                Configuration conf = HBaseConfiguration.create();
                conf.set("hbase.zookeeper.property.clientPort", "2181");
                conf.set("hbase.zookeeper.quorum", "2120.mzhen.cn,2122.mzhen.cn,2121.mzhen.cn");
                connection = ConnectionFactory.createConnection(conf);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return connection;
    }
 }

//OperationDao
public class OperationDao {
    private Connection connection;
    private TableName tablename;
    public OperationDao(String tablename){
        this.tablename = TableName.valueOf(tablename);
        connection = HbaseUtil.getConnection();
    }
    public void createTable(String columnFamily) throws Exception{
        Admin admin = connection.getAdmin();
        if(admin.tableExists(tablename)){
            System.out.println("table exists!");
        }else {
            HTableDescriptor desc = new HTableDescriptor(tablename);
            desc.addFamily(new HColumnDescriptor(columnFamily));
            admin.createTable(desc);
            System.out.println("create table success!");
        }
    }

    public void putData(String rowKey, String columnfamily, String column, String cell) throws Exception {
        Table table = connection.getTable(tablename);
        Put p = new Put(Bytes.toBytes(rowKey));
        p.addColumn(Bytes.toBytes(columnfamily), Bytes.toBytes(column), Bytes.toBytes(cell));
        table.put(p);
        table.close();
    }

    public String getData(String rowKey, String columnfamily, String column) throws Exception{
       Table table = connection.getTable(tablename);
       Get g = new Get(Bytes.toBytes(rowKey));
       Result res = table.get(g);
       Cell cell = res.getColumnLatestCell(Bytes.toBytes(columnfamily), Bytes.toBytes(column));
       if(cell == null){
           return null;
       }
       return Bytes.toString(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength());
    }
}

//常用语句
 System.out.println("-----------cell[]进行扫描-----");
 for(Result rs : scanner){
    for(Cell cell:rs.rawCells()){
        String row = Bytes.toString(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength());
        String family= Bytes.toString(cell.getFamilyArray(),cell.getFamilyOffset(),cell.getFamilyLength());
        String column= Bytes.toString(cell.getQualifierArray(),cell.getQualifierOffset(),cell.getQualifierLength());
        String value = Bytes.toString(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength());
        System.out.println(row+"\n---"+family+":"+column+"\n"+value);
    }
}