HBASE

HBASE

一、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确定的一个存储单元为一个cell。每个cell都保存着一个数据的多个版本,版本通过时间戳来区别。
数据都以二进制形式存储,没有数据类型的区别。
所有空数据都不占用空间。

二、安装配置

HBase-0.92.x HBase-0.94.x HBase-0.96
Hadoop-0.20.205 S X X
Hadoop-0.22.x S X X
Hadoop-1.0.x S S S
Hadoop-1.1.x NT S S
Hadoop-0.23.x X S NT
Hadoop-2.x X S S


前提条件,安装jdk 和 hadoop,并配置了环境变量


1.单机模式
直接解压安装包
tar -zxvf xxxxx.tar.gz
修改conf/hbase-site.xml,配置hbase使用的数据文件的位置,默认在/tmp/hbase-[username],此目录是linux的临时目录,可能会被系统清空,所以最好修改一下
<property>
<name>hbase.rootdir</name>
<value>file:///<path>/hbase</value>
</property>


2.伪分布式模式
修改conf/hbase-env.sh修改JAVA_HOME
export JAVA_HOME=xxxx
修改hbase-site.xml,配置使用hdfs
<property>
<name>hbase.rootdir</name>
<value>hdfs://hadoop00:9000/hbase</value>
</property>
<property>
<name>dfs.replication</name>
<value>1</value>
</property>
启动hbase


3.完全分布式模式
修改conf/hbase-env.sh修改JAVA_HOME
export JAVA_HOME=xxxx
修改hbase-site.xml,配置开启完全分布式模式
配置hbase.cluster.distributed为true。
配置hbase.rootdir设置为HDFS访问地址
<property>
<name>hbase.rootdir</name>
<value>hdfs://hadoop00:9000/hbase</value>
</property>
<property>
<name>dfs.replication</name>
<value>1</value>
</property>
<property>
<name>hbase.cluster.distributed</name>
<value>true</value>
</property>
<property>
<name>hbase.zookeeper.quorum</name>
<value>hadoop01:2181,hadoop02:2181,hadoop03:2181</value>
</property>


修改conf/hbase-env.sh禁用对zookeeper的自动管理
export HBASE_MANAGES_ZK false


配置region服务器,修改conf/regionservers文件,其中配置所有hbase主机,每个主机名独占一行,hbase启动或关闭时会按照该配置顺序启动或关闭主机中的hbase

--------------------------------------------------
~HBASE配置文件说明
hbase-env.sh配置HBase启动时需要的相关环境变量
hbase-site.xml配置HBase基本配置信息
HBASE启动时默认使用hbase-default.xml中的配置,如果需要可以修改hbase-site.xml文件,此文件中的配置将会覆盖hbase-default.xml中的配置
修改配置后要重启hbase才会起作用
--------------------------------------------------


启动集群
启动zookeeper
启动hadoop
启动hbase
访问http://xxxxx:60010来访问web界面,通过web见面管理hbase
也可以通过hbase shell脚本来访问bhase


启动备用master实现高可用
hbase-daemon.sh start master


关闭集群
stop-hbase.sh
三、基本操作
测试
bin/start-hbase.sh
bin/hbase shell
hbase>status


hbase>help


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
hbase>scan 'testtable',{RAW=>true,VERSIONS=>3}

#TODO:画图解释结构,没有数据的不占用空间,物理结构按列存储,方便压缩。


hbase>exit
bin/stop-hbase.sh
--------------------
hbase命令行下不能使用删除:
可以使用 ctrl+删除键 来进行删除

修改xshell配置:
文件->属性->终端->键盘
->delete键序列[VT220Del]
->backspace键序列[ASCII127]
--------------------
四、HBASE原理 参看文章
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 Block 段–保存表中的数据,这部分可以被压缩
Meta Block 段 (可选的)–保存用户自定义的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中获取到的数据 合并后 返回最新的。


而在数据读取时 会将store的中memstore和storefile中的数据进行合并 提供查询
此处所谓的合并 并不是真正的数据的合并 而是将数据的索引进行合并
由于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的寻址过程。
这样的寻址过程是相当耗费时间的,为了提高性能,客户端通常会缓存之前已经知道的region寻址信息,当客户端再次读取同一个表中的数据时,可以通过本地缓存的region地址信息 直接定位读取,提高效率。

