HBase基本概念
HBase安装部署
HBase安装部署简介
HBase高可用配置
HBase集群安装注意事项?
HBase启动的流程是什么?
HBase操作
HBase数据结构
HBase原理
HBase api操作
HBase调优
HBase使用中遇到的问题总结
参考资料
HBase是一个高可靠性、高性能、面向列簇、可伸缩的分布式存储系统,利用HBASE技术可在廉价PC Server上搭建起大规模结构化存储集群。
HBase的目标是存储并处理大型的数据,更具体来说是仅需使用普通的硬件配置,就能够处理由成千上万的行和列所组成的大型数据。
HBase是Google Bigtable的开源实现,但是也有很多不同之处。
对比 | HBase | BigTable |
---|---|---|
文件存储系统 | HDFS | GFS |
数据处理 | MapReduce | MapReduce |
协同服务 | Chubby | Zookeeper |
1.大:一个表可以有数十亿行,上百万列;
2.无模式:每行都有一个可排序的主键和任意多的列,列可以根据需要动态的增加,同一张表中不同的行可以有截然不同的列;
3.面向列(族):面向列(族)的存储和权限控制,列(族)独立检索;
4.稀疏:空(null)列并不占用存储空间,表可以设计的非常稀疏;
5.数据多版本:每个单元中的数据可以有多个版本,默认情况下版本号自动分配,是单元格插入时的时间戳;
6.数据类型单一:Hbase中的数据都是字符串,没有类型。
HBase可以说是一个数据库,也可以说是一个存储。拥有双重属性的HBase天生就具备广阔的应用场景。
在最近的一些版本中,引入了OffHeap降低gc影响,优化链路延迟,提供Replica等可以满足在线的需求。
引入MOB,可以存储10M左右的对象,完全适应了对象存储。另外由于自身的并发能力、存储能力,可以说是具有最为竞争力的引擎
经典的八大使用场景如下图:
总的来说:HBase适用以下场景:
1. 半结构化或非结构化数据
对于数据结构字段不够确定或杂乱无章很难按一个概念去进行抽取的数据适合用HBase。以上面的例子为例,当业务发展需要存储author的email,phone,address信息时RDBMS需要停机维护,而HBase支持动态增加。
2.记录非常稀疏
RDBMS的行有多少列是固定的,为null的列浪费了存储空间。而如上文提到的,HBase为null的Column不会被存储,这样既节省了空间又提高了读性能。
3. 多版本数据
如上文提到的根据Row key和Column key定位到的Value可以有任意数量的版本值,因此对于需要存储变动历史记录的数据,用HBase就非常方便了。比如上例中的author的Address是会变动的,业务上一般只需要最新的值,但有时可能需要查询到历史值。
4. 超大数据量
当数据量越来越大,RDBMS数据库撑不住了,就出现了读写分离策略,通过一个Master专门负责写操作,多个Slave负责读操作,服务器成本倍增。
随着压力增加,Master撑不住了,这时就要分库了,把关联不大的数据分开部署,一些join查询不能用了,需要借助中间层。随着数据量的进一步增加,一个表的记录越来越大,查询就变得很慢,于是又得搞分表,比如按ID取模分成多个表以减少单个表的记录数。
经历过这些事的人都知道过程是多么的折腾。采用HBase就简单了,只需要加机器即可,HBase会自动水平切分扩展,跟Hadoop的无缝集成保障了其数据可靠性(HDFS)和海量数据分析的高性能(MapReduce)。
HBase是由Client、Zookeeper、Master、HRegionServer、HDFS等几个组件组成
1. 管理用户对表的增删改查操作
2. 管理Hregion服务器的负载均衡,调整HRegion服务器的分布
3. Hregion分裂后,负责新的HRegion的分配
4. HRegion服务器停机后,负责失效Hregion服务器上的HRegion迁移过程
1.负责存储HBase的实际数据
2.处理分配给它的Region
3.刷新缓存到HDFS
4.维护Hlog
5.执行压缩
6.负责处理Region分片
Client包含了访问Hbase的接口,另外Client还维护了对应的cache来加速Hbase的访问,比如cache的.META.元数据的信息。
1.Master的高可用,HRegionServer的监控
当HBase集群启动成功后,会在ZK注册如下znode:
/hbase/master,其中包含当前活动(即赢得选举)的HMaster信息;
/hbase/backup-masters/[host-name],每个子znode包含当前作为热备的HMaster信息;
/hbase/rs/[host-name],每个子znode包含各RegionServer的信息。
2. LOG_SPLIT管理
HMaster会在ZK上注册/hbase/splitlog临时节点,其中存放有存活RegionServer与其应该处理的Region HLog的映射关系。各个RegionServer从该节点得到分配的Region,重放HLog,并将结果写回该节点,以通知HMaster进行后续操作。
所有znode都是临时(ephemeral)节点,HMaster和RegionServer通过心跳维护这些znode。
活动HMaster对/hbase/rs路径下的znode注册监听,当有RegionServer失败时,心跳信号消失,超时过后其对应的znode被删除,HMaster即可感知到RegionServer下线,并将该RegionServer持有的Region重新路由到其他服务器上去。
同理,所有热备HMaster都对/hbase/master节点注册监听,当前HMaster挂掉后,该znode被删除,即可触发重新选举HMaster。
3. .META.表位置维护
HDFS为HBase提供最终的底层数据存储服务,同时为HBase提供高可用(Hlog存储在HDFS)的支持,具体功能概括如下:
提供元数据和表数据的底层分布式存储服务
数据多副本,保证的高可靠和高可用性
HBase的修改记录,当对HBase读写数据的时候,数据不是直接写进磁盘,它会在内存中保留一段时间(时间以及数据量阈值可以设定)。但把数据保存在内存中可能有更高的概率引起数据丢失.
为了解决这个问题,数据会先写在一个叫做Write-Ahead logfile的文件中,然后再写入内存中。所以在系统出现故障的时候,数据可以通过这个日志文件重建。
Hbase表的分片,HBase表会根据RowKey值被切分成不同的region存储在RegionServer中,在一个RegionServer中可以有多个不同的region。
HFile存储在Store中,一个Store对应HBase表中的一个列族(列簇, Column Family)。
顾名思义,就是内存存储,位于内存中,用来保存当前的数据操作,所以当数据保存在WAL中之后,RegsionServer会在内存中存储键值对。
这是在磁盘上保存原始数据的实际的物理文件,是实际的存储文件。StoreFile是以Hfile的形式存储在HDFS的。
对比 | HBase | 传统数据库 |
---|---|---|
数据类型 | 字符串 | 丰富的数据类型 |
数据操作 | 简单的插入、查询、删除、清空等操作,表和表之间是分离的,没有复杂的表和表之间的关系 | 有各式各样的函数和连接操作 |
存储模式 | 列(簇)存储 | 基于表格结构和行存储 |
数据更新 | 实际上是插入了新的数据 | 只是替换和修改 |
可伸缩性 | 可以轻松的增加或减少硬件的数目,并且对错误的兼容性比较高 | 需要增加中间层才能实现这样的功能 |
事务 | 只可以实现单行的事务性,行与行之间、表与表之前不必满足事务性 | 可以实现跨行的事务性 |
1.Zookeeper正常部署
2.Hadoop正常部署
3. 解压HBase安装包并配置环境变量
解压安装包:
# 解压HBase安装包到指定目录
tar -zxvf hbase-1.3.1-bin.tar.gz -C 目录名
配置环境变量:
# 编辑~/.bashrc文件
vim ~/.bashrc
#配置HBase环境变量
export HBASE_HOME={
HBase安装目录}
export PATH=$PATH:$JAVA_HOME/bin:/bin:$ZOOKEEPER_HOME/bin:/bin:$FLUME_HOME/bin:$HBASE_HOME/bin:$HADOOP_HOME/sbin:$HADOOP_HOME/bin:$PATH
4.修改HBase配置文件
export JAVA_HOME={
Java安装目录}
export HBASE_MANAGES_ZK=false
<configuration>
<property>
<name>hbase.rootdirname>
<value>hdfs://hadoop102:9000/hbasevalue>
property>
<property>
<name>hbase.cluster.distributedname>
<value>truevalue>
property>
<property>
<name>hbase.master.portname>
<value>16000value>
property>
<property>
<name>hbase.zookeeper.quorumname>
<value>hadoop102:2181,hadoop103:2181,hadoop104:2181value>
property>
<property>
<name>hbase.zookeeper.property.dataDirname>
<value>/opt/module/zookeeper-3.4.10/zkDatavalue>
property>
configuration>
# 配置自己HBase集群的主机名
hadoop102
hadoop103
hadoop104
5.HBase远程发送到集群其它节点
6.启动HBase
bin/hbase-daemon.sh start master
bin/hbase-daemon.sh start regionserver
在HBase中Hmaster负责监控RegionServer的生命周期,均衡RegionServer的负载,如果Hmaster挂掉了,那么整个HBase集群将陷入不健康的状态,并且此时的工作状态并不会维持太久。所以HBase支持对Hmaster的高可用配置。
bin/stop-hbase.sh
touch conf/backup-masters
echo backup-master主机名 > conf/backup-masters
scp -r conf/主机名:{
HBase安装目录}
1.应该先安装并启动Hadoop
2.应该先安装并启动Zookeeper
3.HBase集群对时间要求很高,所以安装前应该配置时钟同步服务
HBase的启动以start-hbase.sh为起点,经历了以下过程:
运行 hbase-config.sh
hbase-config.sh的作用:
① 装载相关配置,如HBASE_HOME目录,conf目录,regionserver机器列表,JAVA_HOME 目录等,它会调用$HBASE_HOME/conf/hbase-env.sh ;
② 解析参数(0.96 版本及以后才可以带唯一参数 autorestart,作用就是重启);
③ 调用 hbase-daemon.sh 来启动 master;
④ 调用 hbase-daemons.sh 来启动 regionserver.sh ,zookeeper.sh, master-backup.sh。
各个脚本的作用:
① hbase-env.sh 的作用:
主要是配置 JVM 及其 GC 参数,还可以配置 log 目录及参数,配置是否需要ZK管理 HBase,配置进程 id 目录等。
② hbase-daemons.sh 的作用:
根据需要启动的进程,如 zookeeper,则调用 zookeepers.sh如 regionserver,则调用 regionservers.sh,如 master-backup,则调用 master-backup.sh。
③ zookeepers.sh 的作用:
如果 hbase-env.sh 中的 HBASE_MANAGES_ZK"=“true”,那么通过ZKServerTool这个类解析xml配置文件,获取 ZK 节点列表,然后通过 SSH 向这些节点发送远程命令执行。
④ regionservers.sh 的作用:
与 zookeepers.sh 类似,通过配置文件,获取 regionserver 机器列表,然后 SSH 向这些机器发送远程命令。
⑤ master-backup.sh 的作用:
通过 backup-masters 这个配置文件,获取 backup-masters 机器列表,然后 SSH 向这些机器发送远程命令。
bin/hbase shell
help
list
create 'student','info' # 创建表,表名student,列簇名info
put 'student','1001','info:sex','male'
put 'student','1001','info:age','18'
put 'student','1002','info:name','Janna'
put 'student','1002','info:sex','female'
put 'student','1002','info:age','20'
scan 'student' #全表扫描
scan 'student',{
STARTROW => '1001', STOPROW => '1001'} #设置起始行键和终止行键扫描
describe 'student'
put 'student','1001','info:name','Nick'
put 'student','1001','info:age','100'
get 'student','1001'
get 'student','1001','info:name'
count 'student'
deleteall 'student','1001' # 删除某rowkey的全部数据
delete 'student','1002','info:sex' # 删除某rowkey的某一列数据
# 提示:清空表的操作顺序为先disable,然后再truncate。
truncate 'student'
# 首先需要先让该表为disable状态
disable 'student'
# 然后才能drop这个表
drop 'student'
# 提示:如果直接drop表,会报错:ERROR: Table student is enabled. Disable it first.
# 将info列族中的数据存放3个版本
alter 'student',{
NAME=>'info',VERSIONS=>3}
关于HBase高阶shell操作,可以参照我之前写的博客,仅做抛砖引玉,链接如下:
HBase高阶shell操作
预分区的目的主要是在创建表的时候指定分区数,提前规划表有多个分区,以及每个分区的区间范围.
这样在存储的时候rowkey按照分区的区间存储,可以避免region热点问题。
shell方案:
create 'tb_splits', {
NAME => 'cf',VERSIONS=> 3},{
SPLITS => ['10','20','30']}
java程序控制:
// 可以指定预分区的splitKey,即是指定region间的rowkey临界值。
HBaseAdmin.createTable(HTableDescriptor tableDescriptor,byte[][]splitkeys)
导入:
# 其中本地路径格式为 file:///path
# HDFS路径格式为:hdfs://path
bin/hbase org.apache.hadoop.hbase.mapreduce.Driver import 表名 路径
除此之外,HBase的数据写入主要有四种方式:
这几种写入方式的具体操作可以参考我之前写过的博客,链接如下:
HBase批量入库遇到的坑
导出:
# 其中本地路径格式为 file:///path
# HDFS路径格式为:hdfs://path
bin/hbase org.apache.hadoop.hbase.mapreduce.Driver export 表名 路径
RowKey行键 (RowKey)可以是任意字符串(最大长度是64KB,实际应用中长度一般为 10-100bytes),在HBASE内部,RowKey保存为字节数组。存储时,数据按照RowKey的字典序(byte order)排序存储。
1. Rowkey长度原则
Rowkey 是一个二进制码流,Rowkey 的长度被很多开发者建议说设计在10~100 个字节,不过建议是越短越好,不要超过16 个字节。
原因如下:
(1)数据的持久化文件HFile 中是按照KeyValue 存储的,如果Rowkey 过长比如100 个字节,1000 万列数据光Rowkey 就要占用100*1000 万=10 亿个字节,将近1G 数据,这会极大影响HFile 的存储效率;
(2)MemStore 将缓存部分数据到内存,如果Rowkey 字段过长内存的有效利用率会降低,系统将无法缓存更多的数据,这会降低检索效率。因此Rowkey 的字节长度越短越好。
(3)目前操作系统是都是64 位系统,内存8 字节对齐。控制在16 个字节,8 字节的整数倍利用操作系统的最佳特性。
2. Rowkey散列原则
如果Rowkey 是按时间戳的方式递增,不要将时间放在二进制码的前面,建议将Rowkey的高位作为散列字段,由程序循环生成,低位放时间字段,这样将提高数据均衡分布在每个Regionserver 实现负载均衡的几率。如果没有散列字段,首字段直接是时间信息将产生所有新数据都在一个 RegionServer 上堆积的热点现象,这样在做数据检索的时候负载将会集中在个别RegionServer,降低查询效率。
3. Rowkey唯一原则
必须在设计上保证其唯一性。
列族:HBASE表中的每个列,都归属于某个列族。列族是表的schema的一部 分(而列不是),必须在使用表之前定义。列名都以列族作为前缀。例如 courses:history,courses:math都属于courses 这个列族。
HBase中通过row和columns确定的为一个存贮单元称为cell。
Cell:由{row key, column(= + ), version}唯一确定的单元。
cell 中的数据是没有类型的,全部是字节码形式存储。
HBASE 中通过rowkey和columns确定的为一个存贮单元称为cell。每个 cell都保存 着同一份数据的多个版本。版本通过时间戳来索引。
时间戳的类型是 64位整型。时间戳可以由HBASE(在数据写入时自动 )赋值,此时时间戳是精确到毫秒 的当前系统时间。时间戳也可以由客户显式赋值。
如果应用程序要避免数据版 本冲突,就必须自己生成具有唯一性的时间戳。每个 cell中,不同版本的数据按照时间倒序排序,即最新的数据排在最前面。
为了避免数据存在过多版本造成的的管理 (包括存贮和索引)负担,HBASE提供 了两种数据版本回收方式。一是保存数据的最后n个版本,二是保存最近一段 时间内的版本(比如最近七天)。用户可以针对每个列族进行设置。
命名空间的结构如下图:
(1) Table:表,所有的表都是命名空间的成员,即表必属于某个命名空间,如果没有指定,则在default默认的命名空间中。
(2) RegionServer group:一个命名空间包含了默认的RegionServer Group。
(3) Permission:权限,命名空间能够让我们来定义访问控制列表ACL(Access Control List)。例如,创建表,读取表,删除,更新等等操作。
(4) Quota:限额,可以强制一个命名空间可包含的region的数量。
关于ACL权限认证的相关内容,可以参考我之前写过的博客,链接如下:
ACL权限认证
每个HBase集群包含一个HMaster节点和多个HRegionServer节点(其中HMaster和HRegionServer的注册信息保存在Zookeeper上),同时Client进行读写操作时,也要通过Zookeeper访问.META.表的元信息
每个HRegionServer对应一个HLog日志文件(主要用于数据恢复),HLog日志文件的LOG_SPLIT信息,存储在Zookeeper中
每个HRegionServer包含多个HRegion,每个HRegion对应多个store,每个store存储的是一个列簇的数据
每个store包含一个Memstore和多个StoreFile,其中
StoreFile的数据以HFile的形式存储在HDFS上
读操作如下图所示:
1)Client先访问zookeeper,从meta表读取region的位置,然后读取meta表中的数据。meta中又存储了用户表的region信息;
2)根据namespace、表名和rowkey在meta表中找到对应的region信息;
3)找到这个region对应的regionserver;
4)查找对应的region;
5)先从MemStore找数据,如果没有,再到BlockCache里面读;
6)BlockCache还没有,再到StoreFile上读(为了读取的效率);
7)如果是从StoreFile里面读取的数据,不是直接返回给客户端,而是先写入BlockCache,再返回给客户端。
HBase写数据流程如下图:
① Client先访问zookeeper,找到Meta表,并获取Meta表元数据。
② 确定当前将要写入的数据所对应的HRegion和HRegionServer服务器。
③ Client向该HRegionServer服务器发起写入数据请求,然后HRegionServer收到请求并响应。
④ Client先把数据写入到HLog,以防止数据丢失。
⑤ 然后将数据写入到Memstore。
⑥ 如果HLog和Memstore均写入成功,则这条数据写入成功
⑦ 如果Memstore达到阈值,会把Memstore中的数据flush到Storefile中。
⑧ 当Storefile越来越多,会触发Compact合并操作,把过多的Storefile合并成一个大的Storefile。
⑨ 当Storefile越来越大,Region也会越来越大,达到阈值后,会触发Split操作,将Region一分为二。
为了保证hbase随机读取的性能,hfile里面的rowkey是有序的。当客户端的请求在到达regionserver之后,为了保证写入rowkey的有序性,所以不能将数据立刻写入到hfile中,而是将每个变更操作保存在内存中,也就是memstore中。
memstore能够很方便的支持操作的随机插入,并保证所有的操作在内存中是有序的。当memstore达到一定的量之后,会将memstore里面的数据flush到hfile中,这样能充分利用hadoop写入大文件的性能优势,提高写入性能。
由于memstore是存放在内存中,如果regionserver因为某种原因死了,会导致内存中数据丢失。所有为了保证数据不丢失,hbase将更新操作在写入memstore之前会写入到一个write ahead log(WAL)中。
WAL文件是追加、顺序写入的,WAL每个regionserver只有一个,同一个regionserver上所有region写入同一个的WAL文件。这样当某个regionserver失败时,可以通过WAL文件,将所有的操作顺序重新加载到memstore中。
1.当某个memstroe的大小达到了hbase.hregion.memstore.flush.size(默认值128M),其所在region的所有memstore都会刷写(阻塞写)。
2.当region server 中memstore 的总大小达到堆内存的百分之40时,region 会按照其所有 memstore 的大小顺序(由大到小)依次进行刷写。直到 region server中所有 memstore 的总大小减小到上述值以下。(阻塞写)
3. 到达自动刷写的时间,也会触发memstoreflush。自动刷新的时间间隔由该属性进行配置hbase.regionserver.optionalcacheflushinterval(默认1小时)。
1.当MemStore数据达到阈值(默认是128M,老版本是64M),将数据刷到硬盘,将内存中的数据删除,同时删除HLog中的历史数据;
2.并将数据存储到HDFS中;
3.在HLog中做标记点。
HBase的查询实现只提供两种方式:
1.按指定RowKey 获取唯一一条记录,get方法(org.apache.hadoop.hbase.client.Get)
Get 的方法处理分两种 : 设置了ClosestRowBefore 和没有设置ClosestRowBefore的rowlock。主要是用来保证行的事务性,即每个get 是以一个row 来标记的。一个row中可以有很多family 和column。
2.按指定的条件获取一批记录,scan方法(org.apache.Hadoop.hbase.client.Scan)实现条件查询功能使用的就是scan 方式。
(1)scan 可以通过setCaching 与setBatch 方法提高速度(以空间换时间);
(2)scan 可以通过setStartRow 与setEndRow 来限定范围([start,end)start 是闭区间,end 是开区间)。范围越小,性能越高。
(3)scan 可以通过setFilter 方法添加过滤器,这也是分页、多条件查询的基础。
setCache用于设置缓存,即设置一次RPC请求可以获取多行数据。对于缓存操作,如果行的数据量非常大,多行数据有可能超过客户端进程的内存容量,由此引入批量处理这一解决方案。
setBatch 用于设置批量处理,批量可以让用户选择每一次ResultScanner实例的next操作要取回多少列,例如,在扫描中设置setBatch(5),则一次next()返回的Result实例会包括5列。
如果一行包括的列数超过了批量中设置的值,则可以将这一行分片,每次next操作返回一片,当一行的列数不能被批量中设置的值整除时,最后一次返回的Result实例会包含比较少的列,如,一行17列,batch设置为5,则一共返回4个Result实例,这4个实例中包括的列数分别为5、5、5、2。
组合使用扫描器缓存和批量大小,可以让用户方便地控制扫描一个范围内的行键所需要的RPC调用次数。Cache设置了服务器一次返回的行数,而Batch设置了服务器一次返回的列数。
假如我们建立了一张有两个列族的表,添加了10行数据,每个行的每个列族下有10列,这意味着整个表一共有200列(或单元格,因为每个列只有一个版本),其中每行有20列。
如下图:
① Batch参数决定了一行数据分为几个Result,它只针对一行数据,Batch再大,也只能将一行的数据放入一个Result中。所以当一行数据有10列,而Batch为100时,也只能将一行的所有列都放入一个Result,不会混合其他行;
② 缓存值决定一次RPC返回几个Result,根据Batch划分的Result个数除以缓存个数可以得到RPC消息个数(之前定义缓存值决定一次返回的行数,这是不准确的,准确来说是决定一次RPC返回的Result个数,由于在引入Batch之前,一行封装为一个Result,因此定义缓存值决定一次返回的行数,但引入Batch后,更准确的说法是缓存值决定了一次RPC返回的Result个数);
RPC请求次数 = (行数 * 每行列数) / Min(每行的列数,批量大小) / 扫描器缓存
下图展示了缓存和批量两个参数如何联动,下图中有一个包含9行数据的表,每行都包含一些列。使用了一个缓存为6、批量大小为3的扫描器,需要三次RPC请求来传送数据:
HBase的过滤器主要包括:
按照value的值过滤 ValueFilter
按照列簇进行过滤 FamilyFilter
按照行键进行过滤 RowFilter
HBase的过滤器是按照比较符结合比较器的方式来实现的
其中,比较符包括:
相等(EQUAL)、
大于(GREATER)、
小于(LESS),
大于等于(GREATER_OR_EQUAL),
小于等于(LESS_OR_EQUAL)
不等于(NOT_EQUAL)
几种过滤方式
比较器主要包括:
BinaryComparator
BinaryPrefixComparator
NullComparator
BitComparator
RegexStringComparator
SubStringComparator
等
关于详细的过滤器使用操作,可以参考我之前写过的博客:
hbase shell及 java api的过滤器操作
在日常生活中,包括在设计计算机软件时,我们经常要判断一个元素是否在一个集合中。
比如在字处理软件中,需要检查一个英语单词是否拼写正确(也就是要判断它是否在已知的字典中);
在 FBI,一个嫌疑人的名字是否已经在嫌疑名单上;
在网络爬虫里,一个网址是否被访问过等等。
最直接的方法就是将集合中全部的元素存在计算机中,遇到一个新元素时,将它和集合中的元素直接比较即可。
一般来讲,计算机中的集合是用哈希表(hash table)来存储的。它的好处是快速准确,缺点是费存储空间。
当集合比较小时,这个问题不显著,但是当集合巨大时,哈希表存储效率低的问题就显现出来了。
比如说,一个象 Yahoo,Hotmail 和 Gmai 那样的公众电子邮件(email)提供商,总是需要过滤来自发送垃圾邮件的人(spamer)的垃圾邮件。一个办法就是记录下那些发垃圾邮件的 email 地址。
由于那些发送者不停地在注册新的地址,全世界少说也有几十亿个发垃圾邮件的地址,将他们都存起来则需要大量的网络服务器。如果用哈希表,每存储一亿个 email 地址, 就需要 1.6GB 的内存(用哈希表实现的具体办法是将每一个 email 地址对应成一个八字节的信息指纹googlechinablog.com/2006/08/blog-post.html,然后将这些信息指纹存入哈希表,由于哈希表的存储效率一般只有 50%,因此一个 email 地址需要占用十六个字节。一亿个地址大约要 1.6GB, 即十六亿字节的内存)。
因此存贮几十亿个邮件地址可能需要上百 GB 的内存。除非是超级计算机,一般服务器是无法存储的。
布隆过滤器只需要哈希表 1/8 到 1/4 的大小就能解决同样的问题。
Bloom Filter是一种空间效率很高的随机数据结构,它利用位数组很简洁地表示一个集合,并能判断一个元素是否属于这个集合.
Bloom Filter的这种高效是有一定代价的:在判断一个元素是否属于某个集合时,有可能会把不属于这个集合的元素误认为属于这个集合(false positive)。
因此,Bloom Filter不适合那些“零错误”的应用场合。而在能容忍低错误率的应用场合下,Bloom Filter通过极少的错误换取了存储空间的极大节省。
下面我们具体来看Bloom Filter是如何用位数组表示集合的。初始状态时,Bloom Filter是一个包含m位的位数组,每一位都置为0。
为了表达S={x1, x2,…,xn}这样一个n个元素的集合,Bloom Filter使用k个相互独立的哈希函数(Hash Function),它们分别将集合中的每个元素映射到{1,…,m}的范围中。
对任意一个元素x,第i个哈希函数映射的位置hi(x)就会被置为1(1≤i≤k)。
注意,如果一个位置多次被置为1,那么只有第一次会起作用,后面几次将没有任何效果。在下图中,k=3,且有两个哈希函数选中同一个位置(从左边数第五位)。
在判断y是否属于这个集合时,我们对y应用k次哈希函数,如果所有hi(y)的位置都是1(1≤i≤k),那么我们就认为y是集合中的元素,否则就认为y不是集合中的元素。下图中y1就不是集合中的元素。y2或者属于这个集合,或者刚好是一个false positive。
a. 为了add一个元素,用k个hash function将它hash得到bloom filter中k个bit位,将这k个bit位置1。
b. 为了query一个元素,即判断它是否在集合中,用k个hash function将它hash得到k个bit位。若这k bits全为1,则此元素在集合中;若其中任一位不为1,则此元素比不在集合中(因为如果在,则在add时已经把对应的k个bits位置为1)。
c. 不允许remove元素,因为那样的话会把相应的k个bits位置为0,而其中很有可能有其他元素对应的位。因此remove会引入false negative,这是绝对不被允许的。
布隆过滤器决不会漏掉任何一个在黑名单中的可疑地址。
但是,它有一条不足之处,也就是它有极小的可能将一个不在黑名单中的电子邮件地址判定为在黑名单中,因为有可能某个好的邮件地址正巧对应个八个都被设置成一的二进制位。好在这种可能性很小,我们把它称为误识概率。
布隆过滤器的好处在于快速,省空间,但是有一定的误识别率,常见的补救办法是在建立一个小的白名单,存储那些可能别误判的邮件地址。
1)ZooKeeper会监控HRegionServer的上下线情况,当ZK发现某个HRegionServer宕机之后会通知HMaster进行失效备援;
2)该HRegionServer会停止对外提供服务,就是它所负责的region暂时停止对外提供服务;
3)HMaster会将该HRegionServer所负责的region转移到其他HRegionServer上,并且会对HRegionServer上存在memstore中还未持久化到磁盘中的数据进行恢复;
4)这个恢复的工作是由WAL重播来完成,这个过程如下:
· wal实际上就是一个文件,存在/hbase/WAL/对应RegionServer路径下。
·宕机发生时,读取该RegionServer所对应的路径下的wal文件,然后根据不同的region切分成不同的临时文件recover.edits。
· 当region被分配到新的RegionServer中,RegionServer读取region时会进行是否存在recover.edits,如果有则进行恢复。
在HBase中每当有memstore数据flush到磁盘之后,就形成一个storefile,当storeFile的数量达到一定程度后,就需要将 storefile 文件来进行 compaction 操作。
Compact 的作用:
① 合并文件
② 清除过期,多余版本的数据
③ 提高读写数据的效率
HBase 中实现了两种 compaction 的方式:minor and major. 这两种 compaction 方式的区别是:
① Minor 操作只用来做部分文件的合并操作以及包括 minVersion=0 并且设置 ttl (Time to live)的过期版本清理,不做任何删除数据、多版本数据的清理工作。
② Major 操作是对 Region 下的HStore下的所有StoreFile执行合并操作,最终的结果是整理合并出一个文件。
compact相关配置
配置名称 | 配置描述 | 默认配置 | 线上配置 |
---|---|---|---|
hbase.hregion.major.compaction | 触发major compact的周期 | 86400000(1d) | 0(关掉major compact) |
hbase.hstore.compaction.min | 进入minor compact队列的storefiles最小个数 | 3 | 10 |
在保证读性能的前提下,优化配置,减少compaction的发生
执行命令: major_compact手动进行major compaction操作
在项目中添加如下依赖:
<dependency>
<groupId>org.apache.hbasegroupId>
<artifactId>hbase-serverartifactId>
<version>1.3.1version>
dependency>
<dependency>
<groupId>org.apache.hbasegroupId>
<artifactId>hbase-clientartifactId>
<version>1.3.1version>
dependency>
<dependency>
<groupId>jdk.toolsgroupId>
<artifactId>jdk.toolsartifactId>
<version>1.8version>
<scope>systemscope>
<systemPath>${JAVA_HOME}/lib/tools.jarsystemPath>
dependency>
public static Configuration conf;
static{
//使用HBaseConfiguration的单例方法实例化
conf = HBaseConfiguration.create();
conf.set("hbase.zookeeper.quorum", "ip地址");
conf.set("hbase.zookeeper.property.clientPort", "2181");
}
public static boolean isTableExist(String tableName) throws MasterNotRunningException,
ZooKeeperConnectionException, IOException{
//在HBase中管理、访问表需要先创建HBaseAdmin对象
//Connection connection = ConnectionFactory.createConnection(conf);
//HBaseAdmin admin = (HBaseAdmin) connection.getAdmin();
HBaseAdmin admin = new HBaseAdmin(conf);
return admin.tableExists(tableName);
}
public static void createTable(String tableName, String... columnFamily) throws
MasterNotRunningException, ZooKeeperConnectionException, IOException{
HBaseAdmin admin = new HBaseAdmin(conf);
//判断表是否存在
if(isTableExist(tableName)) {
System.out.println("表" + tableName + "已存在");
//System.exit(0);
} else {
//创建表属性对象,表名需要转字节
HTableDescriptor descriptor = new HTableDescriptor(TableName.valueOf(tableName));
//创建多个列族
for(String cf : columnFamily){
descriptor.addFamily(new HColumnDescriptor(cf));
}
//根据对表的配置,创建表
admin.createTable(descriptor);
System.out.println("表" + tableName + "创建成功!");
}
}
public static void dropTable(String tableName) throws MasterNotRunningException,
ZooKeeperConnectionException, IOException {
HBaseAdmin admin = new HBaseAdmin(conf);
if(isTableExist(tableName)) {
admin.disableTable(tableName);
admin.deleteTable(tableName);
System.out.println("表" + tableName + "删除成功!");
} else {
System.out.println("表" + tableName + "不存在!");
}
}
public static void addRowData(String tableName, String rowKey, String columnFamily, String
column, String value) throws IOException{
//创建HTable对象
HTable hTable = new HTable(conf, tableName);
//向表中插入数据
Put put = new Put(Bytes.toBytes(rowKey));
//向Put对象中组装数据
put.add(Bytes.toBytes(columnFamily), Bytes.toBytes(column), Bytes.toBytes(value));
hTable.put(put);
hTable.close();
System.out.println("插入数据成功");
}
public static void deleteMultiRow(String tableName, String... rows) throws IOException{
HTable hTable = new HTable(conf, tableName);
List<Delete> deleteList = new ArrayList<Delete>();
for(String row : rows){
Delete delete = new Delete(Bytes.toBytes(row));
deleteList.add(delete);
}
hTable.delete(deleteList);
hTable.close();
}
public static void getAllRows(String tableName) throws IOException{
HTable hTable = new HTable(conf, tableName);
//得到用于扫描region的对象
Scan scan = new Scan();
//使用HTable得到resultcanner实现类的对象
ResultScanner resultScanner = hTable.getScanner(scan);
for(Result result : resultScanner){
Cell[] cells = result.rawCells();
for(Cell cell : cells){
//得到rowkey
System.out.println("行键:" + Bytes.toString(CellUtil.cloneRow(cell)));
//得到列族
System.out.println("列族" + Bytes.toString(CellUtil.cloneFamily(cell)));
System.out.println("列:" + Bytes.toString(CellUtil.cloneQualifier(cell)));
System.out.println("值:" + Bytes.toString(CellUtil.cloneValue(cell)));
}
}
}
public static void getRow(String tableName, String rowKey) throws IOException{
HTable table = new HTable(conf, tableName);
Get get = new Get(Bytes.toBytes(rowKey));
//get.setMaxVersions();显示所有版本
//get.setTimeStamp();显示指定时间戳的版本
Result result = table.get(get);
for(Cell cell : result.rawCells()){
System.out.println("行键:" + Bytes.toString(result.getRow()));
System.out.println("列族" + Bytes.toString(CellUtil.cloneFamily(cell)));
System.out.println("列:" + Bytes.toString(CellUtil.cloneQualifier(cell)));
System.out.println("值:" + Bytes.toString(CellUtil.cloneValue(cell)));
System.out.println("时间戳:" + cell.getTimestamp());
}
}
public static void getRowQualifier(String tableName, String rowKey, String family, String
qualifier) throws IOException{
HTable table = new HTable(conf, tableName);
Get get = new Get(Bytes.toBytes(rowKey));
get.addColumn(Bytes.toBytes(family), Bytes.toBytes(qualifier));
Result result = table.get(get);
for(Cell cell : result.rawCells()){
System.out.println("行键:" + Bytes.toString(result.getRow()));
System.out.println("列族" + Bytes.toString(CellUtil.cloneFamily(cell)));
System.out.println("列:" + Bytes.toString(CellUtil.cloneQualifier(cell)));
System.out.println("值:" + Bytes.toString(CellUtil.cloneValue(cell)));
}
}
HBase调优可以参考我之前写过的博客,链接如下:
实际工作中的HBase优化
HBase遇到的问题,可以参考我之前写过的如下两篇博客,链接如下:
hbase常见问题及解决方案总结(一)
hbase常见问题及解决方案(二)
HBase高阶shell操作
HBase批量入库遇到的坑
ACL权限认证
hbase shell及 java api的过滤器操作
实际工作中的HBase优化
hbase常见问题及解决方案总结(一)
hbase常见问题及解决方案(二)