HBase 数据库架构原理大总结
一. HBase简介
HBase是一个开源的Key-Value型数据库,运行于HDFS文件系统之上,它本本质上是一个多维稀疏排序Map。
采用MAP结构
HBase本质上是一个Map结构数据库,HBase中Map的key是一个复合键,由rowkey、column family、qualifier、type以及timestamp组成,value即为cell的值。比如:
{"rowKeyUserid1", "info", "username", "put", "1646298382549"} -> "xiaohong"
{"rowKeyUserid2", "info", "username", "put", "1646298382549"} -> "xiaowang"
采用稀疏存储
在其他数据库中,对于空值的处理一般都会填充null,而对于HBase,空值不需要任何填充,这样就节省了存储空间。
文件内部有序
构成HBase的KV在同一个文件中都是有序的,但规则并不是仅仅按照rowkey排序,而是按照KV中的key进行排序:
- 先比较 rowkey,rowkey 小的排在前面
- 如果rowkey相同,再比较column,即column family:qualifier,column小的排在前面
- 如果column还相同,再比较时间戳timestamp,即版本信息,timestamp大的排在前面。
二. 基本架构
NameSpace
命名空间,相当于关系型数据库的表,默认有两个命名空间 hbase、default。hbase是内部存放的是 hbase 内置表,default 是用户默认使用的命名空间。
hbase:meta 表是所有查询的入口,表结构如下:
hbase(main):016:0> get 'hbase:meta','wide_userprofile,fae14750,1645407245007.ad6991383a37a1b9f68f59489489fa02.'
COLUMN CELL
info:regioninfo timestamp=1645950005617, value={ENCODED => ad6991383a37a1b9f68f59489489fa02, NAME => 'wide_userprofile,fae14750,1645407245007.ad6991383a37a1b9f68f59489489fa02.', STARTKEY => 'fae14750', ENDKEY => 'fd70a378'}
info:seqnumDuringOpen timestamp=1645950005617, value=\x00\x00\x00\x00\x00\x00\x00\x0A
info:server timestamp=1645950005617, value=node2:16020
info:serverstartcode timestamp=1645950005617, value=1645949989244
info:sn timestamp=1645950004497, value=node2,16020,1645949989244
info:state timestamp=1645950005617, value=OPEN
hbase:meta 表的 rowKey
rowKey 表示能唯一定位到一个 region 的 Name,即在上面示例中,wide_userprofile 表的其中一个 region 名称。由这几个部分组成 TableName,StartKey,Timestamp.EncodedName 。比如 wide_userprofile,fae14750,1645407245007.ad6991383a37a1b9f68f59489489fa02 ,含义如下:
- TableName:表名称;
- StartKey:如果为空表示第一个 region。并且如果一个 region 中 StartKey 和 EndKey 都为空表明这个 table 只有一个 region;
- Timestamp:Region 创建时的时间戳;
- EncodedName:TableName,StartKey,Timestamp字符串的MD5 Hex值。
hbase:meta 表的 column
info(6个qualifier):
- info:regioninfo:主要存储4个信息,即EncodedName、RegionName、Region的StartRow、Region的EndRow。
- info:seqnumDuringOpen:主要存储Region打开时的sequenceId。
- info:server:主要存储Region落在哪个RegionServer上。
- info:serverstartcode:主要存储所在RegionServer的启动Timestamp。
- info:sn:value 由 server 和 serverstartcode 组成
- info:state:该列对应的 value 表示 Region 状态
table(1个qualifier):
- table:state:\x08\x00 表示 ENABLED 状态,\x08\x01 表示 DISABLED 状态
Master
Master主要负责HBase系统的各种管理工作
- 处理用户的各种管理请求,包括建表、修改表、权限操作、切分表、合并数据分片以及Compaction等。
- 管理集群中所有RegionServer,包括RegionServer中Region的负载均衡、RegionServer的宕机恢复以及Region的迁移等。
- 清理过期日志以及文件,Master会每隔一段时间检查HDFS中HLog是否过期、HFile是否已经被删除,并在过期之后将其删除。
ReginServer
RegionServer主要用来响应用户的IO请求,是HBase中最核心的模块,由WAL(HLog)、BlockCache以及多个Region构成。
- 直接负责存储数据的服务器,客户端的读写请求都是发送到regionserver上。
- 负责 Split 在运行过程中变得过大的 Region,client 访问 HBase 上数据的过程并不需要 master 参与(寻址访问 zookeeper 和 RegioneServer,数据读写访问 RegioneServer),Master 仅仅维护着 Table 和 Region 的元数据信息,RegioneServer 当中 Region 在进行分裂之后新产生的 Region,是由 Master 来决定发到哪个 RegioneServer,这就意味着,只有 Master 知道新 Region 的位置信息,即由Master来管理 'hbase:meta' 表数据
- 一个Regionserver包含一个WAL、一个BlockCache、多个region。
Zookeeper
作用:
- 实现Master高可用:zk一旦Active Master宕机,ZooKeeper会检测到该宕机事件并选举出新的Master。
- 管理系统核心元数据:比如系统元数据表 hbase:meta 所在的RegionServer地址等。
- 参与RegionServer宕机恢复:ZooKeeper通过心跳可以感知到RegionServer是否宕机,并在宕机后通知Master进行宕机处理。
- 实现分布式表锁:HBase中对一张表进行各种管理操作(比如alter操作)需要先加表锁,防止其他用户对同一张表进行管理操作,造成表状态不一致。
hbase:meta位置 =》zookeeper节点名称:/hbase/meta-region-server
$ hbase zkcli
[zk: node1:2181,node2:2181,node3:2181(CONNECTED) 4] get /hbase/meta-region-server
�master:16000930�I��5PBUF
node4�}����/
cZxid = 0xe0000288e
ctime = Thu Feb 17 11:08:09 CST 2022
mZxid = 0x110000011e
mtime = Sun Feb 27 16:19:57 CST 2022
pZxid = 0xe0000288e
cversion = 0
dataVersion = 11
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 52
numChildren = 0
Region
- 一个Region包含多个Store,一个Store对应一个列族。
- 一个regionserver上有多个region,一张表可以有多个region,一张表的多个region可以分布在不同的regionserver上,一个region只能属于一张表。
- 使用水平切分,一条完整的数据一定是只属于一个region,HBase使用RowKey将表水平切割成多个Region,每个Region都纪录了它的StartKey和EndKey,由于RowKey是排序的,因而Client可以快速的定位每个RowKey在哪个Region中。
- Table在行的方向上分隔为多个Region。Region是HBase中分布式存储和负载均衡的最小单元。
- 每张表默认初始化是只有一个region,随着数据不断插入表,region不断增大,当region的某个列族达到一个阈值(默认10G)就会分成两个新的region。
Store
每一个region由一个或多个store组成,至少有一个store,即为每个 ColumnFamily 建一个store,如果有几个ColumnFamily,就会有几个Store。一个Store由一个memStore和0个或者多个StoreFile组成,HBase以store的大小来判断是否需要切分region。
MemStore(写缓存)
由于HFile 中的数据要求是有序的,所以数据是先存储在MemStore 中,排好序后,等到达刷写时机(默认128MB)才会刷写到HFile,每次刷写都会形成一个新的HFile。
StoreFile
保存实际数据的物理文件,StoreFile 以HFile 的形式存储在HDFS 上。每个Store 会有0个或多个StoreFile(HFile),数据在每个StoreFile 中都是有序的。
HFile
HBase中KeyValue数据的存储格式,HFile是Hadoop的 二进制格式文件,实际上StoreFile就是对Hfile做了轻量级包装,即StoreFile底层就是HFile。
LSM树
HBase的一个列簇(Column Family)本质上就是一棵LSM树(Log-Structured Merge-Tree),LSM树本质上和B+树一样,是一种磁盘数据的索引结构。但和B+树不同的是,LSM树的索引对写入请求更友好。因为无论是何种写入请求,LSM树都会将写入操作处理为一次顺序写,而HDFS擅长的正是顺序写(且HDFS不支持随机写)。
LSM树中存放的并非数据本身,而是操作记录(操作的类型注意包括Put、Delete、DeleteColumn、DeleteFamily等等)
一个LSM树的索引主要由两部分构成:内存部分和磁盘部分。内存部分是一个ConcurrentSkipListMap,Key就是rowkey、column family、qualifier、type以及timestamp组成的,Value是一个字节数组。数据写入时,直接写入MemStore中。随着不断写入,一旦内存占用超过一定的阈值时,就把内存部分的数据导出,形成一个有序的数据文件,存储在磁盘上。
为了避免flush影响写入性能,会先把当前写入的MemStore设为Snapshot,不再容许新的写入操作写入这个Snapshot的MemStore。另开一个内存空间作为MemStore,让后面的数据写入。一旦Snapshot的MemStore写入完毕,对应内存空间就可以释放。这样,就可以通过两个MemStore来实现稳定的写入性能。
布隆过滤器
在每个HFile内是由一个个数据块组成。要判断指定 Key 是否存在这个HFile中,HBase使用了BloomFilter来提升性能,减少了不必要的 IO 操作。
BlockCache(读缓存)
BlockCache (一级缓存)对象是一系列Block块,一个Block默认为64K,由物理上相邻的多个KV数据组成。BlockCache同时利用了空间局部性和时间局部性原理:
- 空间局部性:表示最近读取的KV数据很可能与当前读取到的KV数据在地址上是邻近的,缓存单位是Block(块)。
- 时间局部性:表示一个KV数据正在被访问,那么近期它还可能再次被访问。
- 一个HRegionServer只有一个BlockCache,在HRegionServer启动的时候完成BlockCache的初始化,通过get.setCacheBlocks()设置,默认为True。
BlockCache 包括 LruBlockCache,以及 CombinedBlockCache(LruBlockCache + BucketCache)
- LruBlockCache:实际上就是一个ConcurrentHashMap管理BlockKey到Block的映射关系,缓存Block只需要将BlockKey和对应的Block放入该HashMap中
- CombinedBlockCache: LruBlockCache 和 BucketCache 的混合使用。
HLog(WAL)
- HLog即为WAL log(write ahead log),用做故障恢复使用,HLog顺序写入记录的所有变更,一旦region server 宕机,就可以从log中进行恢复。
- 一个regionserver,包含一个Hlog文件。
- HLog文件就是一个普通的Hadoop Sequence File
Hbase存储结构
- 逻辑结构
- 物理结构
三. HBase关键流程
写操作
- client先访问zookeeper,获取 'hbase:meta' 表位于哪个Region Server。
- 访问对应的Region Server,获取 'hbase:meta' 表,根据读请求的namespace:table/rowkey,查询出目标数据位于哪个Region Server中的哪个Region中。并将该table的region信息以及meta表的位置信息缓存在客户端的meta cache,方便下次访问。
- 与目标Region Server进行通讯。
- 将数据顺序写入(追加)到WAL。
- 将数据写入对应的MemStore,数据会在MemStore进行排序。
- 向客户端发送ack。
- 等达到MemStore的刷写时机后,将数据刷写到HFile。
刷磁盘
MemStore Flush刷写磁盘
- 当某个memstroe的大小达到了“hbase.hregion.memstore.flush.size”(默认值128M),其所在region的所有memstore都会刷写。当memstore的大小达到了“hbase.hregion.memstore.flush.size(默认值128M)* hbase.hregion.memstore.block.multiplier(默认值4)”时,会阻止继续往该memstore写数据。
- 当region server 中memstore 的总大小达到“java_heapsize * hbase.regionserver.global.memstore.size(默认值0.4) * hbase.regionserver.global.memstore.size.lower.limit(默认值0.95)”,region 会按照其所有memstore的大小顺序(由大到小)依次进行刷写。直到region server中所有memstore的总大小减小到上述值以下。当region server中memstore的总大小达到“java_heapsize * hbase.regionserver.global.memstore.size(默认值0.4)”时,会阻止继续往所有的memstore 写数据。
- 到达自动刷写的时间,也会触发memstore flush。自动刷新的时间间隔由该属性进行配置“hbase.regionserver.optionalcacheflushinterval(默认1 小时)”。
- 一个列族对应一个store,对应一个memstore,刷写时对应一个HFile。
读操作
- Client先访问zookeeper,获取hbase:meta表位于哪个Region Server。
- 访问对应的Region Server,获取hbase:meta表,根据读请求的namespace:table/rowkey,查询出目标数据位于哪个Region Server中的哪个Region中。并将该table的region信息以及meta表的位置信息缓存在客户端的meta cache,方便下次访问。
- 与目标Region Server 进行通讯;
- 分别在Block Cache(读缓存,LRU),MemStore和Store File(HFile)中查询目标数据,并将查到的所有数据进行合并。此处所有数据是指同一条数据的不同版本(time stamp)或者不同的类型(Put/Delete)。
- 将从文件中查询到的数据块(Block,HFile 数据存储单元,默认大小为64KB)缓存到Block Cache。
- 将合并后的最终结果返回给客户端。
StoreFile合并
由于memstore每次刷写都会生成一个新的HFile,且同一个字段的不同版本(timestamp)和不同类型(Put/Delete)有可能会分布在不同的HFile中,因此查询时需要遍历所有的HFile。为了减少HFile的个数,以及清理掉过期和删除的数据,会进行StoreFile Compaction。
Compaction分为两种:Minor Compaction、Major Compaction。Minor Compaction会将临近的若干个较小的HFile合并成一个较大的HFile,但不会清理过期和删除的数据。Major Compaction会将一个Store下的所有的HFile合并成一个大HFile,并且会清理掉过期和删除的数据。
HFile的合并采用了归并排序。
Region拆分
- 当一个region的大小大于hbase.hregion.max.filesize(默认10GMB)时,会拆分region,拆分region以后,HMaster鉴于负载均衡考虑,新的region可能会被分到其他regionserver上。
- 当1个region中的某个Store下所有StoreFile的总大小超过Min(R^2 * hbase.hregion.memstore.flush.size(默认值128M),hbase.hregion.max.filesize),该Region就会进行拆分,其中R为当前Region Server中属于该Table的region个数(0.94 版本之后)。即:第一次总大小到达128M时,切片为:64M、64M;第二次到达521M时,切片为:64M、256M、256M,继续切分后,各个切片大小逐步产生数据倾斜问题(可通过预分区解决)。
参考资料: