vlambda博客
学习文章列表

Hbase的设计原理与技术细节

一、HBASE概述

(1)概述

基于hadoop的数据库工具

来源于google的一片论文BigTable 后来由Apache做了开源实现 就是HBase

是一种 NoSQL 非关系型的数据库 不符合关系型数据库的范式

适合存储 半结构化 非结构化 的数据

适合存储 稀疏的数据 空的数据不占用空间

面向列(族)进行存储

提供实时增删改查的能力 是一种真正的数据库

可以存储海量数据 性能也很强大 可以实现上亿条记录的毫秒级别的查询

但是不能提供严格的事务控制 只能在行级别保证事务

Hbase是一个高可靠性 高性能 面向列 可伸缩的分布式存储系统 利用hbase技术可以在廉价的PC上搭建起大规模结构化存储集群。

HBase利用HadoopHDFS作为其文件存储系统,利用Hadoop的MapReduce来处理HBase中的海量数据,利用Zookeeper作为协调工具

(2)逻辑结构

hbase通过表来存储数据 但是表的结构和关系型数据库非常的不一样

行键 - RowKey - 即hbase的主键,访问hbase中的数据有三种方式:

1.通过单一行键访问

2.通过一组行键访问

3.全表扫描

列族(簇) - Column Family 

是表的元数据的一部分,需要在建表时声明,不能后期增加,如果需要增加只能alter表,一个列族可以包含一个或多个列

列 - Column

可以动态增加列,不需要提前声明,不是表的元数据一部分

单元格与时间戳 - cell timestamp

通过row和columns确定的一个存储单元。每个存储单元中都保存着一个数据的多个版本,版本通过时间戳来区别,而由row column 和 timestamp确定出来的唯一的存储数据的单元 称之为一个 cell 单元格。

数据都以二进制形式存储,没有数据类型的区别。

所有空数据都不占用空间。

二、安装配置

前提条件,安装jdk 和 hadoop,并配置了环境变量

1.单机模式

直接解压安装包

tar -zxvf xxxxx.tar.gz

修改conf/hbase-site.xml,配置hbase使用的数据文件的位置,默认在/tmp/hbase-[username],此目录是linux的临时目录,可能会被系统清空,所以最好修改一下

<property>

<name>hbase.rootdir</name>

<value>file:///<path>/hbase</value>

</property>

2.伪分布式模式

修改conf/hbase-env.sh修改JAVA_HOME

export JAVA_HOME=xxxx

修改hbase-site.xml,配置使用hdfs

<property>

<name>hbase.rootdir</name>

<value>hdfs://hadoop01:9000/hbase</value>

</property>

<property>

<name>dfs.replication</name>

<value>1</value>

</property>

启动hbase

3.完全分布式模式

修改conf/hbase-env.sh修改JAVA_HOME

export JAVA_HOME=xxxx

修改hbase-site.xml,配置开启完全分布式模式

配置hbase.cluster.distributed为true。

<property>

<name>hbase.rootdir</name>

<value>hdfs://hadoop00:9000/hbase</value>

</property>

<property>

<name>dfs.replication</name>

<value>1</value>

</property>

<property>

<name>hbase.cluster.distributed</name>

<value>true</value>

</property>

<property>

<name>hbase.zookeeper.quorum</name>

<value>hadoop01:2181,hadoop02:2181,hadoop03:2181</value>

</property>

修改conf/hbase-env.sh禁用对zookeeper的自动管理

export HBASE_MANAGES_ZK false

配置region服务器,修改conf/regionservers文件,其中配置所有hbase主机,每个主机名独占一行,hbase启动或关闭时会按照该配置顺序启动或关闭主机中的hbase

--------------------------------------------------

~HBASE配置文件说明

hbase-env.sh配置HBase启动时需要的相关环境变量

hbase-site.xml配置HBase基本配置信息

HBASE启动时默认使用hbase-default.xml中的配置,如果需要可以修改hbase-site.xml文件,此文件中的配置将会覆盖hbase-default.xml中的配置

修改配置后要重启hbase才会起作用

--------------------------------------------------

启动集群

启动zookeeper

启动hadoop

启动hbase

访问http://xxxxx:60010来访问web界面,通过web见面管理hbase

也可以通过hbase shell脚本来访问bhase

启动备用master实现高可用

