Hbase 基础知识
一、数据模型
Name Space
命名空间,类似于关系型数据库database的概念,每一个命名空间下面有多个表。
Hbase 有两个自带的命名空间,分别是hbase 和 default ,hbase 中存放的是Hbase 的内置表;default 是用户默认使用的命名空间。
Table
Table 类似于关系型数据库表的概念。与关系型数据库中的表不同的是,Hbase 定义表只需要声明列族即可,不需要声明具体的列。
这就意味着,往Hbase 写入数据时,字段可以动态、按需指定。因此,和关系型数据库相比,Hbase 能够轻松应对字段变更的场景。
Row
Hbase 表中的每行数据都是由一个RowKey 和 多个 Column 组成,数据是按照RowKey 的字段顺序存储的,并且查询时只能根据Rowkey 进行检索,所以RowKey 的设计十分重要。
Column
Hbase 中的每列都是由Column Family ( 列族) 和 Column Qualifiler ( 列限定符) 进行限定。例如 info: name、info: age。
建表时,只需要指明列祖,而列限定符无需预先定义。
Time Stamp
用于标识数据的不同版本(version), 每条数据写入时,系统都会自动为其加上该字段,其值为写入Hbase 的时间。
Cell
由{ rowkey, column Family , column Qualifier , time Stamp } 唯一确定的单元。cell 中的数据没有类型,全部是以字节码的形式存储。
二、Hbase 的基本架构
Region Server
Region Server 作为 Region 的管理者,其主要实现类为HRegionServer , 主要作用如下:
对数据的操作:get, put, delete
对Region 的操作:splitRegion、CompactRegion
Master
Master 作为 Region Server 的管理者,其实现类为HMaster ,主要作用如下:
对表的操作:Create 、delete、alter
对RegionServer 的操作:为RegionServer 分配Region ,监控每个RegionServer 的状态,负责RegionServer 负载均衡和故障转移
ZooKeeper
Hbase 通过ZooKeeper 来做Master 的高可用、RegionServer 的监控、元数据的入口以及集群的配置维护工作。
这使得HMaster 不再是单点故障。可以使用HA通过选举,保证任何时候,集群中只有一个活着的HMaster ,HMaster 与RegionServers 启动时会向ZooKeeper 注册。
HDFS
HDFS 为 Hbase 提供最终的底层的数据存储服务,同时为HBase 提供高可用支持。
三、HBase的详细架构
HMaster :相当于HBase 的大脑,充当Region Server 的管理者。
当HRegionSrever 中存储的数据表过大以后,HMaster 通知HRegionSrever 对表进行切割,实现集群的负载均衡;
当HRegionSrever 故障失效时,HMaster 负责此节点上所有数据的迁移;
同时整个HBase 的数据读写操作都是通过HMaster 进行管理和通知的;
ZooKeeper :实现HBase 的高可用,它保证集群中只有一个HMaster 工作,同时还监视HRegionServer 的工作状态,当HRegionServer 处理客户端的数据读写出现异常时,ZooKeeper 会通知HMaster 进行处理。
HRegionServer :是HBase 的核心组件,负责执行HBase 的所有数据读写操作,HRegionSrever 包含了:HLog 、HRegion 、Store 等组件。
HRegion :是HRegionServer 中存储数据的组件,一张数据表会被周期切分,所以一张完整的数据表可能会对应多个HRegion ,而HRegion 又由多个Store 组成。
Store :Store 也是存储数据的核心组件,它内部包含Mem Store 和StoreFile 两个组件,前者以内存形式存储数据,后者以HDFS文件形式存储数据。
Mem Store :是数据存储的首选方式,当内存空间满了后,HBase 会将内存的数据一次性刷写到HDFS上,以文件形式存储,也就是StoreFile 中,空间大小并不是刷写数据的唯一条件,当数据在内存中存储时间达到设定时间时,HBase 也会进行数据刷写操作。
StoreFile :是存储数据的文件形式,基于HDFS存储,StoreFile 以HFile 的形式存储在HDFS上。每个Store 会有一个或多个StoreFile (HFile ),数据在每个StoreFile 中都是有序(局部有序)的。
Meta 表:用于保存集群中HRegions 的位置信息(region列表)。ZooKeeper 存储着Meta 表的位置。
HLog :保证了HBase 的可靠性,它是记录HRegionSrever 的数据读写等操作的编辑日志,当HRegionSrever 发生故障时,HMaster 接收到ZooKeeper 的通知后,可以通过HLog 对数据进行恢复。
四、Region Server 的架构
MemStore
MemStroe 是存放在内存中。保存修改的数据即 KeyValues 。当MemStore 的大小达到一定的阈值(默认为64 M), MemStore 就会被Flush 到文件中,即生成一个快照文件。目前Hbase 会有一个线程负责MemStore 的flush 操作,即写缓存。
由于HFile 中的数据要求是有序的,所以数据现存储在MemStore 中,排好序后,等到达刷写时机才会刷写到HFile 。每次刷写都会生成一个新的HFile 文件。
StoreFile
MemStore 内存中的数据写入到文件后就是StoreFile [ 即MemStore 每次Flush 操作都会生成一个StoreFile ] ,StoreFile 底层是以Hfile 的格式存储的。每个Store 会有一个或者多个StoreFile [ HFile ] , 数据在StoreFile 中都是有序的
WAL
由于数据要经过MemStore 排序后才能刷写到HFile , 但是把数据保存在内存中会有很高的概率导致数据丢失。
为了解决这个问题,数据会先写入到一个叫Write - Ahead - log 文件中,然后再写入到MemStore 中。所以系统出现故障的时候,数据可以通过日志文件重建。
BlockCache
读缓存,每次查询的数据都会被缓存到BlockCache , 方便下次查询
Region / Store / StoreFile / Hfile 之间的关系
( 1 ) Region
Table 在行的方向上分隔成多个Region , Region 是Hbase 中分布式存储和负载均衡的最小单位,即不同的Region 分别在不同的RegionServer 上,但是同一个Region 是不会被拆分成多个Server 上。
Region 是按照大小分隔的,表中的每一行只能属于一个Region 。随着数据不断插入到表中,Region 不断增大,当region 中某一列族达到一个阈值(默认256 M)时就会被分成两个Region .
( 2 ) Store
一个Region 由一个或者多个Store 组成,至少是一个Store.
Hbase 会把一起访问的数据放到一个Store , 即为每个ColumnFamily 建一个Store [ 即有几个ColumnFamily ,也就有几个Store ]
一个Store 由一个MemStore 和0 或者多个StoreFile .
( 3 ) MemStore
MemStore 存放在内存中,保存修改的数据即KeyValues 。
当MemStore 的大小达到一个阈值时(64 M), MemoStore 会被flush 到一个文件,即生成一个快照,目前Hbase 会有一个线程负责MemStore 的Flush 操作。
( 4 ) StoreFile
MemStore 内存中的数据写入到文件后就是StoreFile [ 即MemStore 每次Flush 操作都会生成一个新的StoreFile ] ,StoreFile 底层是以HFile 的格式存储的。
( 5 ) HFile
HFile 是Hbase 中KeyValue 数据的存储格式,是Hadoop 的二进制文件。一个StoreFile 对应一个HFile , HFile 存储在HDFS 上面。
五、逻辑架构&物理架构
六、读写数据流程
写数据
1 )Client 先从缓存中定位region,如果没有缓存则需访问zookeeper获取 hbase: meta 表位于哪个 Region Server 。
2 )访问对应的 Region Server ,获取 hbase: meta 表,根据读请求的 namespace: table/ rowkey,
查询出目标数据位于哪个 Region Server 中的哪个 Region 中。(找到小于rowkey并且最接近rowkey的startkey对应的region)
并将该 table 的 region 信息以及 meta 表的位置信息缓存在客户端的 meta cache,方便下次访问。
3 )与目标 Region Server 进行通讯;
4 )将数据顺序写入(追加)到 WAL;
5 )将数据写入对应的 MemStore ,数据会在 MemStore 进行排序;
6 )向客户端发送 ack;
7 )等达到 MemStore 的刷写时机后,将数据刷写到 HFile 。
读数据
1 )Client 先从缓存中定位region,如果没有缓存则需访问zookeeper获取 hbase: meta 表位于哪个 Region Server 。
2 )访问对应的 Region Server ,获取 hbase: meta 表,根据读请求的 namespace: table/ rowkey,查询出目标数据位于哪个 Region Server 中的哪个 Region 中。并将该 table 的 region 信息以及 meta 表的位置信息缓存在客户端的 meta cache,方便下次访问。
3 )与目标 Region Server 进行通讯;
4 )分别在 Block Cache (读缓存),MemStore 和 Store File (HFile )中查询目标数据,并将查到的所有数据进行合并。此处所有数据是指同一条数据的不同版本(time stamp)或者不同的类型(Put / Delete )。
5 ) 将从文件中查询到的数据块(Block ,HFile 数据存储单元,默认大小为 64 KB)缓存到Block Cache 。
6 )将合并后的最终结果返回给客户端
七、HLog
HBase 的数据写入并不是直接写入到数据文件中,而是先写入缓存(MemStroe ), 当满足一定的条件下缓存中的数据再会异步刷新到磁盘。
为了防止数据写入缓存之后不会因为RegionServer 进程发生异常而导致数据丢失,在写入缓存之前需要将数据顺序写入到HLog 中。如果RegionServer 发生宕机或者其他的异常,这种设计可以从HLog 中进行日志回放进行数据的补救,保证数据的不丢失。
HLog 的生命周期包括:HLog 构建、HLog 滚动、HLog 失效、HLog 删除
HLog 构建:
Hbase 中,WAL的实现类为HLog 。每一个RegionServer 拥有一个HLog 日志,该RegionServer 中所有Region 的写入都会写入到同一个HLog 。
日志数据的最小单元为< HLogKey , WALEdit > , HLogKey 主要存储了log sequence number[ 日志序列号] , write time [ 写入时间] ,region name, table name[ 表名] 以及cluster ids。
log sequence number是一次行级事务的自增序号,是HLog 的一个重要的元数据,和HLog 的生命周期息息相关。[ 行级事务是什么?简单来说,就是更新一行中的多个列族,多个列,行级事务能够保证这次更新的原子性、一致性、持久性、隔离性。] sequenceid是region级别的自增序号。每个region都维护属于自己的sequenceid,不同region的sequenceid相互独立。
region name 和 table name 分别表示该段日志属于哪个region的哪张表。
cluster ids 用于表示将日志复制到集群中的其他机器上。
WALEdit 用来表示一个事务中的更新集合,一次行级事务可以原子操作同一行中的多个列。上图中WALEdit 包含多个KeyValue 。
HLog 滚动
HBase 后台启动了一个线程会每隔一段时间(由参数’hbase. regionserver. logroll. period’决定,默认1 小时)进行日志滚动, 即新生成一个新的日志文件。
可见,HLog 并不是一个大文件,而是会生成很多的小文件。其原因在于:
随着数据的不断写入,HLog 所占的空间会越来越大,然而很多的日志已经没有任何作用,这一部分数据完全可以被删除掉。删除数据的较好的方式即为一个文件一个文件的删除。因此设计了日志滚动机制。
Hlog 失效
上文提到,很多日志文件失效而被删除掉,并且删除是以文件为单元执行的。
从原理上来说,一旦数据从MemStore 中落盘,对应的日志文件就可以被删除。因此一个文件中所有数据失效,只需要看该文件中所有的Region 对应的最大的Sequenceid 对应的数据是否已经落盘就可以。同时Hbase 在执行Flush 操作时会记录对应的最大Sequenceid , 如果HLog 中最大的SequenceId 小于 FLush 刷新操作对应的SequenceId 就可以认为该文件失效。一旦该文件失效,就可以将该文件从WALS 文件夹移动到OLdWALS .
HLog 删除
HMaster 后台会启动一个线程每隔一段时间(由参数’hbase. master. cleaner. interval’,默认1 分钟)会检查一次OldWALs 下所有的日志文件,确认是否可以被删除。确认之后执行删除操作。
之所以要确认:第一对于使用Hlog 进行主从复制的业务来说,第三步的确认并不是完整的,需要继续确认该HLOG 是否还在应用于主从复制;第二对于没有执行主从复制的业务来讲,HBase 依然提供了一个过期的TTL(由参数’hbase. master. logcleaner. ttl’决定,默认10 分钟)也就是说OldWALs 里面的文件最多依然再保存10 分钟。
八、Region 切分策略
RegionServer 中Region 的数量取决于MemStore 内存的使用,每个Region 拥有一组MemStore [ memStore的数量由Store 决定,Store 的数量由建表时的指定的列族个数决定,即每个Region 的MemStore 的个数= 表的列族的个数] ,可以通过配置来修改MemStore 占用内存的大小,一般配置在128 M- 256 M。
Region 是分布式存储和集群负载均衡的最小单元,一个RegionServer 可以维护多个Region , 但是同一个Region 是不会被拆分成多个Region Server 上。
Region 自动切分是Hbase 能够拥有良好扩张的重要因素之一,也必然是所有分布式系统追求无限扩张的一副良药。
Hbase Region 的拆分策略有比较多,最为常见的Hbase 的切分策略有3 种:ConstantSizeRegionSplitPolicy 、IncreasingToUpperBoundRegionSplitPolicy 、 SteppingSplitPolicy
ConstantSizeRegionSplitPolicy
0.94 版本之前默认的切分策略。这是最容易理解但也是最容易产生误解的切分策略。从字面的意思来看,当Region 大小大于某一阈值(hbase. hregion. max. filesize)之后就会触发切分。实际上并不是这样的,在真正的实现中这个阈值是对于某个Store 来说的。即Region 中最大的Store 大小大于设置的阈值之后才会触发切分。 这里的Store 大小是指压缩后的文件大小。
ConstantSizeRegionSplitPolicy 相对来说最容易想到,但是在生产线上这种切分策略却有相当大的弊端:切分策略对于大表和小表没有明显的区分。
阈值(hbase. hregion. max. filesize)设置较大对大表比较友好,但是小表就有可能不会触发分裂,极端情况下可能就1 个,这对业务来说并不是什么好事。
阈值(hbase. hregion. max. filesize)设置较小对小表比较友好,但一个大表就会在整个集群产生大量的region,这对于集群的管理、资源使用、failover【故障转移】来说都不是一件好事。
IncreasingToUpperBoundRegionSplitPolicy
0.94 版本~ 2.0 版本默认切分策略。这种切分策略微微有些复杂,总体来看和ConstantSizeRegionSplitPolicy 思路相同,一个region中最大store大小大于设置阈值就会触发切分。
但是这个阈值不像ConstantSizeRegionSplitPolicy 是一个固定值,而是会在一定的条件下不断地调整。调整规则和Region 所属的表在当前RegionServer 上的Region 个数有关系。
== == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == =
Math . min ( tableRegionsCount^ 3 * initialSize, defaultRegionMaxFileSize)
tableRegionCount:当前表在所在RegionServer 上拥有的所有的Region 数量的总和;
initialSize:如果定义了hbase. increasing. policy. initial. size,则使用该值,否则用memstore刷写值得2 倍,即hbase. hregion. memstore. flush. size* 2 ;
defaultRegionmaxFileSize: ConstantSizeRegionSplitPolicy 所用到的配置项,也就是Region 的最大大小;
Math . min:取这两个数值的最小值。
当初始hbase. hregion. memstore. flush. size定义为128 M,过程为:
刚开始只有一个Region ,上限为1 ^ 3 * 128 * 2 = 256 M
当有2 个Region ,上限为2 ^ 3 * 128 * 2 = 2048 M
当有3 个Region ,上限为3 ^ 3 * 128 * 2 = 6912 M
以此类推当有4 个Region 时候,为16 G,上限达到了10 GB,最大值就保持在了10 G,Region 数量再增加也不会增加上限
== == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == =
当然阈值并不会无限增大,最大值为用户设置的MaxRegionFileSize 。这种切分策略很好的弥补了ConstantSizeRegionSplitPolicy 的短板,能够适应大表和小表。而且在大集群的条件下对于很多大表来说表现优秀,但是并不完美。
这种切分策略下很多小表在大集群中产生大量的小Region , 分布在整个集群中。并且在Region 发生迁移时可能触发Region 分裂。
SteppingSplitPolicy :
2.0 版本默认切分策略。这种切分策略的切分阈值又发生了变化,相比IncreasingToUpperBoundRegionSplitPolicy 简单了一些。
依然和待分裂Region 所属表当前RegionServer 上的Region 个数有关系。
拆分规则为:
If : region= 1
then: flush size * 2
else : MaxRegionFileSize 。
还是以flushsize为128 M、maxFileSize为10 场景为列,计算出Region 的分裂情况如下:
第一次拆分大小为:2 * 128 M= 256 M
第二次拆分大小为:10 G
从上面的计算我们可以看出,这种策略兼顾了ConstantSizeRegionSplitPolicy 策略和IncreasingToUpperBoundRegionSplitPolicy 策略,对于小表也肯呢个比较好的适配。
Region 切分准备工作-寻找SplitPoint
region切分策略会触发region切分,切分开始之后的第一件事是寻找切分点-splitpoint。
所有默认切分策略,无论是ConstantSizeRegionSplitPolicy 、IncreasingToUpperBoundRegionSplitPolicy 抑或是SteppingSplitPolicy ,对于切分点的定义都是一致的。
切分点是如何定位的呢?整个region中最大store中的最大文件中最中心的一个block的首个rowkey。另外,HBase 还规定,如果定位到的rowkey是整个文件的首个rowkey或者最后一个rowkey的话,就认为没有切分点。
什么情况下会出现没有切分点的场景呢?最常见的就是一个文件只有一个block,执行split的时候就会发现无法切分。
很多新同学在测试split的时候往往都是新建一张新表,然后往新表中插入几条数据并执行一下flush,再执行split,奇迹般地发现数据表并没有真正执行切分。原因就在这里,这个时候仔细的话你翻看debug日志是可以看到这样的日志滴:
Region 核心切分流程
Hbase 将Region 的整个切分过程包装成一个事务,意图能够保证事务切分的原子性。整个事务分裂过程分为3 个阶段:Prepare - Execute - RollBack
Prepare 阶段:在内存中初始化两个子Region , 具体是生成两个HRegionInfo 对象。包含tableName, RegionName , startKey, EndKey 等。同时会生成一个Transaction Journal ( 事务日志) ,这个对象用来记录切分的进展,具体看RollBack 阶段。
Execute 阶段: Region 划分的核心操作
1 、RegionServer 首先将Zk 节点下的/ Region - in- Transaction 中该Region 的状态修改为Splitting
2 、Mater 检测到Zk 节点下的/ Region - in- Transaction 的Region 状态发生改变,修改内存中的Region 状态
3 、在HDFS父存储目录下新建临时文件夹. split, 用于保存split后的子region信息
4 、关闭父Region : 父Region 关闭数据写入并触发flush操作,将写入到Region 的数据全部持久化到磁盘。【此后短时间内客户端落在父region上的请求都会抛出NotServingRegionException 。】
5 、核心分裂步骤:在. /split 文件夹下新建两个子文件夹,称为daughterA, daughterB。并在A 、B 两个文件夹下生成reference 文件,分别指向父Region 中对应的文件。生成Refernece 文件的日志如下:
2017 - 08 - 12 11 : 53 : 38 , 158 DEBUG [ StoreOpener - 0155388346 c3c919d3f05d7188e885e0- 1 ] regionserver. StoreFileInfo: reference 'hdfs://hdfscluster/hbase-rsgroup/data/default/music/0155388346c3c919d3f05d7188e885e0/cf/d24415c4fb44427b8f698143e5c4d9dc.00bb6239169411e4d0ecb6ddfdbacf66' to region = 00 bb6239169411e4d0ecb6ddfdbacf66 hfile= d24415c4fb44427b8f698143e5c4d9dc。
其中reference 的文件名为: d24415c4fb44427b8f698143e5c4d9dc. 00 bb6239169411e4d0ecb6ddfdbacf66。前者为父rigion对应的HFile 文件,后者为父Region 。reference 文件的内容非常简单主要有两个部分组成:其一是切分点SplitKey , 其二是Boolean 类型的变量(True 或者 false )。True 表示该refernece文件引用的是父文件的上半部分,false 引用的是父文件的下部分。
6 、父Region 分裂成两个子Region 后,将daughterA, daughterB拷贝到Hbase 根目录,形成两个新的Region 。
7 、parent region 通知修改Hbase mate表后下线,不在提供服务。下线后的Parent Region 在Mate 表中的信息并不会被立刻删除,而是标注Split 列、offline列为True 。并记录两个子region。
8. 开启daughter A 、daughter B 两个子region。通知修改 hbase. meta 表,正式对外提供服务。
RollBack 阶段:
如果Execute 阶段出现异常,则执行RollBack 。为了实现回滚,整个切分过程被分成很多子阶段。回滚程序会根据当前进程进展到哪个子阶段清理对应的垃圾数据。
九、LSM树详解
LSM 树(log- structured- Merge - Tree )并不是一棵严格意义上的树,它其实是一种存储结构。LSM树的核心特点就是利用顺序写来提高写性能,但是因为分层(此处的分层是指分为内存和文件两个部分)设计稍微会降低读的性能。
LSM 由以下三个重要组成部分:Memtable 、Immutable memtable、bLock cache
MemTable
Memtable 是内存中的数据结构,用于存储最近更新的数据。并按照key有序的组织这些数据。LSM树对于具体如何有序地组织数据并没有明确的数据结构定义。例如Hbase 使跳跃表来保证内存中key的有序。
因为数据暂时保存在内存中,内存并不是可靠存储,如果断电会丢失数据,因此通常会通过WAL【Write - ahead logging,预写式日志】的方式来保证数据的可靠性。
Immutable MemTable
当 MemTable 达到一定大小后,会转化成Immutable MemTable 。Immutable MemTable 是将转MemTable 变为SSTable 的一种中间状态。写操作由新的MemTable 处理,在转存过程中不阻塞数据更新操作。
SSTable ( Sorted String Table )
有序键值对集合,是LSM树组在磁盘中的数据结构。为了加快SSTable 的读取,可以通过建立key的索引以及布隆过滤器来加快key的查找。
这里需要关注一个重点,LSM树 ( Log - Structured - Merge - Tree ) 正如它的名字一样,LSM树会将所有的数据插入、修改、删除等操作记录( 注意是操作记录) 保存在内存之中,当此类操作达到一定的数据量后,再批量地顺序写入到磁盘当中。这与B + 树不同,B + 树数据的更新会直接在原数据所在处修改对应的值。LSM数的数据更新是日志式的,当一条数据更新是直接append一条更新记录完成的。
这样设计的目的就是为了顺序写,不断地将Immutable MemTable flush到持久化存储即可,而不用去修改之前的SSTable 中的key,保证了顺序写。
当MemTable 达到一定大小flush到持久化存储变成SSTable 后,在不同的SSTable 中,可能存在相同Key 的记录,当然最新的那条记录才是准确的。这样设计的虽然大大提高了写性能,但同时也会带来一些问题:
1 )冗余存储,对于某个key,实际上除了最新的那条记录外,其他的记录都是冗余无用的,但是仍然占用了存储空间。因此需要进行Compact 操作( 合并多个SSTable ) 来清除冗余的记录。
2 )读取时需要从最新的倒着查询,直到找到某个key的记录。最坏情况需要查询完所有的SSTable ,这里可以通过前面提到的索引/ 布隆过滤器来优化查找速度。
LSM Compact 策略
在Compact 策略上,主要介绍两种基本的合并策略:size- tiered 和 leveled
在介绍这两种合并策略之前,先介绍三种比较重要的概念,实际上不同的策略主要是围绕着这三种概念进行权衡和取舍
(1 )读放大:读取数据时实际上读取的数据量大于真正的数据量。例如在LSM树中需要先在MemTable 查看当前key 是否存在,不存在则继续在SSTable 中寻找。
(2 )写放大:写入数据时实际上写入的数据量大于真正的数据量。例如在LSM树中写入时可能触发Compact 操作,导致实际写入的数据量远远大于给key的数据量
(3 )空间放大:数据实际占用的磁盘空间比数据真正大小更多。例如冗余存储,对于一个key来说,只有最新的那条记录时有效的,而之前的记录都是可以被清理回收的。
Size - tiered 策略
Size - Tiered 策略保证每层的SSTable 大小相近,同时限制每一层的SSTable 的数量。例如每层限制SSTable 的数量为N , 当每层的SStable 达到N 后,就会触发Compact 操作合并这些SSTable 。并将合并的结果写入到下一层的一个更大的SStable .
由此可以看出,当层数达到一定数量时,最底层的单个SSTable 的大小会变得非常大。并且size- tiered策略会导致空间放大比较严重。
Leveled 策略
Leveled 策略也是采用分层的思想。每一层的总大小固定,从上到下逐渐变大。
leveled会将每一层切分成多个大小相近的SSTable 。这些SSTable 是这一层全局有序的,意味着每一个key在每一层至多只有一条记录,不存在冗余记录。
假设存在以下这样的场景:
1 ) L1的总大小超过L1本身大小限制:
此时L1超过了最大阈值限制
2 ) 此时会从L1中选择至少一个文件,然后把它跟L2有交集的部分( 非常关键) 进行合并。生成的文件会放在L2:
如上图所示,此时L1第二SSTable的key的范围覆盖了L2中前三个SSTable,那么就需要将L1中第二个SSTable与L2中前三个SSTable执行Compact操作。
3) 如果L2合并后的结果仍旧超出L2的阈值大小,需要重复之前的操作 —— 选至少一个文件然后把它合并到下一层:
需要注意的是,多个不相干的合并是可以并发进行的:
leveled策略相较于size-tiered策略来说,每层内key是不会重复的,即使是最坏的情况,除开最底层外,其余层都是重复key,按照相邻层大小比例为10来算,冗余占比也很小。因此空间放大问题得到缓解。
但是写放大问题会更加突出。举一个最坏场景,如果LevelN层某个SSTable的key的范围跨度非常大,覆盖了LevelN+1层所有key的范围,那么进行Compact时将涉及LevelN+1层的全部数据。
十、MemStore Flush
HBase 是基于LSM- Tree 模型,所有数据的更新操作都会首先写入到MemStore 中(同时会顺序写入到Hlog 中)。当达到指定大小后再将这些修改操作批量写入到磁盘,生成一个HFile 文件。这种设计可以极大的提升Hbase 的写性能;
HBase 为了方便按照Rowkey 进行检索,要求HFile 中的数据按照RowKey 进行排序。MemStore 数据在Flush 前会对内存中的数据进行一次排序(快排),将数据有序化。
根据局部性原理,新写入的数据会有很大的概率被读取,因此Hbase 在读取数据的时候会首先检查请求的数据是否在MemStore 【写缓存】,MemStore 未找到的话再去,再去读缓存中查找,读缓存还没有找到,才回到Hfile 文件中查找,最终返回Merged 的一个结果给用户。
MemStore 对Hbase 的写入和 读取性能至关重要,Flush 操作又是MemStore 中最核心的操作。
MemStore Flush 触发条件
Hbase 会在如下几种情况下触发Flush 操作,需要注意的是MemStore 的最小Flush 单元是HRegion 而非单个MemStore 。可想而知,如果一个HRgion 中MemStore 过多,每次Flush 的开销必然会很大。因此我们建议在进行表设计的时候尽量就按少CoulmnFamily 的数量。
1 、MemStore 级别的限制:当Region 中任意一个MemStore 的大小达到了上限 (hbase. hregion. memstore. flush. size,默认128 MB),会触发Memstore 刷新。
2 、Region 级别的限制:当Region 中所有MemStore 的大小总和达到了上限(hbase. hregion. memstore. block. multiplier * hbase. hregion. memstore. flush. size,默认 2 * 128 M = 256 M),会触发memstore刷新。
3 、Region Server 级别的限制:当一个RegionServer 中所有的MemStore 大小总和达到了上限(hbase. regionserver. global. memstore. upperLimit * hbase_heapsize,默认 40 % 的JVM内存使用量),才会触发MemStore 的刷新操作。Flush 顺序是按照MemStore 由大到小执行,先执行MemStore 最大的Region ,再执行大的。直到总体的MemStore 的使用量低于阈值(hbase. regionserver. global. memstore. lowerLimit * hbase_heapsize,默认 38 % 的JVM内存使用量)。
4 、WAl 级别的限制:当一个Region serVer 中Hlog 的数量达到上限(可通过参数hbase. regionserver. maxlogs配置)时,系统会选取最早的一个HLog 对应的一个或者多个Region 进行Flush
5 、Hbase 定期刷新MemStore :默认周期为1 小时,确保MemStore 不会长时间没有持久化。为了避免所有的MemStore 在同一时间都将进行Flush 操作,定期的Flush 操作会有20000 ms左右的随即延迟。
6 、手动执行Flush : 用户可以通过shell 命令 flush 'Table Name' 或者 Flush 'Region name' 分别对一个表或者一个Region 进行flush
Memstore Flush 流程
为了减少Flush 过程对于读写的影响,Hbase 采用类似于两阶段提交的方式,将整个Flush 过程分为三个阶段:
1 、prepare阶段:遍历当前Region 中所有的MemStore ,将MemStore 中当前数据集kvset做一个快照snapshot。然后再先建一个新的KVSet 。后期所有的写入操作都会写入到的新的kvset。
整个Flush 阶段的读取操作首先会遍历kvset和snapshot,如果找不到再会在HFile 中查找。
Prepare 阶段需要加一把updateLock 对写请求进行阻塞,结束之后释放该锁。因为此阶段没有任何的费时操作,因此持锁时间很短。
2 、Flush 阶段:遍历所有的MemStore ,将prepare 阶段生成的snapshot 持久化为临时文件,临时文件是存放在对应 Region 文件夹下面的 . tmp 目录里面。因为这个阶段涉及磁盘IO, 因此相对比较耗时。
3 、commit 阶段:遍历所有的MemStore 将Prepare 阶段生成的临时文件转移到指定的ColumnFamily 目录下,最后清空prepare 阶段生成的snapshot。
十一、StoreFile (HFile) Compaction
Hbase 是一种Log - Structured - Merge - Tree 架构模式,用户数据写入先写入到Wal , 再写入到写缓存。当满足一定条件后缓存数据会执行Flush 操作真正的落盘,生成一个Hfile 文件。随着数据写入的增多,Flush 的次数也会不断增多,进而HFile 数据文件就会越来越多。然而,太多的数据文件会导致数据查询的IO次数增加。Hbase 尝试着不断将这些文件进行合并,这个合并称为Compaction
Compaction 会从一个Region 的一个Store 中选择一些HFile 文件进行合并。需要说明的是,compaction都是以Store 为单位进行的
合并原理如下:先从这些待合并的数据文件中读出KeyValues , 再按照由小到大排序后写入到一个新的文件中。之后这个新生成的文件将取代之前待合并的所有文件对外提供服务。
Hbase 根据合并规模将Compaction 分为两类:MinorCompation 和 MajorCompation
MinorCompaction : 是指选取一些小的,相邻的StoreFile 将它们合并成一个更大的StoreFile 。这个过程不会处理已经被delete或者expired 的cell。一次MinorCompaction 的结果是更少并且更大的StoreFile
MajorCompaction : 是指将 Region 下所有的StoreFile 合并成一个StoreFile 。这个过程会处理三类没有意义的数据:同时删除过期数据、已删除数据(打了Delete 标记的)、版本过大的数据等三类无效数据。一般情况下,Major Compaction 持续时间比较长,整个过程会耗费大量的系统资源,对上层业务有较大的影响。因此线上业务都会关闭自动触发MajorCompaction 功能,改为手动在业务低峰期触发。
Compaction 触发时机:
Hbase 中触发Comapction 的因素有很多,最常见的因素有三种:MemStore Flush , 后台线程周期性检查,手动触发。
1 、MemStore Flush : 应该说Flush 是Compaction 的源头,MemStore Flush 会产生HFile 文件,文件越多就越需要Compact 。Hbase 每次Flush 之后都会对当前Store 中的文件数进行判断,一旦文件数大于配置,就会触发compaction。需要说明的是,compaction都是以Store 为单位进行的,而在Flush 触发条件下,整个Region 的所有Store 都会执行compact,所以会在短时间内执行多次compaction。
2 、后台线程周期性的检查:后台线程CompactionChecker 周期性检查是否需要执行Compaction , 检查周期为hbase. server. thread. wakefrequency * hbase. server. compactchecker. interval. multiplier
hbase. server. thread. wakefrequency:是HBase 服务端线程唤醒时间间隔
hbase. server. compactchecker. interval. multiplier:是compaction操作周期性检查乘数因子
该线程优先检查文件数是否大于配置,一旦大于就会触发ComPaction 。如果不满足,他就会检查是否满足MajorComPaction 条件。简单来说,如果当前Store 中Hfile 最早的更新时间早于某个值MCTime ,就会触发Major Compaction 。MCTime 是一个浮动值,浮动区间为[ 7 - 7 * 0.2 , 7 + 7 * 0.2 ] 。7 为Hbase. Hregion . majorcompaction, 0.2 为Hbase. Hregion . majorcompaction. jitter。可见默认为7 天左右执行一次major compaction。如果想要禁用major compaction,只需要将Hbase. Hregion . majorcompaction设置为0
3 、手动触发:一般来讲,手动触发compaction通常是为了执行major compaction。
原因有三,其一是因为很多业务担心自动major compaction影响读写性能,因此会选择低峰期手动触发;
其二也有可能是用户在执行完alter操作之后希望立刻生效,执行手动触发major compaction;
其三是HBase 管理员发现硬盘容量不够的情况下手动触发major compaction删除大量过期数据;
无论哪种触发动机,一旦手动触发,HBase 会不做很多自动化检查,直接执行合并。
Compaction 过程会有以下作用:
1 、合并文件
2 、清除删除、过期、多余版本的数据
3 、提高读写数据的效率
十二、HFile
Hbase 的rowkey+ column family+ column qualifier+ timestamp+ value是Hfile 中数据的排列依据。
HFile 是Hbase 存储数据的文件组织形式。对数据的检索为dataBlock 级别的,而不是行级别的。所以这种Key 是HFile 内部粗粒度的本地索引主键。
从HBase 开始到现在,HFile 经历了三个版本。其中V2是0.92 引入,V3是0.98 引入。在HFile 实际使用过程中发现V1占用内存比较多,V2对此进行了优化,V3版本基本上和V2相同,只是在Cell 层面添加了Tag 数组的支持。
HFile v1
HFile v1的逻辑数据结构如下图:DataBlock 区域、mateBlock【Bloom Filter 】、Fileinfo 、DataBlockIndex 、MateBlockIndex 、Trailer 六部分组成。
HFile V1 在实际使用过程中发现他占用的内存比较多,并且Bloom Filter 和 Block Index 会变得很大。
Bloom Filter 可以增长到100 MB, 这会在查询时引起性能的问题,因为加载并查询Bloom Filter 。
BlockIndex 在一个RegionServer 中可能会达到的6 GB, RegionServer 在启动时需要加载这些BlockIndex , 因而增加了启动时间。
HFile v2
HFile V2 的逻辑结构如下图所示:
文件主要分为四部分:Scanned Block section、Non - Scanned Block section、Load - on- open Section 、Trailer
1 、Scanned Block Section : 表示顺序扫描Hfile 时所有的数据块都将被读取(dataBlock/ Leaf index block/ Bloom Block )
2 、Non - Scanner Block Section : 表示顺序扫描Hfile 时数据不会被读取,主要包括(Leaf index Block 和 Intermediate Level data index Blocks )两部分。
3 、Load - on- open section : 这部分数据在Hbase 的RegionServer 启动时,需要加载到内存中。包括(FileInfo 、Bloom filter Block 、data Block index和meta Block index)
4 、Trailer : 这部分数据记录了Hfile 的基本信息、各个部分的偏移值和寻址信息。
HFile 在读取数据的时候首先会解析Trailer Block 并加载到内存中,然后再加载Load - On - Open section的数据
HFile V2 的物理结构如下图所示
Hfile 被分割成大小相等的Block , 每个Block 的大小可以在创建表列族的时候通过参数BlockSize = > ‘65535 ’ 进行指定默认为64 K。大号的Block 有利于Scan , 小号的Block 有利于随机查询。
并且所有的Block 都拥有相同的数据结构,Hbase 将Block 抽象为统一的HFileBlock 。HfileBlock 支持两种类型:一种类型支持checkSum, 一种支持。下图选用不支持CheckSum 的HFileBlock 的内部结构。
HFileBlock 主要包括两部分: Blockheader 和 BlockData 。其中BlockHeader 主要用于存储Block 元数据,BlockData 用来存储具体数据。Block 元数据中最为重要的是BlockType 字段,用来标注该Block 块的类型。
Hbase 中定义了8 中BlockType , 每种BlockType 对应的Block 都存储不同的数据类型,有的存储用户数据,有的存储索引数据,有的存储meta元数据。对于任意一种HFileBlock , 都拥有相同结构的BlockHeader ,但是BlockData 结构却不同。
从HFile 层面上将文件切分成多种类型的Block , 下面重点介绍记录Hfile 基本信息的Trailer Block 、存储用户实际数据的DataBlock 、布隆过滤器相关的块。
( 1 ) Trailer Block 主要记录了HFile 的基本信息、各部分的偏移值和寻址信息,HFile 在读取数据的时候首先解析Trailer Block 并加载到内存中,然后进一步加载Load - on- open section 的数据。
具体步骤为:
1 、首先加载Version 版本信息,Hbase version 主要包含MajorVersion 和Minor Version 两部分。前者取决于HFile 的主版本V1, V2, V3; 后者在主版本的基础上决定是否支持一些微小的调整,比如是否支持checkSum。不同的版本决定了使用不同的Reader 对象对Hfile 进行解析
2 、根据Version 信息获取Trailer 长度(不同的version Trailer 长度不同),再根据Trailer 长度加载整个HFileTRailer Block
3 、最后加载Load - on- Open 部分到内存。
( 2 ) Data Block
DataBlock 是Hbase 中数据存储的最小单元。DataBlock 中主要存储用户的KeyValue 数据(KeyValue 后面一般会跟着一个timeStamp, 图中未标出),而KeyVlaue 结构是Hbase 存储的核心,每个数据都是以KeyValue 结构在Hbase 中进行存储。
每个KeyValue 都是由4 部分构成,分别是Key length, Value Length , key 和 value。
其中key length 和 value length 是两个固定长度的数值。
而key是一个复杂的结构,首先是rowkey的长度,接着是rowkey; 然后是ColumnFamily 的长度,再是ColumnFamily ;之后是ColumnQualifier 长度和ColumnQualifier 值。最后是时间戳和KeyType [ keyType 有4 中类型,分别是put, Delete , DeleteColumn 和 DeleteFamily ]
value没有复杂的结构,就是一串纯粹的二进制数据。
( 3 ) 布隆过滤器相关的块 BloomFilter Meta Block & Bloom Block
BloomFilter 对于Hbase 的随机读性能至关重要,对于get操作以及部分的Scan 操作可以剔除掉不会用得到的Hfile 文件,减少实际的IO次数,提高随机读性能。
BloomFilter 的工作原理
BloomFilter 使用位数组来实现过滤,初始状态下位数组的每一位都为0 ,如下图所示
假设此时有一个集合S = { x1, x2. . . xn} , Bloom Filter 会使用K 个独立的hash 函数,分别将集合中的每一个元素映射到{ 1 , . . . m} 的范围。对于任何一个元素,被映射到的数字作为作为对应的位数组的索引,该位会被置为1.
下图中集合S 只有两个元素x和y,分别被3 个hash函数进行映射,映射到的位置分别为(0 ,3 ,6 )和(4 ,7 ,10 ),对应的位会被置为1 :
现在假如要判断另一个元素是否在此S 集合中,只需要将该元素通过这3 个Hash 函数进行映射,并查看对应的位置上是否有0 存在。如果有的话,则表示此元素肯定不存在于该集合中,否则有可能存在。下图所示 Z 肯定不在集合{ x, y} 中
Hbase 中的每一个HFile 都有对应的位数组,KeyValue 在写入到Hfile 会先经过几个Hash 函数的映射,映射后经对应的数组位改为1 。在执行Get 请求之后再进行Hash 映射,如果对应的数组位为0 ,则说明Get 请求查询的数据不在给HFile 中
Bloom Block 用于存储HFile 中位数组的值。可以想象,一个Hfile 文件越大,里面存储的KeyValue 值越多,位数组就会相应越大。一旦太大就不适合加载到内存中。因此HFileV2 在设计上将位数组进行了拆分,拆成多个独立的位数组(根据Key 进行拆分,一部分连续的Key 使用一个位数组)。这样一个Hfile 中就会包含多个位数组,只需要根据Key 进行查询,首先定位到具体的某个位数组,只需要将此位数组加载到内存中进行过滤即可,减少了内存开支。
在结构上每一个位数组对应HFile 中的一个Bloom Block ,为了方便根据key 定位具体加载哪一个位数组,HFile V2 设计了对应的索引 Bloom Index Block 。Bloom Index Block 对应的内存和逻辑结构如下:
Bloom Index Block 结构中totalByteSize表示位数组的bit数;
numChunks表示Bloom Block 的个数;
hashCount表示hash函数的个数;
hashType表示hash函数的类型;
totalKeyCount表示bloom filter当前已经包含的key的数目;
totalMaxKeys表示bloom filter当前最多包含的key的数目;
Bloom Index Entry 对应每一个bloom filter block的索引条目,作为索引分别指向’scanned block section’部分的Bloom Block ,Bloom Block 中就存储了对应的位数组。
Bloom Index Entry 的结构见上图左边所示,BlockOffset 表示对应的Bloom Block 在HFile 中的偏移量,FirstKey 表示对应的BLoomBlock 的第一个Key 。
根据上文所述,一次get请求进来,首先会根据key在所有的索引条目中进行二分查找,找到对应的Bloom Index entry,就可以定位该key对应的位数组,记载到内存就可以进行过滤判断。
十三、HFile索引机制
Hbase 中索引结构根据索引层次不同分为:Single Level 和 mutil- Level 。前者是单层索引,后者是多层索引一般为两级或者三级。
HFile V1 中只有single level 一种索引结构,V2 版本中引入多级索引结构。之所以你引入多级索引结构的原因在于随着Hfile 文件越来越大,DataBlock 越来越多,索引数据也越来越多,无法直接加载到内存中,多级索引可以只加载部分索引,降低内存的使用。
Bloom Filter 内存使用问题是促使V1版本升级到V2版本的一个原因,再加上这个原因,这两个原因就是V1版本升级到V2版本最重要的两个因素。
V2 版本中的索引块分为两大类:Root Index Block 和 NonRoot Index Block , 其中NonRoot Index block 又分为 Intermediate Index Block 和 leaf Index Block 两种。HFile 中索引结构类似于一棵树,Root Index Block 表示索引数根节点,Intermediate Index Block 表示中间节点,Leaf Index block表示叶子节点,叶子节点直接指向实际数据块。
Bloom Block 也需要索引,索引结构实际上就是采用了single- level结构,文中Bloom Index Block 就是一种Root Index Block 。
对于DataBlock , 由于Hfile 刚开始数据量较小,索引采用single- level 结构,只有Root Index 一层索引,直接指向数据块。当数据量慢慢变大,Root Index Block 满了之后,索引就会变成 mutil- level 结构,有一层索引变成两层,根节点指向叶子节点,叶子节点指向实际的数据块。如果数据量再大索引层次就会变成3 层。
Root Index Block
表示索引树根节点索引块,可以作为bloom的直接索引,也可以作为data索引的根索引。而且对于single- level和mutil- level两种索引结构对应的Root Index Block 略有不同
Index Entry 表示具体的索引对象,每个索引对象由3 个字段组成; Block Offset 表示索引指向数据块的偏移量; BlockDataSize 表示索引指向数据块在磁盘上的大小; BlockKey 表示索引指向数据块中的第一个key。
除此之外,还有另外3 个字段用来记录MidKey 的相关信息,MidKey 表示HFile 所有Data Block 中中间的一个Data Block ,用于在对HFile 进行split操作时,快速定位HFile 的中间位置。需要注意的是* * single- level索引结构和mutil- level结构相比,就只缺少MidKey 这三个字段* *
NonRoot Index Block
和Root Index Block 相同,NonRoot Index Block 中最核心的字段也是Index Entry ,用于指向叶子节点块或者数据块。不同的是,NonRoot Index Block 结构中增加了block块的内部索引entry Offset 字段,entry Offset 表示index Entry 在该block中的相对偏移量(相对于第一个index Entry ) ,用于实现block内的二分查找。
mutil- level结构中NonRoot Index Block 作为中间层节点或者叶子节点存在,无论是中间节点还是叶子节点,其都拥有相同的结构,如下图所示:
索引流程
图中上面三层为索引层,在数据量不大的时候只有最上面一层,数据量大了之后开始分裂为多层,最多三层,如图所示。最下面一层为数据层,存储用户的实际keyvalue数据。
基本流程可以表示为:
1. 用户输入rowkey为fb,在root index block中通过二分查找定位到fb在’a’和’m’之间,因此需要访问索引’a’指向的中间节点。因为root index block常驻内存,所以这个过程很快。
2. 将索引’a’指向的中间节点索引块加载到内存,然后通过二分查找定位到fb在index ‘d’和’h’之间,接下来访问索引’d’指向的叶子节点。
3. 同理,将索引’d’指向的中间节点索引块加载到内存,一样通过二分查找定位找到fb在index ‘f’和’g’之间,最后需要访问索引’f’指向的数据块节点。
4. 将索引’f’指向的数据块加载到内存,通过遍历的方式找到对应的keyvalue。
上述流程中因为中间节点、叶子节点和数据块都需要加载到内存,所以io次数正常为3 次。但是实际上HBase 为block提供了缓存机制,可以将频繁使用的block缓存在内存中,可以进一步加快实际读取过程。所以,在HBase 中,通常一次随机读请求最多会产生3 次io,如果数据量小(只有一层索引),数据已经缓存到了内存,就不会产生io。
十四、HBase行级事务模型
事务必须满足ACID属性:
原子性:事务中一系列操作要么全部完成,要么全部不完成。
隔离性:如果有两个事务,运行在相同的时间内,执行相同的功能。事务的隔离性将确保每一事务在系统中认为只有该事务在使用系统。
一致性:事务必须始终保持系统处于一致的状态,不管在任何给定的时间并发事务有多少。
持久性:在事务完成以后,该事务对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。
目前Hbase 只支持行级事务
( 1 ) Hbase 事务原子性保证
Hbase 数据首先写入到WAL,再写入MemStore 。写入MemStore 异常很容易造成回滚。因此保证写入/ 更新的原子性只需要保证写入到WAL的原子性即可。
在Hbase 0.98 之前版本保证Wal 写入的原子性并不是很容易,这是由WAl 的结构决定的。假设一个行级事务更新R 行中的3 列(c1、c2、c3)
之前版本的WAL:
< logseq1- for - edit1> : < KeyValue - for - edit- c1>
< logseq2- for - edit2> : < KeyValue - for - edit- c2>
< logseq3- for - edit3> : < KeyValue - for - edit- c3>
每个KV 都会生成一个WAL单元,这样一个行级事务更新了多少列就会生生成多少个WAL单元。在将这些WAL单元append到日志文件的时候,一旦出现宕机或者其他异常,就会出现部分写入成功部分写入失败,原子性得不到保障。
当前版本的WAL:
< logseq#- for - entire- txn> : < WALEdit - for - entire- txn>
< logseq#- for - entire- txn> : < - 1 , 3 , < Keyvalue - for - edit- c1> , < KeyValue - for - edit- c2> , < KeyValue - for - edit- c3>>
通过这种结构,每个事务只会产生一个WAL单元。这样就可以保证WAL写入时候的原子性。
( 2 ) Hbase 事务隔离性
2.1 如何实现写写并发控制?
实现写写并发其实很简单,只需要在写入(或更新)之前先获取行锁,如果获取不到行锁,则表明已经有其他线程拿了该锁。就需要不断地重试等待,直到其他的线程释放行锁。拿到行锁之后开始写入数据,写入数据之后释放行锁即可。
2.2 如何实现批量写入多行的写写并发?两阶段锁协议
Hbase 支持批量写入,即一个线程同时更新一个Region 中的多行记录。那如何保证当前事务中的批量写入和其他事务中的批量写入的并发控制那?
首先获取所有待写入行记录的行锁;开始执行写入操作;写入完成之后在统一释放所有行记录的行锁
不能更新一行锁定/ 释放一行,这样容易造成多个事务之间的死锁。
十五、META表
META表主要用于存储Region 的分布情况以及每个Region 的详细信息,META表的结构如下图:
META 表的每条ROW记录了Region 信息。
ROWKey 主要由三部分构成:TableName , StartKey , TimeStamp , Row 存储的内容我们又称为Region 的Name 。
然后是表中主要列族:Info , Info 主要包含3 个Column : regionInfo, Server , ServerStartCode 。
RegionInfo 就是Region 的详细信息,包括StartKey , EndKey 以及每个Family 的信息等等。Server 存储的是管理这个Region 的RegionServer 地址。
当Region 被拆分、合并、重新分配时,都需要修改这张表的内容。
假设Hbase 中只有两张用户表,Table1 和 Table2 。Table1 非常大,被划分成很多个Region 。因此在Meta 表中有很多条Row 来记录这些Region 。而Table2 很小,只是被划分成两个Region ,因此在META 表中只有两条Row 来记录
假设我们要在Table2 中查询一条RowKey 为RK10000的记录。那么我们需要遵循一下步骤
1. 从meta表里面查询哪个Region 包括这条数据。
2. 获取管理这个Region 的RegionServer 地址
3. 连接这个RegionServer ,查到这条数据
为了知道哪个RegionServer 管理了Meta 表,我们把管理Meta 表的RegionServer 地址放到ZK 上面即可。
0.96 版本之前考虑meta表过大,可能需要将meta 划分成多个Region . 这就意味着可能需要多个RegionServer 管理Meta 表,因此需要两一个表来记录Meta 表Region 的详细信息,即引入Root 表,Root 表的结构和Meta 表结构基本一致。最后由ZK 存储Root 表的位置信息。
所有客户端访问用户数据前,首先需要访问ZK获取root 表的位置,然后访问Root 表获取Meta 表的位置。最后通过Meta 表的信息确定用户数据的存放位置。
在0.98 版本后,hbase: mata表不在split,只有一个region,也去除掉了hbase: root表,client的访问过程也不用进行这一步。
舍弃Root 表的原因在于:
一条HBase 的元数据信息大于在100 字节
Block 的默认大小为128 M= 134217728 B
一个BLock 大约存储134 W 条元数据
一个表最多也就3 - 5 元数据。
也就是一个BLock 能存储26 W 个表。
一个项目再复杂,表的个数一般不会过百,所以Root 表存在没有意义
十六、Hbase 故障恢复之RegionServer 宕机恢复
HBase 故障恢复我们以RegionServer 宕机恢复为例,引起RegionServer 宕机的原因各种各样,例如:GC导致、网络异常、DataNode 异常。
上述这些场景中一旦RegionServer 发生故障,Hbase 都会马上检测到这种宕机。并且在检测到宕机之后将宕机的RegionServer 上的所有Region 重新分配到其他正常的RegionServer 上去。再根据HLog 进行数据丢失恢复,恢复完成之后即可对外提供服务。整个过程完全自动完成,无需人工介入。
Hbase 检测宕机是通过ZooKeeper 实现的,正常情况下RegionServer 会周期性向ZooKeeper 发送心跳,一旦发生宕机,心跳就会停止,超过一定的时间ZooKeeper 就会认为RegionServer 发生了宕机,并将消息通知发送给Master 。
根据HLog 数据丢失恢复,需要对HLog 进行划分。在0.98 版本中一台RegionServer 只有一个Hlog ,即该台Regionserver 的所有Region 日志都混合写入到HLog 中。然而日志回放是以Region 为单位的,一个Region 一个Region 的回放,因此在回放前需要对HLog 按照Region 进行分组。将每个Region 的日志数据都放到一起,方便后面按照Region 进行数据的回放。这个按照Region 对日志进行分组称之为HLog 切分。
根据实现方式不同,Hbase 的故障恢复先后经历了三种不同的模式。
1 、Log Splitting :整个过程都是由Master 控制,效率低下
a. 将待切分的日志文件夹重命名,防止RegionServer 未发生真的宕机而持续的向Hlog 写入日志。
b. Master 启动一个进程依次读取出每个Hlog 中所有的< HLogKey , WALEdit > ,根据HLogKey 所属的Region 不同写入到不同的内存Buffer 中。这样整个Hlog 所有数据会被完整Group 到不同的BUffer 中。
c. 每个Buffer 会对应启动一个写线程,负责将Buffer 中的数据写入到HDFS 中,等到Region 重新分配到其他RegionServer 后按顺序回放对应的REgion 的日志数据。
2 、Distributed Log Spliting
Distributed Log Spliting 是 Log Splitting 的分布式实现,它借助Master 和所有的region Server 的计算能力进行Hlog 的日志切分。其中Master 作为协作者,RegionServer 作为主要实际工作者。
a. Master 会将带切分的日志路径发布到ZooKeeper 节点(/ Hbase / splitWal), 每一个日志作为一个任务,都有对应的任务状态Task_UnAssgined 。
b. RegionServer 启动后都会注册在这个ZK节点等待任务,一旦Master 发布任务之后,RegionServer 就会抢占该任务
c. 抢占任务实际上首先查看任务的状态,如果zk节点任务是Task_UnAssigned 状态,表明当前任务没有人占用,此时就会修改zk节点任务状态为Task_Owned 。如果修改失败,则表明其他的RegionServer 也在抢占,修改成功表明任务抢占成功。
d. RegionServer 取得任务后会让对应的HLogSplitter 线程处理Hlog 的切分,切分的时候读取出Hlog 的对,然后写入不同的Region buffer的内存中;
RegionServer 启动对应写线程,将Region buffer的数据写入到HDFS中,路径为/ hbase/ table/ region/ seqenceid. temp,seqenceid是一个日志中该Region 对应的最大sequenceid,如果日志切分成功,而RegionServer 会将对应的ZK节点的任务修改为TASK_DONE,如果切分失败,则会将任务修改为TASK_ERR。
e. Master 一直会监听ZK节点,一旦发生任务状态修改就会得到通知。如果任务状态修改为Task_Err , Master 就会重新发布任务。如果任务状态修改为Task_DONe , Master 就会将对应的节点删除。
f、Master 重新将宕机的RegionServer 中的Rgion 分配到正常的RegionServer 中,对应的RegionServer 读取Region 的数据,将该region目录下的一系列的seqenceid. temp进行从小到大进行重放,从而实现对应Region 数据的恢复。
这种Distributed Log Splitting 方式在很大程度上加快了故障恢复的进程,正常的故障恢复可以降低到分钟级别。然而这种方式会产生大量的小文件,产生的小文件数量为M * N 。其中M 为待切分的HLog 数量, N 是一个宕机的Region Server 上的Region 数量。
3 、Distributed Log RePlay
Distributed Log RePlay 和 DisTributed Log Splitting 不同的是先将宕机的RegionServer 上的Region 分配给正常的RegionServer , 并将该Region 标记为ReCovering 。
在使用DisTributed Log Splitting 的方式进行HLog 的划分,不同的是,RegionServer 将Hlog 切分到对应的Buffer 之后,并不写入到文件中。而是直接进行重放。这样可以大大减少了文件数量,减少了IO消耗。
HBase 面试知识点
一、简单介绍一下HBase
HBase 概述
Hbase 是建立在HDFS之上的分布式NoSQL 数据库;
适合对于海量数据进行实时的随机读写;
一张HBase 表能够支撑数亿行,数百万列;
数据模型
NameSpace 【命名空间】:类似于关系型数据库得database 概念,每一个命名空间下可以有多个表。Hbase 自带了两个命名空间:hbase 和 default 。hbase 主要用于存储hbase的内置表;default 是用户默认使用命名空间。
table: 类似于关系型数据库表的概念。与关系型数据库表不同的是,Hbase 定义表时只需要声明列族即可,无需声明具体的列。因此往Hbase 写入数据时,字段可以动态、按需指定。与关系型数据库相比,hbase 能够轻松适应字段变更的情景。
Row : Hbase 表的每行数据都由一个RowKey 和 多个ColumnFamily 构成,并且数据的存储是按照RowKey 顺序进行存储的,查询时也是根据RowKey 进行检索,所以Rowkey 的设计非常重要。
Column : HBase 表的每列都是由一个ColumnFamily 和 多个Column Qualifier ( 列限定符) ,建表时,只需要指定列祖,列限定符无需指定。
TimeStamp : 用于标识数据的不同版本,每条数据写入时,系统都会自动为其加上该字段,值为写入Hbase 的时间。
Cell : 由{ RowKey , ColumnFamily , ColumnQualifier , timeStamp} 唯一确定的单元。Cell 数据没有类型,全部以字节码的形式存储
基础架构
(1 )HMaster :相当于HBase 的大脑,充当Region Server 的管理者。
当HRegionSrever 中存储的数据表过大以后,HMaster 通知HRegionSrever 对表进行切割,实现集群的负载均衡;
当HRegionSrever 故障失效时,HMaster 负责此节点上所有数据的迁移。
同时为RegionServer 分配Region 。
同时整个HBase 的数据读写操作都是通过HMaster 进行管理和通知的;
(2 )RegionServer : 是Hbase 的核心组件,负责执行Hbase 所有数据的读写操作。由WAL、Region 、BlockCache 等多个组件构成
Region : 是RegionServer 中数据存储的组件,Table 在行的方向上分隔成多个Region , Region 是Hbase 中分布式存储和负载均衡的最小单位。他们可以分布在多个或者一个RegionServer 上,Region 由一个或者多个Store 组成[ 每一个ColumnFamily 建一个Store ] 。
Store : 其内部包含MemStore 和 StoreFile 两个组件,前者以内存的形式存储数据,后者以文件的形式存储数据。
MemStore :俗称写缓存,存放在内存中,用于保存修改的数据。当MemStore 的大小达到一定阈值默认为64 M, 就会被Flush 到HFile 中。由于HFile 要求数据是有序的,所以需要在MemStore 中对Key - value数据进行排序,等待刷写时机进行刷写到Hfile 。每次刷写都会生成一个Hfile 文件。这样能充分利用hadoop写入大文件的性能优势,提高写入性能。
StoreFile : MemStore 中的数据写入到文件后就是StoreFile 。StoreFile 的底层以Hfile 的形式进行存储。
WAL: Write Ahead Log 预写日志。由于数据先存放在MemStore 中,这有很高的概率导致数据丢失。为了解决这一问题,数据先写入到WAL文件中,再写入到MemStore 。当系统出现故障时,可利用日志文件进行重建。
BlockCache : 俗称读缓存,每次查询的数据都会被缓存到BlockCache 中,方便下一次查询。
(3 )ZK: 作为HBase Master 的HA解决方案, 即保证集群中只有一个Master 处于工作状态。同时还监控RegionServer 的工作状态,当RegionServer 出现异常时通知Master 进行处理。
二、为什么要使用HBase
(1 )大:Hbase 可存储大量的数据, Hbase 可存储PB级数据,支持数亿行,上百万列;
(2 )面向列存储:Hbase 的表由列族构成,以列为存储单位。
(3 )无模式:每一行由一个可排序的RowKey 和任意多列构成,列可以按需求动态增加。
(4 )稀疏:空列并不占用资源,表可以设计的非常稀疏。
(5 )数据类型单一:Hbase 中的数据都是字符串,没有类型。
(6 )数据多版本:每个单元中的数据可以有多个版本,默认情况下版本号自动分配,是单元格插入时的时间戳;
三、HBase 适用于什么场景
1 、半结构化或者非结构化数据:对于数据结构不确定或者杂乱无章很难按照统一的概念进行抽取数据就可以使用HBase
2 、记录非常稀疏:关系型数据库表中的行有多少列都是固定的,为Null 的列浪费了存储空间。但是Hbase 为Null 的列不会被存储,这样既可以节省空间又可以提升读性能。
3 、多版本数据:根据RoWkey 和ColumnFamily 以及ColumnQualifications 定位到的Value 可以有任意数量的版本值。因此对于需要存储变动历史记录的数据,Hbase 就非常方便。
4 、超大量数据存储:Hbase 可存储大量的数据, Hbase 可存储PB级数据,支持数亿行,上百万列;
四、HBase和关系型数据库的区别
1 、关系型数据库采用关系模型,数据以表的形式存在,数据类型和存储方式多样化,且操作复杂。
HBase 中的数据以Region 形式存在,每个Region 中包含多个列族,将数据存储为未经解释的字符串,没有复杂的表间关系。只有简单的添加查询等,不支持join操作
2 、存储模式:Hbase 基于列存储,每个列族有几个文件构成,不同列族分开保存。
3 、数据索引:关系型数据库针对不同的列构建多个索引,HBase 只有一个索引-- -- 行键,所有访问都通过行键进行访问或扫描。
五、HBase和Hive的区别?
六、HBase 表的RowKey 设计原理
1 、RowKey 长度原则:RowKey 是二进制流,开发者建议长度为10 - 100 字节,不过建议越短越好,最好不超过16 字节。
原因在于:
数据的持久化文件HFile 是按照KeyValue 存储的,如果RowKey 太长的话就会影响HFile 的存储效率。
MemStore 将缓存数据到内存,如果RowKey 字段过长,则造成内存的有效利用率降低,系统将无法缓存更多地数据,降低了检索的效率。
目前的操作系统都是64 位系统,内存8 字节对齐。控制在16 个字节,8 字节的整数倍利用了系统的最佳特性。
2 、RowKey 散列原则
如果RowKey 按照时间戳递增,不要将时间放在二进制码前面。建议将RowKey 的高位作为散列字段,由程序循环生成;低位放时间字段。这样将提高数据均衡分布每个Regionserver ,实现RegionServer 负载均衡的几率。
如果没有散列字段,首字段直接是时间就会产生所有的新数据都在一个RegionServer 上堆积的热点问题。这样在查询时负载将集中在个别RegionServer 上,降低了查询效率。
3 、唯一性原则
必须在设计上保证RowKey 的唯一性。
七、如何避免HBase 行键热点问题
HBase 热点现象:检索Hbase 记录要通过RowKey 来定位数据行,当大量的客户端请求集中访问集群中的一个或少数据几个节点时,就会造成这些节点的负载较大。最终导致单个主机的负载过大,引起性能的下降甚至是Region 不可用。
热点产生的原因: 有大量连续编号的RowKey 集中在个别Region 中
避免方法:
1 、在RowKey 前面加上随机数,此方法适用于Hbase 作为海量数据存储但是不频繁查询的业务场景。
2 、Hash 散列,将数据打乱
3 、将RowKey 字段和时间戳反转。
八、Hbase 的查询方式及异同
1 、全表查询: scan tableName
2 、基于rowkey的单行查询: get tableName, '1'
3 、基于RowKey 的范围查询:scan tableName, { STARTROW= > '1' , STOPROW<= '2' }
Get 和 Scan 方法
1 、按指定的RowKey 获取唯一的一条数据,使用get方法。get方法分为两种:分别是设置了closestRowBefore 和 没有设置 ClosestRowBefore 的rowlock。主要是用来保证行的事务性。即每一个get是以一个Row 来标记的,一个Row 可以有多个Family 和 Column 。
2 、按指定条件获取一批记录,scan实现按条件查询。
scan 可以通过SetCaching 和SetBatch 方法提高速度(空间换时间)
scan 可以通过setStartRow 和 setEndRow 来限定范围,startRow是闭区间,endRow是开区间,范围越小,性能越高。
scan 可以通过setFilter方法添加过滤器,这也是分页、多条件查询的基础。
九、请描述HBase 中Scan 对象的SetChache 和 setBatch的方法使用
SetCache 用于设置缓存,即设置一次RPC请求可以返回多少行数据。对于缓存操作来说,如果返回的行数太多,可能会造成内存溢出。这个时候就需要使用SetBatch 。
SetBatch 用于设置批处理,设置这个之后客户端可以选择返回的列数。如果一行中包括的列数超过了设置值,那么就会将该列分片。例如,如果一行中有17 列,batch 设置为5 的话,返回4 组分别四5 ,5 ,5 ,2 。
Cache 设置了服务器一次返回的行数,Batch 设置了服务器一次返回的列数,只针对一行数据而言。
RPC请求次数= (行数* 每行列数)/ MIN ( 每行列数,批大小) / 扫描器缓存
十、Hbase 的Cell 结构
Hbase 通过Row 和Column 确定一个存储单元,称为Cell
Cell 由{ RowKey , ColumnFamily , ColumnQualifier , Version } 构成,cell 中的数据是没有类型的,全部是字节码形式存贮。
十一、Hbase 的Compact 机制
在Hbase 中每当MemStore 的数据被Flush 到磁盘之后,就会生成一个StoreFile 文件。当StoreFile 文件数量达到一定程度之后,就需要对这些文件进行合并。
ComPact 的作用就是:合并文件;清理过期,多余版本数据;提高读写效率。
Hbase 中实现了两种Compact 方式:Minor 和 Major
Minor 操作只用来做部分文件的合并操作,不做任何删除数据、多版本数据的清理工作。
major操作是对Region 下的HStore 下的所有StoreFile 执行合并操作,最终的结果是整理合并出一个文件,会清理过期,多余版本数据。
十二、每天百亿的数据存入Hbase,如何保证数据的存储正确和在规定的时间里全部录入完毕
需求分析
1 ) 百亿数据:证明数据量非常大;
2 ) 存入Hbase : 证明与Hbase 的写入数据有关
3 ) 保证数据的正确性:要设计正确的数据结构保证正确性
4 ) 在规定的时间内完成:对存入速度有要求
结局思路:
1 )百亿数据意味着,假设一整天60 * 60 * 24 = 86400 秒都在写入数据,则每秒数据的写入量高达100 万条。Hbase 当然不支持每秒百万条的数据写入。所以对于百万条的数据写入不可能通过实时写入,只能通过批量导入。批量导入推荐使用BulkLoad , 性能是普通写入的几倍以上。
2 )存入Hbase : 普通写入是使用JAVA API 的PUT 来实现的,批量导入推荐使用BULKLoad ;
3 ) 保证数据的正确性:这里考虑RowKey 的设计,预建分区 和 列族设计。
4 )在规定时间内完成也就意味着存入速度不能过慢,当然越快越好,使用BulkLoad 。
BulkLoad 方法能够将数据快速的load到HBase 中,打一个“生动”的比方:
使用API就好比将饭一口一口喂给HBase ,而使用BulkLoad 就相当于切开HBase 的肚子直接将食物放到胃中
*十三、Hbase 优化的方法
1 ) 减少调整:Hbase 可以调整Region 和 Hfile 。
Region : 如果没有预建分区的话,随着Region 中数据条目的增加,Region 就会分裂,这将增加IO开销。解决方法是根据RowKey 的设计进行预建分区,减少Region 的动态分裂。
HFile : HFile 会随着MemStore 进行刷新时生成一个HFile , 当HFile 达到了一定量的时候,会将属于一个Region 的Hfile 进行合并。如果合并后的HFile 大小大于预设值,那么Hfile 就会进行重新分裂,这样就会导致IO的开销增大。为了减少这样的无谓的I / O 开销,建议估计项目数据量大小,给HFile 设定一个合适的值。
2 )减少启动:
关闭自动Compaction , 在闲时进行手动合并:对于Hbase 会有Compact 机制,会合并HFile ,大量的HFile 合并肯定会带来IO开销。因此建议关闭自动ComPact ,在闲时进行手动合并。
批量数据写入时采用BulKLoad
3 ) 减少数据量:
开启过滤,提高查询速度:开启Bloom Filter , 这是列族级别的过滤,在生成StoreFile 文件时,同时会生成一个MetaBlock ,用于查询时过滤)
使用压缩:一般使用Snappy 和 LZO 压缩
4 ) 合理设计:
rowKey设计应该具备以下属性:
散列性:建议将RowKey 的高位作为散列字段,由程序循环生成;低位放时间字段。这样将提高数据均衡分布每个Regionserver ,实现RegionServer 负载均衡的几率。
简短性:数据的持久化文件HFile 是按照KeyValue 存储的,如果RowKey 太长的话就会影响HFile 的存储效率。
唯一性:必须在设计上保证RowKey 的唯一性。
业务性:如果我们的查询条件比较多,而且不是针对列的条件。那么Rowkey 的设计应该支持多条件查询。如果我的查询要求是最近插入的数据优先,那么rowKey则可以采用叫上Long. Max - 时间戳的方式,这样rowKey就是递减排列。
列族的设计:列族的设计需要看应用场景
优势:Hbase 中数据是按照列进行存储的,那么查询某一列族的某一列时就不需要扫描全表,只需要扫描某一列族即可,减少了读IO
劣势:降低了写IO的性能。原因同一个Region 中存在多个列族,则存在多个Store 。每个Store 都是一个MemStore . 当MemStore 进行Flush 时,属于同一个Region 的Store 中的MemStore 都会进行Flash ,增加了IO开销
十四、Region 如何预建分区
预建分区的目的主要是创建表的时候提前规划的表有多少分区,以及每个分区的区间范围。这样在存储的时候RowKey 可以按照区间进行存储,减少Rigion 分裂。
方案一:shell 方法,create 'tb_splits' , { NAME = > 'cf' , VERSIONS= > 3 } , { SPLITS = > [ '10' , '20' , '30' ] }
方案二:JAVA 程序控制,HBaseAdmin . createTable ( HTableDescriptor tableDescriptor, byte [ ] [ ] splitkeys) 可以指定预分区的splitKey,即是指定region间的rowkey临界值。
十五、RegionServer 宕机如何处理
1 、ZK会监控RegionServer 的上下线情况,当ZK发现某个Regionserver 宕机之后会通知Master 进行故障恢复
2 、Hmaster 会将该RegionServer 所负责的Region 转移到其他的RegionServer 上,并对RegionServer 上存在MemStore 中还未持久化到磁盘的数据进行恢复。
3 、恢复过程通过WAL的重播来完成,Wal 时一个文件,存在于/ Hbase / WAL/ 对应的RegionServer 下;发生宕机时,读取该RegionServer 所对应的路径下的Wal ,并"根据不同的Region 切分成不同的临时文件recover.edits" ;当region 被分配到新的Regionserver 中,RegionServer 读取Region 时判断是否存在recover. edits,如果有则进行恢复。
十六、Hbase 读写流程
读数据
1 )Client 先从缓存中定位region,如果没有缓存则需访问zookeeper获取 hbase: meta 表位于哪个 Region Server 。
2 )访问对应的 Region Server ,获取 hbase: meta 表,根据读请求的 namespace: table/ rowkey,查询出目标数据位于哪个 Region Server 中的哪个 Region 中。并将该 table 的 region 信息以及 meta 表的位置信息缓存在客户端的 meta cache,方便下次访问。
3 )与目标 Region Server 进行通讯;
4 )分别在 Block Cache (读缓存),MemStore 和 Store File (HFile )中查询目标数据,并将查到的所有数据进行合并。此处所有数据是指同一条数据的不同版本(time stamp)或者不同的类型(Put / Delete )。
5 ) 将从文件中查询到的数据块(Block ,HFile 数据存储单元,默认大小为 64 KB)缓存到Block Cache 。
6 )将合并后的最终结果返回给客户端。
写数据
1 )Client 先从缓存中定位region,如果没有缓存则需访问zookeeper获取 hbase: meta 表位于哪个 Region Server 。
2 )访问对应的 Region Server ,获取 hbase: meta 表,根据读请求的 namespace: table/ rowkey,
查询出目标数据位于哪个 Region Server 中的哪个 Region 中。(找到小于rowkey并且最接近rowkey的startkey对应的region)
并将该 table 的 region 信息以及 meta 表的位置信息缓存在客户端的 meta cache,方便下次访问。
3 )与目标 Region Server 进行通讯;
4 )将数据顺序写入(追加)到 WAL;
5 )将数据写入对应的 MemStore ,数据会在 MemStore 进行排序;
6 )向客户端发送 ack;
7 )等达到 MemStore 的刷写时机后,将数据刷写到 HFile 。
*十七、HBase内部机制是什么?
物理存储:hbase的持久化数据是将数据存储在HDFS上。
存储管理:一个表是划分为很多region的,这些region分布式地存放在很多regionserver上Region 内部还可以划分为store,store内部有memstore和storefile。
版本管理:hbase中的数据更新本质上是不断追加新的版本,通过compact操作来做版本间的文件合并。
集群管理:ZooKeeper + HMaster + HRegionServer 。
十八、Hbase中的memstore是用来做什么的?
MemStore :俗称写缓存,存放在内存中,用于保存修改的数据。当MemStore 的大小达到一定阈值默认为64 M, 就会被Flush 到HFile 中。由于HFile 要求数据是有序的,所以需要在MemStore 中对Key - value数据进行排序,等待刷写时机进行刷写到Hfile 。每次刷写都会生成一个Hfile 文件。这样能充分利用hadoop写入大文件的性能优势,提高写入性能。
十九、HBase在进行模型设计时重点在什么地方?一张表中定义多少个Column Family最合适?为什么?
设计的重点为ColumnFamily. Column Family 的个数具体看表的数据,一般来说划分标准是根据数据访问频度,如一张表里有些列访问相对频繁,而另一些列访问很少,这时可以把这张表划分成两个列族,分开存储,提高访问效率。
二十、如何提高Hbase 客户端读写性能
1 、开启Bloom 过滤器,开启Bloom filter 比没开启快3 ,4 倍
Bloom Filter : 主要功能是提高随机读的性能。
存储开销:"bloom filter的数据存在StoreFile的meta中" ,"一旦写入无法更新,由于StoreFile是不可变的" 。"Bloomfilter是一个列族(cf)级别的配置属性" ,假设你在表中设置了Bloomfilter ,那么HBase 会在生成StoreFile 时包括一份bloomfilter结构的数据,称其为MetaBlock ;MetaBlock 与DataBlock (真实的KeyValue 数据)一起由LRUBlockCache 维护。所以,开启"bloomfilter会有一定的存储及内存cache开销" 。
2 、Hbase 对于内存有特别的要求,在硬件允许的情况下分配足够多的内存。
3 、通过修改hbase- env. sh中的 export HBASE_HEAPSIZE= 3000 #这里默认为1000 m
4 、增大RPC数量,通过修改hbase- site. xml 中的hbase. regionservder. handler. count 属性,可以适当放大RPC 数量。
二十一、直接将时间戳作为行健,在写入单个region 时候会发生热点问题,为什么呢?
region中数据是按照rowkey有序存储,若时间比较集中。就会存储到一个region中,这样一个region的数据变多,其它的region数据很少,加载数据就会很慢,直到region分裂,此问题才会得到缓解。
二十三、Hbase 的过滤器
过滤器分为两大类: 比较过滤器和专用过滤器
比较过滤器: 行键过滤器、列族过滤器、列过滤器、值过滤器
专用过滤器: 单列值过滤器 SingleColumnValueFilter -- -- 会返回满足条件的整行
单列值排除器 SingleColumnValueExcludeFilter -- -- - 返回排除了该列的结果 与上面的结果相反