HBase实践

  • Post author:
  • Post category:其他




一、HBASE概述



1.概述

  • 基于hadoop的数据库工具

  • 来源于google的一片论文BigTable 后来由Apache做了开源实现 就是HBase

  • 是一种 NoSQL 非关系型的数据库 不符合关系型数据库的范式

  • 适合存储 半结构化 非结构化 的数据

  • 适合存储 稀疏的数据 空的数据不占用空间

  • 面向列(族)进行存储

  • 提供实时增删改查的能力 是一种真正的数据库

  • 可以存储海量数据 性能也很强大 可以实现上亿条记录的毫秒级别的查询

  • 但是不能提供严格的事务控制 只能在行级别保证事务

是一个高可靠性 高性能 面向列 可伸缩的分布式存储系统 利用hbase技术可以在廉价的PC上搭建起大规模结构化存储集群。

HBase利用HadoopHDFS作为其文件存储系统,利用Hadoop的MapReduce来处理HBase中的海量数据,利用Zookeeper作为协调工具



2.逻辑结构

hbase通过表来存储数据 但是表的结构和关系型数据库非常的不一样

行键 – RowKey – 即hbase的主键,访问hbse中的数据有三种方式

  • 通过单一行键访问
  • 通过一组行键访问
  • 全表扫描

列族(簇) – Column Family : 是表的元数据的一部分,需要在建表时声明,不能后期增加,如果需要增加只能alter表,一个列族可以包含一个或多个列

列 – Column: 可以动态增加列,不需要提前声明,不是表的元数据一部分

单元格与时间戳 – cell timestamp: 通过row和columns确定的一个存储单元。每个存储单元中都保存着一个数据的多个版本,版本通过时间戳来区别,而由row column 和 timestamp确定出来的唯一的存储数据的单元 称之为一个 cell 单元格。



三、基本操作

hbase>create 'testtable',''colfam1','colfam2'
hbase>list
hbase>describe 'testtable'
hbase>put 'testtable','myrow-1','colfam1:q1','value-1'
hbase>put 'testtable','myrow-2','colfam1:q2','value-2'
hbase>put 'testtable','myrow-2','colfam1:q3','value-3'
hbase>scan 'testtable'
hbase>get 'testtable','myrow-1'
hbase>delete 'testtable','myrow-2','colfam1:q2'
hbase>scan 'testtable'
hbase>disable 'testtable'
hbase>drop 'testtable'

#建表时可以指定VERSIONS,配置的是当前列族在持久化到文件系统中时,要保留几个最新的版本数据,这并不影响内存中的历史数据版本
hbase>create 'testtable',{NAME=>'colfam1',VERSIONS=>3},{NAME=>'colfam2',VERSIONS=>1}
hbase>put 'testtable','myrow-1','colfam1:q1','value-1'
#直接使用scan而不加RAW=>true只能查询到最新版本的数据
hbase>scan 'testtable'
hbase>put 'testtable','myrow-1','colfam1:q1','value-2'
hbase>scan 'testtable'
hbase>put 'testtable','myrow-1','colfam1:q1','value-3'
hbase>scan 'testtable'
#可以在查询时加上RAW=>true来开启对历史版本数据的查询,VERSIONS=>3指定查询最新的几个版本的数据
hbase>scan 'testtable',{RAW=>true,VERSIONS=>3}
hbase>put 'testtable','myrow-1','colfam1:q1','value-4'
hbase>scan 'testtable'
hbase>scan 'testtable',{RAW=>true,VERSIONS=>3}

hbase>put 'testtable','myrow-1','colfam2:x1','value-1'
hbase>scan 'testtable'
hbase>put 'testtable','myrow-1','colfam2:x1','value-2'
hbase>scan 'testtable'
hbase>scan 'testtable',{RAW=>true,VERSIONS=>3}



四、HBASE原理 参看文章



1.HBase的工作方式:



1.region的分裂和结构

hbase表中的数据按照行键的字典顺序排序

hbase表中的数据按照行的的方向切分为多个region

最开始只有一个region 随着数据量的增加 产生分裂 这个过程不停的进行 一个表可能对应一个或多个region

region是hbase表分布式存储和负载均衡的基本单元 一个表的多个region可能分布在多台HRegionServer上

region是分布式存储的基本单元 但不是存储的基本单元 内部还具有结构

一个region由多个Store来组成