hbase-daemon.sh start master

关闭集群

stop-hbase.sh

三、HBASE原理 

HBase的工作方式:

1.region的分裂和结构

hbase表中的数据按照行键的字典顺序排序

hbase表中的数据按照行的的方向切分为多个region

最开始只有一个region 随着数据量的增加 产生分裂 这个过程不停的进行 一个表可能对应一个或多个region

region是hbase表分布式存储和负载均衡的基本单元 一个表的多个region可能分布在多台HRegionServer上

region是分布式存储的基本单元 但不是存储的基本单元 内部还具有结构

一个region由多个Store来组成

有几个store取决于表的列族的数量 一个列族对应一个store 之所以这么设计 是因为 一个列族中的数据往往数据很类似 方便与进行压缩 节省存储空间

表的一个列族对应一个store store的数量由表中列族的数量来决定

一个store由一个memstore 和零个或多个storefile组成

storefile其实就是hdfs中的hfile 只能写入不能修改 所以hbase写入数据到hdfs的过程其实是不断追加hfile的过程

2.hbase写入数据

数据写入hbase时 先在hlog中记录日志 再修改memstore 直接返回成功 这样 不需要真正等待写入hdfs的过程 所以很快

memstore 内存有限 当写入数量达到一定的阈值的时候 就会创建一个新的memstore继续工作 而旧的memstore 会用一个单独的线程 写出到storefile中 最终清空旧的memstore 并在zookeeper中记录最后写出数据时间的redo point信息

由于storefile 不能修改 所以数据的更新其实是不停创建新的storefile的过程

这样多个storefile中可能存在对同一个数据的多个版本 其中旧的版本其实是垃圾数据 时间一长 垃圾数据就可能很多 浪费磁盘空间

所以当达到一定的阈值的时候 会自动合并storefile 在合并的过程中将垃圾数据清理

而当合并出来的文件达到一定程度时 再从新进行切分 防止文件过大

虽然看起来是小变大再变小 但是经过这个过程垃圾数据就被清理掉了

所以store中的数据 其实是memstore和storefile来组成的

而memstore由于是内存中的数据 一旦断电就会丢失

为了解决可能的意外造成数据丢失的问题 hbase在整个hregionserver中 通过记录hlog 来保存了所有数据操作的记录

当hbase启动时 会检查zookeeper中的redopoint信息 从hlog中恢复 这个时间点之后的数据 解决数据容易丢失的问题

hlog整个hregionServer中只有一个 所有这台机器中的所有HRegion都公用这个文件 这样整个机器的磁盘性能都可以为这一个文件提供支持 提升文件的读写效率

hlog文件最终对应的是hdfs中的文件 也是分布式存储的 保证了日志文件的可靠性

3.hbase读取数据

**hfile的内部由以下部分组成:

Data Blocks 段–保存表中的数据,这部分可以被压缩

Meta Blocks 段 (可选的)–保存用户自定义的kv对,可以被压缩。

File Info 段–Hfile的元信息,不被压缩,用户也可以在这一部分添加自己的元信息。

Data Block Index 段–Data Block的索引。每条索引的key是被索引的block的第一条记录的key。

Meta Block Index段 (可选的)–Meta Block的索引。

Trailer–这一段是定长的。保存了每一段的偏移量,读取一个HFile时,会首先 读取Trailer,Trailer保存了每个段的起始位置(段的Magic Number用来做安全check),然后,DataBlock Index会被读取到内存中,这样,当检索某个key时,不需要扫描整个HFile,而只需从内存中找到key所在的block,通过一次磁盘io将整个 block读取到内存中,再找到需要的key。DataBlock Index采用LRU机制淘汰。

HFile的Data Block,Meta Block通常采用压缩方式存储,压缩之后可以大大减少网络IO和磁盘IO,随之而来的开销当然是需要花费cpu进行压缩和解压缩。

在查询数据时,先查找内存,如果内存中有当前键对应的数据,获取数据直接返回。如果没有在内存中找到数据,就去查找region对应的hfile,注意不是将所有hfile中的数据恢复到内存,而是查找每个hfile的Trailer,通过trailer找到Data Block Index,如果在这里发现了要找的数据,通过索引找到Data Blocks中对应的Data Block,将Data Block数据送回内存组装,最终多个hfile中获取到的数据 合并后 返回最新的。