5.存储系统三种结构
hash存储
B树 B+树 B-树 B*树
LSM树

    6.hbase系统架构:
hbase中的老大叫hmaster 小弟叫hregionServer
客户端叫Client
Zookeepr为hbase提供集群协调


client
访问hbase 保留一些缓存信息提升效率
zookeeper
保证任何时候集群只有一个HMaster
监控regionServer的状态 将其上线下线信息通知mater
存储所有Region的寻址地址
存储hbase的元数据信息 包括 有哪些表 有哪些列族等等
Mater
为RegionServer分配Region
为RegionServer进行负载的均衡
GFS上的垃圾回收
处理对Schema数据的更新请求
RegionServer
维护Master分配给它的region,处理对这些region的IO请求
负责切分在运行过程中变得过大的region

为什么hbase可以很快:
从逻辑结构上来说:
表按照行键进行了排序,所以查询时可以很快定位
数据按照行键切分为多个HRegion,分布在多个RegionServer中,查询大量数据时,多个RegionServer可以一起工作,从而提高速度
从物理结构上来说:
HRegion是存活在RegionServer的内存中的,读写会非常的高效
还有HFile的支持保证大量的数据可以持久化的保存
数据最终落地到HDFS中,分布式的存储,保证数据段可靠性和可扩展性


为什么hbase可以存储很多数据:
基于hdfs,所以支持可扩展性,可以通过增加大量的廉价的硬件提高存储容量
按列存储,空的数据不占用空间,当存储稀疏数据时,不会浪费空间
按例存储,同一列的数据存放在一起,而同一列的数据一般都是同样的类型的内容相似的数据,可以实现非常高效的压缩,节省空间

为什么hbase的数据是可靠的:
基于hdfs,由hdfs的可靠性保证了hbase的可靠性--即数据可以有多个备份
利用zookeeper实现了HA,即使某一台机器挂掉另外的机器也可以很快的替换它


hbase和hive和传统的关系型数据库的比较:
比起传统的关系型数据库,可以存储半结构化非结构化的数据,可以存储和处理更大级别的数据,提供高效的查询,对于稀疏数据的处理更好,具有更好的横向扩展性,免费开源性价比很高。但是不能支持非常好的事务特性,只支持行级的事务。只能通过行键来查询,表设计时难度更高。而mysql用来存储结构化的数据提供更好的事务控制。
比起hive,hive只是在mapreduce上包了一层壳,本质上还是离线数据的处理的工具,实时查询性能有限,本质上是一个基于hadoop的数据仓库工具,不能支持行级别的新增修改和删除。hbase可以提供实时的数据的处理能力,适用于在线数据查询处理,本质上是一种数据库工具。



五、java api操作
导入开发包
将hbase安装包中lib下包导入java项目


创建表

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


HBaseAdmin admin = new HBaseAdmin(conf);


HTableDescriptor htd = new HTableDescriptor(TableName.valueOf("tabe"));
HColumnDescriptor hcd_fam1 = new HColumnDescriptor("fam1");
hcd_fam1.setMaxVersions(3);
HColumnDescriptor hcd_fam2 = new HColumnDescriptor("fam2");
htd.addFamily(hcd_fam1);
htd.addFamily(hcd_fam2);


admin.createTable(htd);


admin.close();



插入数据
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();





**javaapi操作hbase时,入口类为HTable,此对象创建时需要扫描.META表,以及其他操作,这非常耗时,所以,应该将该对象设置为单例,复用该对象,如果需要多个HTable对象,应该使用HTable
Pool,通过对象池复用对象。
HTablePool pool = new HTablePool(conf,10);//不知道为什么过时了?
**hbase所有修改数据的操作都保证了行级别的原子性,


试验:一次插入100万条数据
HTable table = new HTable(conf,"tabx");
List<Put> puts = new ArrayList<Put>();
for(int i=1;i<=1000000;i++){
Put put = new Put(Bytes.toBytes("row"+i));
put.add(Bytes.toBytes("fam1"),Bytes.toBytes("col1"),Bytes.toBytes("val"+i))
puts.add(put);


if(i % 10000 == 0){
table.put(puts);
puts = new ArrayList<Put>();
}
}
table.put(puts);
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();


HBase的高级查询


