HBase
存储原理、读写原理以及flush
和合并过程HBase
存储原理(架构)HBase
依赖于Zookeeper
和Hadoop
的,所以在启动HBase
前需要启动Zookeeper
和Hadoop
。
HMaster
用于管理整个HBase
集群,即管理每个HRegionServer
,它掌握着整个集群的元数据信息,同时会将相应的数据存储到Zookeeper
(元数据信息、高可用信息等)。
HMaster
的职责:1)
管理用户对Table
的增、删、改、查操作;2)
记录region
在哪台Hregion server
上;3)
在Region Split
后,负责新Region
的分配;4)
新机器加入时,管理HRegion Server
的负载均衡,调整Region
分布;5)
在HRegion Server
宕机后,负责失效HRegion Server
上的Regions
迁移。
HRegionServer
是每台机器上的一个Java
进程(一台机器只有一个HRegionServer
进程),用来处理客户端的读写请求(和Hadoop
的DataNode
类似),并维护着元数据信息。
HRegionServer
的职责:1)
HRegion Server
主要负责响应用户I/O
请求,向HDFS
文件系统中读写数据,是HBASE
中最核心的模块。2)
HRegion Server
管理了很多table
的分区,也就是region
。
每个HRegionServer
有一个HLog
(有且仅有一个)。HLog
是操作日志,用来做灾难恢复的,当客户端发起一个写请求时,会先往HLog
中写再往Memory Store
中写。假设没有HLog
,我们进行一个写请求,会首先写到Memory Store
上,等到Memory Store
到达一定容量后,才会flush
到StoreFile
中。但是如果在这之前主机断电了呢?那这部分操作的数据全丢失了。这显然不是我们想到的结果,于是就有了HLog
。
每个HRegionServer
里面有多个HRegion
,一个HRegion
对应于HBase
的一张表(也可能是表的一部分,因为表太大了会切分,表和HRegion
的对应关系是一对多),当这张表到一定大小的时候会进行切分,切分成两个HRegion
,切分出来的新的HRegion
会保存到另一台机器上。每个HRegionServer
里面有多个HRegion
,可以理解为有多张表。
每个HRegion
里面有多个Store
(一张表中有多个列族),一个Store
对应于HBase
一张表的一个列族,。按照这个原理,我们在设计列族的时候,可以把经常查询的列放在同一个列族,这样可以提高效率,因为同一个列族在同一个文件里面(不考虑切分)。
每个Store
有一个内存级别的存储Memory Store
(有且仅有一个)。当Memory Store
达到一定大小或一定时间后会进行数据刷写(flush
),写到磁盘中(即HFile
)。
每个Store
有多个磁盘级别的存储StoreFile
,Memory Store
每刷写一次就形成一个StoreFile
,HFile
是StoreFile
在HDFS
上的存储格式。
HBase
读原理在上图中,我们模拟一下客户端读取数据过程,假设Zookeeper
存放的meta
表在RS1
机器上,meta
表存放的内容如下,Student
表行键范围在1~100
的存放在RS4
上,在101~200
的存放在RS3
上,等等。
meta表:
_________________________________________________
| 表名 | rowkey范围 | 所在位置 |
|____________|________________|_________________|
| Student | 1 ~ 100 | RS4 |
|____________|________________|_________________|
| Student | 101 ~ 200 | RS3 |
|____________|________________|_________________|
| Teacher | 1 ~ 500 | RS1 |
|____________|________________|_________________|
| ··· |
|_______________________________________________|
客户端现在要读取Student
表的第100
行,具体步骤如下:
1.
客户端向Zookeeper
发起请求,请求元数据所在RegionServer
,Zookeeper
集群存放的是HBase
的meta
表所在位置。
2.
Zookeeper
返回给客户端元数据所在RegionServer
(即RS1
)。
3.
客户端收到应答后去请求RS1
,请求查询Student
表的rowkey=100
数据所在位置。
4.
在RS1
上查询meta
表可知该数据在RS4
机器上,即返回给客户端rowkey
所在位置(RS4
)。
5.
客户端收到应答后去请求RS4
读数据。
6.
RS4
查询数据返回给客户端。查询时先去内存(MemStore
)查找,因为内存是最新的数据,如果找到了就返回结果,如果没找到则去缓存(cache
)找,如果找到了就返回结果,如果还没找到就去磁盘(StoreFile
)找,如果在磁盘找到了,则先将结果写入缓存(cache
),再返回给客户端,写入缓存是为了下次查询提高效率。
可以发现,在整个读过程中HMaster
并没有参与,即读流程与HMaster
无关,所以如果HMaster
挂了,也是可以读数据的。
HBase
写原理HBase
的写是比读快的,为什么呢,看下面的写过程,同样假设Zookeeper
存放的meta
表在RS1
机器上,meta
表存放的内容如下,Student
表行键范围在1~100
的存放在RS4
上,在101~200
的存放在RS3
上,等等。
meta表:
_________________________________________________
| 表名 | rowkey范围 | 所在位置 |
|____________|________________|_________________|
| Student | 1 ~ 100 | RS2 |
|____________|________________|_________________|
| Student | 101 ~ 200 | RS1 |
|____________|________________|_________________|
| Teacher | 1 ~ 500 | RS1 |
|____________|________________|_________________|
| ··· |
|_______________________________________________|
客户端现在要插入数据给Student
表,其中rowkey=100
,具体步骤如下:
1.
客户端向Zookeeper
发起请求,请求元数据所在RegionServer
,Zookeeper
集群存放的是HBase
的meta
表所在位置。
2.
Zookeeper
返回给客户端元数据所在RegionServer
(即RS1
)。
3.
客户端收到应答后去请求RS1
,请求查询Student
表的rowkey=100
数据所在位置。
4.
在RS1
上查询meta
表可知该数据在RS2
机器上,即返回给客户端rowkey
所在位置(RS2
)。
5.
客户端收到应答后去请求RS4
写入数据。
6.
RS2
收到请求,先将数据写入HLog
,再将数据写入MemStore
,写入MemStore
后就返回给客户端写入成功信息,此时,客户端的写流程完成了。
因为写入内存就结束了写流程,不用访问磁盘,所以总体比读流程是快一点的。
同样,在整个写流程中HMaster
也没有参与,所以如果HMaster
挂了,也是可以进行写数据的。但是,如果时间长了,表的大小一直变大,而HMaster却挂了,即不会触发Region
切分,这样就会导致数据倾斜,系统就变得不安全了。
HBase
数据flush
刷写过程在hbase-default.xml
配置文件中有这么几项配置(见下面),只要regionserver
其中某一个MemStore
满足第一点或者第二点,都会进行regionserver
级别的flush
,即所有MemStore
都要flush
;而满足第三点的,就会进行HRegion
级别的flush,即某个HRegion
下的所有MemStore
都要flush
。
hbase.regionserver.global.memstore.size
:regionServer
的全局MemStore
的大小,超过该大小会触发flush
到磁盘的操作,默认值是堆大小的40%
,而且regionserver
级别的flush
会阻塞客户端读写。说明白点就是当regionserver
中所有Memstore
的大小加起来达到了当前regionserver
堆内存的40%
就触发flush
操作,不管MemStore
有多小,每个MemStore
都要进行flush
到磁盘。hbase.regionserver.optionalcacheflushinterval
:内存中的文件在自动刷新之前能够存活的最长时间,默认是1h
,也就是说,当regionserver
中某个MemStore
存活时间达到了1h
,所有MemStore
都会进行flush
。hbase.hregion.memstore.flush.size
:单个region
里MemStore
的缓存大小,超过了这个大小那么整个HRegion
就会flush
,默认大小为128M
。
需要注意的是HBase
的最小flush
单元是HRegion
而不是单个MemStore
。
Flush
是由HMaster
触发的,Flush
顺序是按照Memstore
由大到小执行,先Flush Memstore
最大的Region
,再执行次大的,直至总体Memstore
内存使用量低于阈值(hbase.regionserver.global.memstore.lowerLimit
)。
HBase
数据compaction
合并过程由于在flush
过程中,可能会产生很多小文件(这很好理解,比如有两个MemStore
,一个很大,一个很小,然后就触发了flush
操作,那么那个小的就形成了小文件),我们都知道,HDFS
不适合存储小文件,所以在写入HDFS
之前会进行合并操作。
在hbase-default.xml
配置文件中有这么几项配置:
hbase.hregion.majorcompaction
:一个region
进行major compaction
合并的周期,在这个点的时候, 这个region
下的所有hfile
会进行合并,默认是7
天,major compaction
非常耗资源,建议生产关闭(设置为0
),在应用空闲时间手动触发。hbase.hstore.compactionThreshold
:一个store
里面允许存的hfile
的个数,超过这个个数会被写到新的一个hfile
里面 也即是每个region
的每个列族对应的memstore
在fulsh
为hfile
的时候,默认情况下当超过3
个hfile
的时候就会对这些文件进行合并重写为一个新文件,设置个数越大可以减少触发合并的时间,但是每次合并的时间就会越长。
在我们利用shell
命令或者API
删除数据的时候,数据并没有被删除,而是被打上标记,而是在这里的compaction
合并过程中才会被完全删除。