有几个store取决于表的列族的数量 一个列族对应一个store 之所以这么设计 是因为 一个列族中的数据往往数据很类似 方便与进行压缩 节省存储空间

表的一个列族对应一个store store的数量由表中列族的数量来决定

一个store由一个memstore 和零个或多个storefile组成

storefile其实就是hdfs中的hfile 只能写入不能修改 所以hbase写入数据到hdfs的过程其实是不断追加hfile的过程



2.hbase写入数据

数据写入hbase时 先在hlog中记录日志 再修改memstore 直接返回成功 这样 不需要真正等待写入hdfs的过程 所以很快

memstore 内存有限 当写入数量达到一定的阈值的时候 就会创建一个新的memstore继续工作 而旧的memstore 会用一个单独的线程 写出到storefile中 最终清空旧的memstore 并在zookeeper中记录最后写出数据时间的redo point信息

由于storefile 不能修改 所以数据的更新其实是不停创建新的storefile的过程

这样多个storefile中可能存在对同一个数据的多个版本 其中旧的版本其实是垃圾数据 时间一长 垃圾数据就可能很多 浪费磁盘空间

所以当达到一定的阈值的时候 会自动合并storefile 在合并的过程中将垃圾数据清理

而当合并出来的文件达到一定程度时 再从新进行切分 防止文件过大

虽然看起来是小变大再变小 但是经过这个过程垃圾数据就被清理掉了

所以store中的数据 其实是memstore和storefile来组成的

而memstore由于是内存中的数据 一旦断电就会丢失

为了解决可能的意外造成数据丢失的问题 hbase在整个hregionserver中 通过记录hlog 来保存了所有数据操作的记录

当hbase启动时 会检查zookeeper中的redopoint信息 从hlog中恢复 这个时间点之后的数据 解决数据容易丢失的问题

hlog整个hregionServer中只有一个 所有这台机器中的所有HRegion都公用这个文件 这样整个机器的磁盘性能都可以为这一个文件提供支持 提升文件的读写效率

hlog文件最终对应的是hdfs中的文件 也是分布式存储的 保证了日志文件的可靠性



3.hbase读取数据

hfile的内部由以下部分组成:

Data Blocks 段–保存表中的数据,这部分可以被压缩

Meta Blocks 段 (可选的)–保存用户自定义的kv对,可以被压缩。

File Info 段–Hfile的元信息,不被压缩,用户也可以在这一部分添加自己的元信息。

Data Block Index 段–Data Block的索引。每条索引的key是被索引的block的第一条记录的key。

Meta Block Index段 (可选的)–Meta Block的索引。

Trailer–这一段是定长的。保存了每一段的偏移量,读取一个HFile时,会首先 读取Trailer,Trailer保存了每个段的起始位置(段的Magic Number用来做安全check),然后,DataBlock Index会被读取到内存中,这样,当检索某个key时,不需要扫描整个HFile,而只需从内存中找到key所在的block,通过一次磁盘io将整个 block读取到内存中,再找到需要的key。DataBlock Index采用LRU机制淘汰。

HFile的Data Block,Meta Block通常采用压缩方式存储,压缩之后可以大大减少网络IO和磁盘IO,随之而来的开销当然是需要花费cpu进行压缩和解压缩。

在查询数据时,先查找内存,如果内存中有当前键对应的数据,获取数据直接返回。如果没有在内存中找到数据,就去查找region对应的hfile,注意不是将所有hfile中的数据恢复到内存,而是查找每个hfile的Trailer,通过trailer找到Data Block Index,如果在这里发现了要找的数据,通过索引找到Data Blocks中对应的Data Block,将Data Block数据送回内存组装,最终多个hfile中获取到的数据 合并后 返回最新的。

由于hbase中的数据天然排序 再加上索引 整个查询也可以非常的快



4.hbase中region的寻址

在hbase的hbase名称空间下有一张meta表,其中存放了 表和region和regionSever 之间的对应关系信息,这个表很特别,规定只能有一个region

并且这个meta表的这个region的位置信息被存放在了zookeeper的meta-region-server节点下

在客户端从hbase中查找数据时,需要先联系zookeeper找到meta表对应的region的位置,连接这个位置读取到meta表中的信息,才能知道要查询的表 和 表的region和region对应的regionServer的信息

再根据这些信息连接真正要查询的表 对应的region的regionServer进行读取

这个过程就称之为region的寻址过程。



五、java api操作