(1)实现范围查询
如果只设置scan但是不做任何限制 则查询所有数据
Scan scan = new Scan();
如果设置scan并且设置scan的扫描开始和结束为止则查询范围数据 注意含头不含尾
Scan scan = new Scan();
scan.setStartRow("rk2".getBytes());
scan.setStopRow("rk4".getBytes());
(2)过滤器实现过滤查询
在scan上提供了方法来实现过滤查询
Scan scan = new Scan();
Filter filter = ...
scan.setFilter(filter)
Hbase内置的14种过滤器
HBase为筛选数据提供了一组过滤器,通过这个过滤器可以在HBase中的数据的多个维度(行,列,数据版本)上进行对数据的筛选操作,也就是说过滤器最终能够筛选的数据能够细化到具体的一个存储单元格上(由行键,列明,时间戳定位)。
Filter filter = new RowFilter(CompareOp.GREATER_OR_EQUAL,new BinaryComparator("rk3".getBytes()));
//--RowFilter配合正则过滤器 可以通过正则表达式从hbase表中筛选所有行键符合正则的数据
!!Filter filter = new RowFilter(CompareOp.EQUAL,new RegexStringComparator("^[^x]*x[^x]*$"));
Filter filter = new PrefixFilter("rkx".getBytes());
Filter filter = new KeyOnlyFilter();
Filter filter = new RandomRowFilter(0.2f);
Filter filter = new InclusiveStopFilter("rk4".getBytes());
Filter filter = new FirstKeyOnlyFilter();
//--ColumnPrefixFilter可以实现按照列的前缀过滤数据
!!Filter filter = new ColumnPrefixFilter("c2".getBytes());
//--ValueFilter可以按照值来过滤数据
!!Filter filter = new ValueFilter(CompareOp.EQUAL,new RegexStringComparator("^[^2]*2.*$"));
//--SingleColumnValueFilter按照某一个指定列的值决定该行是否返回
!!Filter filter = new SingleColumnValueFilter("cf1".getBytes(), "c1".getBytes(), CompareOp.EQUAL, new RegexStringComparator("^[^3]*3.*$"));
//--FilterList 可以将多个过滤器的效果合并起作用
!!Filter f1 = new RowFilter(CompareOp.EQUAL,new RegexStringComparator("^rk\\d+$"));
!!Filter f2 = new KeyOnlyFilter();
!!FilterList fl = new FilterList(Operator.MUST_PASS_ALL, f1,f2);
scan.setFilter(fl);


六、HBase的表设计
对HBase表的设计 会直接影响hbase使用的效率 和 使用的便利性
对HBase表的设计 主要是 列族的设计 和 行键的设计


1.列族的设计
在设计hbase表时候,列族不宜过多,越少越好,官方推荐hbase表的列族不宜超过3个。
经常要在一起查询的数据最好放在一个列族中,尽量的减少跨列族的数据访问。
如果有多个列族 多个列族中的数据应该设计的比较均匀
2.行键的设计
hbase表中行键是唯一标识一个表中行的字段,所以行键设计的好不好将会直接影响未来对hbase的查询的性能和查询的便利性
所以hbase中的行键是需要进行设计的

行键设计的基本原则:
行键必须唯一
必须唯一才能唯一标识数据
行键必须有意义
这样才能方便数据的查询
行键最好是字符串类型
因为数值类型在不同的系统中处理的方式可能不同
行键最好具有固定的长度
不同长度的数据可能会造成自然排序时排序的结果和预期不一致
行键不宜过长
行键最多可以达到64KB,但是最好是在10~100字节之间,最好不要超过16字节,越短越好,最好是8字节的整数倍。

行键的最佳实践:
散列原则:
行键的设计将会影响数据在hbase表中的排序方式,这会影响region切分后的结果,要注意,在设计行键时应该让经常要查询的数据分散在不同的region中,防止某一个或某几个regionserver成为热点。
有序原则:
行键的设计将会影响数据在hbase表中的排序方式,所以一种策略是将经常连续查询的条件作为行键最前面的数据,这样一来可以方便批量查询




七、hbase案例
用户表
id name age gender email
001 zhang 19 [email protected]
002 wang 20 [email protected]
用户访问的网页
host viewtime content userid
www.baidu.com 2016-12-20 xxxx 001
www.sina.com 2016-11-10 xxxx 001
www.souhu.com 2016-11-09 xxxx 001
www.baidu.com 2016-12-20 xxxx 002
www.163.com 2016-12-20 xxxx 002

猜你喜欢

转载自blog.csdn.net/vitaair/article/details/80220636