由于hbase中的数据天然排序 再加上索引 整个查询也可以非常的快

4.hbase中region的寻址

在hbase的hbase名称空间下有一张meta表,其中存放了 表和region和regionSever 之间的对应关系信息,这个表很特别,规定只能有一个region

并且这个meta表的这个region的位置信息被存放在了zookeeper的meta-region-server节点下

在客户端从hbase中查找数据时,需要先联系zookeeper找到meta表对应的region的位置,连接这个位置读取到meta表中的信息,才能知道要查询的表 和 表的region和region对应的regionServer的信息

再根据这些信息连接真正要查询的表 对应的region的regionServer进行读取

这个过程就称之为region的寻址过程。

5.存储系统三种结构

目前常见的主要的三种存储引擎是:哈希、B+树、LSM树:

哈希存储引擎:是哈希表的持久化实现,支持增、删、改以及随机读取操作,但不支持顺序扫描,对应的存储系统为key-value存储系统。对于key-value的插入以及查询,哈希表的复杂度都是O(1),明显比树的操作O(n)快,如果不需要有序的遍历数据,哈希表性能最好。

B+树存储引擎是B+树的持久化实现,不仅支持单条记录的增、删、读、改操作,还支持顺序扫描(B+树的叶子节点之间的指针),对应的存储系统就是关系数据库(Mysql等)。

LSM树(Log-Structured MergeTree)存储引擎和B+树存储引擎一样,同样支持增、删、读、改、顺序扫描操作。而且通过批量存储技术规避磁盘随机写入问题。当然凡事有利有弊,LSM树和B+树相比,LSM树牺牲了部分读性能,用来大幅提高写性能。

上面三种引擎中,LSM树存储引擎的代表数据库就是HBase.

LSM树核心思想的核心就是放弃部分读能力,换取写入的最大化能力。LSM Tree ,这个概念就是结构化合并树的意思,它的核心思路其实非常简单,就是假定内存足够大,因此不需要每次有数据更新就必须将数据写入到磁盘中,而可以先将最新的数据驻留在内存中,等到积累到足够多之后,再使用归并排序的方式将内存内的数据合并追加到磁盘队尾(因为所有待排序的树都是有序的,可以通过合并排序的方式快速合并到一起)。

日志结构的合并树(LSM-tree)是一种基于硬盘的数据结构,与B+tree相比,能显著地减少硬盘磁盘臂的开销,并能在较长的时间提供对文件的高速插入(删除)。然而LSM-tree在某些情况下,特别是在查询需要快速响应时性能不佳。通常LSM-tree适用于索引插入比检索更频繁的应用系统。

LSM树和B+树的差异主要在于读性能和写性能进行权衡。在牺牲的同时寻找其余补救方案:

(a)LSM具有批量特性,存储延迟。当写读比例很大的时候(写比读多),LSM树相比于B树有更好的性能。因为随着insert操作,为了维护B+树结构,节点分裂。读磁盘的随机读写概率会变大,性能会逐渐减弱。

(b)B树的写入过程:对B树的写入过程是一次原位写入的过程,主要分为两个部分,首先是查找到对应的块的位置,然后将新数据写入到刚才查找到的数据块中,然后再查找到块所对应的磁盘物理位置,将数据写入去。当然,在内存比较充足的时候,因为B树的一部分可以被缓存在内存中,所以查找块的过程有一定概率可以在内存内完成,不过为了表述清晰,我们就假定内存很小,只够存一个B树块大小的数据吧。可以看到,在上面的模式中,需要两次随机寻道(一次查找,一次原位写),才能够完成一次数据的写入,代价还是很高的。

(c)LSM优化方式:

Bloom filter: 就是个带随机概率的bitmap,可以快速的告诉你,某一个小的有序结构里有没有指定的那个数据的。于是就可以不用二分查找,而只需简单的计算几次就能知道数据是否在某个小集合里啦。效率得到了提升,但付出的是空间代价。

compact:小树合并为大树:因为小树性能有问题,所以要有个进程不断地将小树合并到大树上,这样大部分的老数据查询也可以直接使用log2N的方式找到,不需要再进行(N/m)*log2n的查询了

6.hbase系统架构:

hbase中的老大叫hmaster 小弟叫hregionServer

客户端叫Client