​ 导入开发包,将hbase安装包中lib下包导入java项目

// 插入数据
Configuration conf = HBaseConfiguration.create();
conf.set("hbase.zookeeper.quorum","CentOS01:2181,CentOS02:2181,CentOS03:2181");

HTable table = new HTable(conf,"tabe");
Put put = new Put(Bytes.toBytes("row1"));
put.add(Bytes.toBytes("fam1"),Bytes.toBytes("col1"),Bytes.toBytes("val1"));
put.add(Bytes.toBytes("fam1"),Bytes.toBytes("col2"),Bytes.toBytes("val2"));
put.add(Bytes.toBytes("fam2"),Bytes.toBytes("col3"),Bytes.toBytes("val3"));
table.put(put);

table.close();
// 获取数据
Configuration conf = HBaseConfiguration.create();
conf.set("hbase.zookeeper.quorum","CentOS01:2181,CentOS02:2181,CentOS03:2181");

HTable table = new HTable(conf,"tabe");
Get get = new Get(Bytes.toBytes("row1"));
Result result = table.get(get);
byte [] bs = result.getValue(Bytes.toBytes("fam1"),Bytes.toBytes("col1"));
String str = Bytes.toString(bs);
System.out.println(str);

table.close();
// 获取数据集
Configuration conf = HBaseConfiguration.create();
conf.set("hbase.zookeeper.quorum","CentOS01:2181,CentOS02:2181,CentOS03:2181");

HTable table = new HTable(conf,"tabe");
Scan scan = new Scan(Bytes.toBytes("row1"));
ResultScanner scanner = table.getScanner(scan);
Iterator it = scanner.iterator();
while(it.hasNext()){
Result result = (Result) it.next();
byte [] bs = result.getValue(Bytes.toBytes("fam1"),Bytes.toBytes("col1"));
String str = Bytes.toString(bs);
System.out.println(str);
}
table.close();
// 删除数据
Configuration conf = HBaseConfiguration.create();
conf.set("hbase.zookeeper.quorum","CentOS01:2181,CentOS02:2181,CentOS03:2181");

HTable table = new HTable(conf,"tabe");
Delete delete = new Delete(Bytes.toBytes("row1"));
table.delete(delete);
table.close();
// 删除表
//1.创建配置对象
HBaseConfiguration conf = new HBaseConfiguration();
conf.set("hbase.zookeeper.quorum", "CentOS01");
//2.创建HBaseAdmin对象
HBaseAdmin admin = new HBaseAdmin(conf);
//3.删除表
admin.disableTable(Bytes.toBytes("tab1"));
admin.deleteTable(Bytes.toBytes("tab1"));
//4.关闭连接
admin.close();
// 实现范围查询
// 如果只设置scan但是不做任何限制 则查询所有数据
Scan scan = new Scan();
// 如果设置scan并且设置scan的扫描开始和结束为止则查询范围数据 注意含头不含尾
Scan scan = new Scan();
scan.setStartRow("rk2".getBytes());
scan.setStopRow("rk4".getBytes());
// 过滤器实现过滤查询
// 在scan上提供了方法来实现过滤查询
Scan scan = new Scan();
Filter filter = ...;
scan.setFilter(filter)



六、HBase的表设计

​ HBase表的设计 会直接影响hbase使用的效率 和 使用的便利性

​ HBase表的设计 主要是 列族的设计 和 行键的设计



1.列族的设计

​ 在设计hbase表时候,列族不宜过多,越少越好,官方推荐hbase表的列族不宜超过3个。

​ 经常要在一起查询的数据最好放在一个列族中,尽量的减少跨列族的数据访问。

​ 如果有多个列族 多个列族中的数据应该设计的比较均匀



2.行键的设计

hbase表中行键是唯一标识一个表中行的字段,所以行键设计的好不好将会直接影响未来对hbase的查询的性能和查询的便利性

所以hbase中的行键是需要进行设计的

行键设计的基本原则:

行键必须唯一

必须唯一才能唯一标识数据

行键必须有意义

这样才能方便数据的查询

行键最好是字符串类型

因为数值类型在不同的系统中处理的方式可能不同

行键最好具有固定的长度

不同长度的数据可能会造成自然排序时排序的结果和预期不一致

行键不宜过长

行键最多可以达到64KB,但是最好是在10~100字节之间,最好不要超过16字节,越短越好,最好是8字节的整数倍。



版权声明:本文为wzluestc原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。