一:基本信息
1.Hbase是什么
2006年,Google公布了BigTable论文,BigTable是一种构建于GFS和 MapReduce之上的多维稀疏图管理工具。
正是这三篇论文,掀起了开源软件的大数据热潮。人们根据GFS,开发出了HDFS 文件存储。MapReduce计算框架,也成了海量数据处理的标准。而HDFS与 MapReduce结合在一起,形成了Hadoop。而BigTable更是启发了无数的NoSQL 数据库。而HBase正是继承了正统的BigTable思想。所以,Hadoop+HBase是模拟 了Google处理海量网页的三大基石实现的,他们也就成了开源大数据处理的基石。
2.hbase的特征
1、HBase支持非常大的数据集,数十亿行*数百万列。如此庞大的数据量级,足以 撑爆我们在J2EE阶段学习过的所有数据存储引擎。
2、HBase支持大数据量的随机、实时读写操作。在海量数据中,可以实现毫秒级的 数据读写。
3、HBase从一开始就深度集成了Hadoop。HBase基于Hadoop进行文件持久化, 还继承了Hadoop带来的强大的可扩展性。Hadoop可以基于廉价PC机组建庞大的 应用集群。HBase也深度集成了Hadoop的MapReduce计算框架,并且也正在积极 整合Spark。这使得HBase能够很轻松的融入到整个大数据生态圈。
4、HBase的数据是强一致性的,从CAP理论来看,HBase是属于CP的。这种设计 可以让程序员不需要担心脏读、幻读这些事务最终一致性带来的问题。
5、最后最重要的还是HBase的框架性能是足够高效的。HBase的开源社区非常活 跃,他的性能经过很多大型商业产品的验证。Facebook的整个消息流转的基础设施 就构建于HBase之上。
2.数据结构
HBase也可以作为一个数据库使用,但是为了应对海量数据,他存储数据的方式 与我们理解的传统关系型数据库有很大的区别。虽然他也有表、列这样的逻辑结 构,但是整体上,他是以一种k-v键值对的方式来存储数据的。
纵向来看,HBase中的每张表由Rowkey和若干个列族或者称为列簇组成。其中 Rowkey是每一行数据的唯一标识,在对数据进行管理时,必须自行保证Rowkey的 唯一性。接下来HBase依然会以不同的列来管理数据,但是这些列分别归属于不同 的列簇。在HBase中,同一张表的数据,只需要保证列簇是相同的,而列簇下的 列,可以是不相同的。所以由此可以扩展出非常多的列。在HBase中,对于同一张 表,不建议定义过多的列簇,通常不要超过三个。而更多的数据,可以以列的方式 来扩展。
从横向来看,HBase中的记录,会划分为一个一个的Region,存储在不同的 RegionServer上。并且会在不同的RegionServer之前形成备份,以Region为单位 提供了故障后自动恢复的机制。
最后,从整体来看,HBase虽然还是以HDFS作为文件存储,但是他存储的数据不再是简单的文本文件,而是经过HBase优化压缩过的二进制文件,所以他的存储文 件通常是不能够直接查看的。
3.基础架构
其中,
client客户端包含了访问HBase的接口,另外也维护了对应的缓存来加速对 HBase的访问。
RegionServer直接对接用户的读写请求,是真正干活的节点。他会将数据以 StoreFile的形式存储到不同的HDFS目录中。
HMaster主要是维护一些集群的元数据信息,同时监控RegionServer的服务状 态,并且通过Zookeeper提供集群服务,向客户端暴露集群的服务端信息。
二:基本操作
基础指令
数据结构
1> 基础指令
1.使用客户端
#查看HBase基础指令 [root@com hbase-0.98.20-hadoop2]# bin/hbase --help #hbase命令行 [root@com hbase-0.98.20-hadoop2]# bin/hbase shell #查看帮助 hbase:001:0> help #列出已有的表 hbase:002:0> list
2.基础表操作
#创建表 user表,有一个列簇 basicinfo hbase:003:0> create 'user','basicinfo' #插入一条数据 hbase:004:0> put 'user','1001','basicinfo:name','roy' hbase:005:0> put 'user','1001','basicinfo:age',18 hbase:006:0> put 'user','1001','basicinfo:salary',10000 #插入第二条数据 hbase:007:0> put 'user','1002','basicinfo:name','sophia' hbase:008:0> put 'user','1002','basicinfo:sex','female' hbase:009:0> put 'user','1002','basicinfo:job','manager' #插入第三条数据 hbase:005:0> put 'user','1003','basicinfo:name','yula' hbase:006:0> put 'user','1003','basicinfo:school','phz school'
3.数据操作
#按照RowKey,查找单条记录 hbase:007:0> get 'user','1001' hbase:011:0> get 'user','1001','basicinfo:name' #按照版本查询数据,每次put都会给这条数据的VERSIONS+1 hbase:031:0> get 'user','1001',{COLUMN => 'basicinfo:name',VERSIONS=>3} #使用scan,查询多条记录 hbase:008:0> scan 'user' hbase:009:0> scan 'user',{STARTROW => '1001',STOPROW => '1002'}
1、HBase查询数据只能依据Rowkey来进行查询,而Rowkey是由客户 端直接指定的,所以在使用HBase时, Rowkey如何设计非常重要,要 带上重要的业务信息。
2、scan指令后面的查询条件,STARTROW和STOPROW是必须大写 的。查询的结果是左开右闭的。
3、其他查询数据的操作可以使用help 'get' 或者 help 'scan',来查看更 多的查询方式。例如对数据进行过滤
#查看表结构 这个结果很重要。列出了列簇的所有属性。 hbase:012:0> desc 'user' Table user is ENABLED user COLUMN FAMILIES DESCRIPTION {NAME => 'basicinfo', BLOOMFILTER => 'ROW', IN_MEMORY => 'false', VERSIONS => '1', KEEP_DELETED_CELLS => 'FALSE', DATA_BLOCK_ENCODING => 'NONE', COMPRESSION => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', BLOCKCACHE => 'true', BLOCKSIZE => '65536', REPLICATION_SCOPE => '0'} #查询表中的记录数。 hbase:013:0> count 'user' #修改表结构 hbase:019:0> alter 'user',{NAME => 'basicinfo',VERSIONS => 3} hbase:020:0> desc 'user' COLUMN FAMILIES DESCRIPTION {NAME => 'basicinfo', BLOOMFILTER => 'ROW', IN_MEMORY => 'false', VERSIONS => '3', KEEP_DELETED_CELLS => 'FALSE', DATA_BLOCK_ENCODING => 'NONE', COMPRESSION => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', BLOCKCACHE => 'true', BLOCKSIZE => '65536', REPLICATION_SCOPE => '0'} #删除某一列 delete 'user','1002','basicinfo:sex' #删除某一条数据 deleteall 'user','1003' #清空表数据 truncate 'user' #删除表 删除之前需要先disable。 disable 'user' drop 'user'
2> HBase的数据结构
4.数据结构
RowKey:
Rowkey是用来检索记录的唯一主键,类似于Redis中的key。访问HBase中的表 数据,只能通过Rowkey来查找。
访问HBase的数据只有三种方式:
1、通过get指令访问单个rowkey对应的数据。
2、通过scan指令,默认全表扫描
3、通过scan指令,指定rowkey的范围,进行范围查找。
Rowkey可以是任意字符串,最大长度是64KB,实际中通常用不到这么长。
在 HBase内部,Rowkey保存为字节数组。存储时,会按照Rowkey的字典顺序排序存 储。
在实际使用时,对Rowkey的设计是很重要的,往往需要将一些经常读取的重要 列都包含到Rowkey中。并且要充分考虑到排序存储这个特性,让一些相关的数据尽 量放到一起。比如我们创建一个用户表,经常会按用户ID来查询, 那Rowkey中一 定要包含用户ID字段。而如果把用户ID放在Rowkey的开头,那数据就会按照用户 ID排序存储,查询Rowkey时效率就会比较快。
Column Family(列簇) 与 Column(列):
HBase中的列都是归属于某一个列簇的,HBase在表定义中只有对列簇的定义, 没有对列的定义。也就是说,列是可以在列簇下随意扩展的。要访问列,也必须以 列簇作为前缀,使用冒号进行连接。列中的数据是没有类型的, 全部都是以字节码 形式存储。同一个表中,列簇不宜定义过多。
物理上,一个列簇下的所有列都是存储在一起的。由于HBase对于数据的索引和 存储都是在列簇级别进行区分的,所以,通常在使用时,建议一个列簇下的所有列 都有大致相同的数据结构和数据大小,这样可以提高HBase管理数据的效率。
Versions:
在HBase中,是以一个{row,column,version}这样的数据唯一确定一个存储单 元,称为cell。在HBase中,可能存在很多cell有相同的row和column,但是却有不 同的版本。多次使用put指令,指定相同的row和column,就会产生同一个数据的 多个版本。
默认情况下,HBase是在数据写入时以时间戳作为默认的版本,也就是用scan指 令查找数据时看到的timestamp内容。HBase就是以这个时间戳降序排列来存储数 据的,所以,HBase去读取数据时,默认就会返回最近写入的数据。客户端也可以 指定写入数据的版本,并且这个版本并不要求严格递增。 当一个数据有多个版本时,HBase会保证只有最后一个版本的cell数据是可以查询 的,而至于其他的版本,会由HBase提供版本回收机制,在某个时间进行删除。 例如:以下指令可以指定要存储多少个版本
#给basicinfo声明最多保存5个版本 alter 'user',NAME => 'basicinfo',VERSIONS=>5 #指定最少两个版本 alter 'user',NAME => 'basicinfo',MIN_VERSIONS => 2 #查询多个版本的数据。 get 'user','1001',{COLUMN => 'basicinfo:name',VERSIONS => 3} #查询10个历史版本 scan 'user',{RAW => true, VERSIONS => 10}
Namespace 命名空间
在创建表的时候,还可以指定表所属的命名空间。例如
create_namespace 'my_ns' create 'my_ns:my_table','fam' alter_namespace 'my_ns',{METHOD => 'set','PROPERTY_NAME' =>'PROPERTY_VALUE'} list_namespace drop_namespace 'my_ns'
在HBase中,每个命名空间会对应HDFS上的/hbase/data目录下的一个文件夹。
不同命名空间的表存储是隔离的。
在HDFS上可以看到, HBase默认创建了两个命名空间,一个是hbase,这是系 统的命名空间,用来存放HBase的一些内部表。另一个是default,这个是默认的命 名空间。不指定命名空间的表都会创建在这个命名空间下。
三:HBase原理
1.HBase读写框架
1、StoreFile 实际保存数据的物理文件,StoreFile以HFile的形式存储在HDFS上,每个Store 会有一个或多个StoreFile,数据在每个StoreFile内都是有序的。在HDFS /hbase/data/default/user目录下。
2、MemStore 写缓存。由于HFile中的数据要求是有序的,所以数据是先存储在MemStore中, 排好序后,等到达刷写时机才会写入到HFile。每次刷写都会形成一个新的HFile。
3、WAL 由于数据要经过MemStore排序后才能刷写到HFile,但是数据在内存中会有很高 的概率丢失。为了解决这个问题,数据会先写在一个叫做Write-Ahead-logfile的文 件中,然后再写入MemStore中。当系统出现故障时,就可以从这个日志文件进行 重建。
2.HBase写数据流程
图中meta表信息为hbase默认维护的一个表。 可以用scan 'hbase:meta'指令查 看。他维护了系统中所有的Region信息。这个表的HDFS路径会维护在Zookeeper 中。这个表里的info:server字段就保存了Region所在的机器以及端口。
客户端要写入的数据,实际上写入到MenStore就算完成了,HBase会在后续的过程 中定期将MemStore内的数据写入到StoreFile中。在客户端可以通过flush指令手动 触发这一过程。
HBase刷写MemStore的几个时机:
MemStore级别限制
habse.hregion.memstore.flush.size 默认128M。 当Region中任意一个 MemStore的大小达到这个上限,就会触发一次MemStore操作,生成一个 HFile。
Region级别限制:
hbase.hregion.memstore.block.multiplier 默认4. 当Region中所有MemStore 的大小总和达到上限,也会触发MemStore的刷新。这个上限就是 hbase.hregion.memstore.flush.size * habse.hregion.memstore.flush.size
RegionServer级别限制
对整个RegionServer里写入的所有MemStore数据大小,配置了一个低水位阈值 和高水位阈值。 当所有MemStore文件大小达到低水位阈值时,会开始强制执行 flush。并按照MemStore文件从大到小一次刷写。 而当所有MenStore文件大小达到高水位时,就会阻塞所有的写入操作,强制执 行flush。直到总MenStore大小下降到低水位阈值。 涉及到两个参数: hbase.regionserver.global.memstore.size 表示RegionServer的高水位阈值。 默认配置None。分配JVM的Heap堆内存大小的40%(0.4)。 老版本的参数是 hbase.regionserver.global.memstore.upperLimit hbase.regionserver.global.memstore.size.lower.limit 表示RegionServer的低 水位占据高水位阈值的百分比。 默认配置也是None,表示是高水位阈值的95% (0.95)。老版本的参数是hbase.regionserver.global.memstore.lowerLimit
WAL级别限制
hbase.regionserver.maxlogs WAL数量上限。当RegionServer的WAL文件数 量达到这个上下后,系统就会选取最早的Hlog对应的一个或多个Region进行 Flush。这时候会在日志中有一条报警信息 Too many WALs。count=....
定期刷新
MemStore hbase.regionserver.optionalcacheflushinterval 默认是3600000 单位是毫 秒,即1个小时。这是HBase定期刷新MemStore的时间间隔。通常在生产中, 为了尽量保证业务性能会将这个参数配置为0,表示关闭定时自动刷写。 手动调用flush执行
在HBase的刷写机制下,只有RegionServer达到高水位阈值时才会阻塞写入操作, 对业务产生直接影响。其他的几个限制级别都不会产生阻塞,但是通常还是会对性 能产生一定的影响。所以在很多生产系统中,会根据业务的进展情况定制 MemStore文件刷写策略。比如在业务不繁忙的时候进行定期手动刷写。
3.HBase读数据流程
1、Client先访问zookeeper,从meta表读取region的位置,然后读取meta表中的 数据。meta中存储了用户表的region信息。
2、根据namespace、表名和rowkey在meta表中找到对应的region信息。
3、找到这个Region对应的Regionserver
4、读取数据时,会启动多个StoreFileScanner和一个MemStoreScanner,最终的 结果会同时读取内存和磁盘中的数据,并按照数据的版本号也就是时间戳,获取最 新的一条数据返回给客户端。
5、在读取StoreFile时,为了提高读取数据的效率,会构建一个BlockCache作为读 缓存。MemStore和StoreFile中查询到的目标数据,都会先缓存到BlockCache中, 再返回给客户端。
4.HBase文件压缩流程
上面从HBase读写文件的流程简单的推出了一个结论:HBase的写操作会表现得比 读操作更快。但是如果在面试过程中问个为什么,这样简单的流程推导显然无法让 面试官信服。这个时候,就需要更加深入HBase的底层,寻找答案。
4.1 HBase底层的LSM树
HBase的每个Region中存储的是一系列可搜索的键值映射,底层会以LSM树( Log Structured Merge Tree )的结构来对key进行索引。LSM树也是B-树的一种扩 展,很多NoSQL数据库都会采用这种LSM树来存储数据。
LSM树的基础思想是将对数据的修改增量保存在内存中,当内存达到指定大小限 制后,将这些修改操作批量写入磁盘。这样写的性能得到极大的提升,不过读取的 时候就会稍微麻烦一些,需要合并磁盘中的隶属数据和内存中最近修改的操作。 LSM树通过这种机制,将一棵大树拆分成N棵小树,这些小树首先写入内存。随着 小树越来越大,内存中的小树会flush到磁盘中,磁盘中的树再异步定期进行merge 操作,最终将数据合并成一棵大树,以优化读性能。
在HBase中,因为小树要先写入内存,为了防止内存数据丢失,写内存的同时需 要暂时持久化到磁盘(HBase的磁盘对应HDFS上的文件 ),这就对应了HBase的 MemStore和Hlog。MemStore对应一个可变的内存存储,记录了最近的写(put)操 作。当MemStore上的树达到一定大小后,就需要进行flush操作,这样MemStore 就变成了HDFS上的磁盘文件StoreFile。之前介绍过,HBase是将所有数据修改存储 为单独的版本,因此,对同一个key,会有多个版本保留在MemFile和StoreFile 中,这些过时的数据是有冗余的,HBase会定期对StoreFile做merge合并操作,彻 底删除无效的空间,多棵小树在这个时候合并成大树,来增强读性情。
在HBase 2.0版本中,还引入了一个重要的机制IN_MEMORY Compact内存压 缩,来优化LSM中的内存树。内存压缩是HBase2.0中的一个重要特性,通过在内存 中引入LSM结构,减少多余的数据,实现降低flush频率和减少MemFile刷写数据的 效果。
4.2 HBase文件压缩过程
HBase使用LSM树的方式,可以将应用程序级别的随机IO转换成为顺序磁盘IO, 对于写性能的提升非常明显。但是LSM树对读数据的性能影响也是非常大的。所 以,整体上,相比于MySQL的B+树结构,HBase的写性能会比MySQL高很多,同 时读性能又会比MySQL低很多。另外,LSM结构是一种append-only-tree,文件 不支持修改,只支持添加,这也正贴合Hadoop的文件机构,再加上HBase设计 时,所面临的是TB级别的数据量,这种机制基本也就成了必选的方式了。
而HBase也对读操作做了一定的优化。例如,为了加快对MemFile映射的内存做 数据读取,HBase会构建一个布隆过滤器,对内存中的数据进行快速过滤,从而减 少对内存的搜索。这也就对应了desc指令中看到的BLOOMFILTER属性。
在HBase中,flush指令,将内存中的数据刷写到HDFS上。这时,只刷写,不过 滤数据。每次刷写都会在HDFS上新刷写一个文件。
另外compact和major-compact两个指令,会用来将文件进行合并。
这个compact指令是将相邻的部分小文件合并成大文件,并且他不会删除过时的 数据,所以性能消耗不会太大。而major-compact指令是将所有的storefile文件合 并成一个大文件,这时他就会删除过时的数据,就会消耗很多的机器性能。
当我们发送一个delete指令删除一个列时,HBase并不会直接删除数据,而是给 数据添加一个删除标记,这样客户端就查不到当前列的值。而在flush阶段,只会删 除那一列最新版本的数据,但是删除标记同样不会删除,以保证历史的版本不会让 客户端查询出来。compact阶段,由于数据依然没有统一,所以删除标记依然不会 删除,以保证客户端始终查不到历史版本的数据。只到major-compact阶段,数据 全部合并到一个StoreFile中时,才会将历史版本的数据以及删除标记一起删除。
不同版本的数据可以使用下面的指令查看。包含历史版本以及删除标 记。 hbase> scan 't1', {RAW => true, VERSIONS => 10}
HBase提供了按照HFile文件大小以及文件个数,定时触发compact和majorcompact的机制。例如 hbase.hregion.majorcompaction这个参数就用来配置自 动文件压缩的时间间隔
但是这个参数在生产环境一般都是建议设置为0,关闭的。由手动来定时 触发major-compact操作。这是因为文件压缩需要对数据做大量的合并 和删除,会影响线上的性能。所以通过定时脚本保证集群在晚上业务不 太繁忙时进行major-compact。
如果storeFile文件过大,HBase还会有另外的机制将storefile重新拆分成几个大 小合适的文件,即Region,分到不同的RegionServer上。所以整体上,如果HBase 的数据操作频繁, 你可以看到他的文件是分久必合合久必分,经常变来变去的。
四:客户端
1.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)