Zookeepr为hbase提供集群协调

client:访问hbase 保留一些缓存信息提升效率

zookeeper:

保证任何时候集群只有一个HMaster

监控regionServer的状态 将其上线下线信息通知mater

存储hbase的元数据信息 包括 有哪些表 有哪些列族等等

Mater:

为RegionServer分配Region

为RegionServer进行负载的均衡

GFS上的垃圾回收

处理对Schema数据的更新请求

RegionServer:

维护Master分配给它的region,处理对这些region的IO请求

负责切分在运行过程中变得过大的region

为什么hbase可以很快?

从逻辑结构上来说:

表按照行键进行了排序,所以查询时可以很快定位

数据按照行键切分为多个HRegion,分布在多个RegionServer中,查询大量数据时,多个RegionServer可以一起工作,从而提高速度

从物理结构上来说:

HRegion是存活在RegionServer的内存中的,读写会非常的高效

还有HFile的支持保证大量的数据可以持久化的保存

数据最终落地到HDFS中,分布式的存储,保证数据段可靠性和可扩展性

为什么hbase可以存储很多数据:

基于hdfs,所以支持可扩展性,可以通过增加大量的廉价的硬件提高存储容量

按列存储,空的数据不占用空间,当存储稀疏数据时,不会浪费空间

按例存储,同一列的数据存放在一起,而同一列的数据一般都是同样的类型的内容相似的数据,可以实现非常高效的压缩,节省空间

为什么hbase的数据是可靠的:

基于hdfs,由hdfs的可靠性保证了hbase的可靠性--即数据可以有多个备份

利用zookeeper实现了HA,即使某一台机器挂掉另外的机器也可以很快的替换它

hbase和hive和传统的关系型数据库的比较:

比起传统的关系型数据库,可以存储半结构化非结构化的数据,可以存储和处理更大级别的数据,提供高效的查询,对于稀疏数据的处理更好,具有更好的横向扩展性,免费开源性价比很高。但是不能支持非常好的事务特性,只支持行级的事务。只能通过行键来查询,表设计时难度更高。而mysql用来存储结构化的数据提供更好的事务控制。

比起hive,hive只是在mapreduce上包了一层壳,本质上还是离线数据的处理的工具,实时查询性能有限,本质上是一个基于hadoop的数据仓库工具,不能支持行级别的新增修改和删除。hbase可以提供实时的数据的处理能力,适用于在线数据查询处理,本质上是一种数据库工具。

六、HBase的表设计

HBase表的设计 会直接影响hbase使用的效率 和 使用的便利性

HBase表的设计 主要是 列族的设计 和 行键的设计

1.列族的设计

在设计hbase表时候,列族不宜过多,越少越好,官方推荐hbase表的列族不宜超过3个。

经常要在一起查询的数据最好放在一个列族中,尽量的减少跨列族的数据访问。

如果有多个列族 多个列族中的数据应该设计的比较均匀

2.行键的设计

hbase表中行键是唯一标识一个表中行的字段,所以行键设计的好不好将会直接影响未来对hbase的查询的性能和查询的便利性

所以hbase中的行键是需要进行设计的

行键设计的基本原则:

行键必须唯一,必须唯一才能唯一标识数据

行键必须有意义,这样才能方便数据的查询

行键最好是字符串类型,因为数值类型在不同的系统中处理的方式可能不同

行键最好具有固定的长度,不同长度的数据可能会造成自然排序时排序的结果和预期不一致

行键不宜过长,行键最多可以达到64KB,但是最好是在10~100字节之间,最好不要超过16字节,越短越好,最好是8字节的整数倍。

散列原则:

行键的设计将会影响数据在hbase表中的排序方式,这会影响region切分后的结果,要注意,在设计行键时应该让经常要查询的数据分散在不同的region中,防止某一个或某几个regionserver成为热点。

有序原则:

行键的设计将会影响数据在hbase表中的排序方式,所以一种策略是将经常连续查询的条件作为行键最前面的数据,这样一来可以方便批量查询

实践:工作实际使用中,列族一般都为1个,rowkey根据实际查询情况进行拼接,一般先查询的字段会放前面,并在最前面加上某个字段的reverse值。

当rowkey查询不满足需求时,使用全局索引(实际就是给索引再建表)和全文索引(基于ES)进行高并发的检索查询。