前言
Hbase是一个分布式、可扩展、支持海量数据存储的NoSQL数据库。当数据量较小时,Hbase的优势不仅体现不出来,反而相比其他传统数据库而言更加消耗性能,但在数据量巨大的情况下,Hbase能达到秒级查询。
Hbase的数据存储于HDFS,HDFS不支持随机写,但一个数据库需要支持增删改查,所以Hbase在底层存储时,实际上是只增不删,每条数据都包含一个时间戳属性,和删除标记,用于判断数据是否有效。
Hbase概念
物理架构
Hbase集群中有两种角色:Master服务器和RegionServer服务器。一般一个HBase集群有一个Master服务器和几个RegionServer服务器。Master服务器负责维护表结构信息,实际的数据都存储在RegionServer服务器上。
虽说Master是负责维护表结构信息,但client实际上是通过访问zookeeper来获取表信息,然后直连RegionServer读写数据。所以你会发现Master挂掉之后你依然可以查询数据,但就是不能新建表了。
客户端每次与HBase连接,其实都是先与ZooKeeper通信,查询出哪个RegionServer需要连接,然后再连接RegionServer。
Hbase的物理架构大致如图:
Master:负责启动的时候分配Region到具体的RegionServer,执行各种管理操作,比如Region的分割和合并。在HBase中的
Master的角色功能比其他类型集群弱很多。其他的集群系统中的主节点都是至关重要,比如Hadoop的namenode就负责了管理所有data的映射,而MongoDB的mongos负责了路由,他们的主节点只要宕机了,整个集群就瘫痪了。但是HBase的Master很特别,因为数据的读取和写入都跟它没什么关系,它挂了业务系统照样运行。这是为什么呢?具体原因我在后面解释。当然Master也不能宕机太久,有很多必要的操作,比如创建表、修改列族配置,以及更重要的分割和合并都需要它的操作。
RegionServer:RegionServer上有一个或者多个Region。我们读写的数据就存储在Region上。如果你的HBase是基于HDFS的,那么Region所有数据存取操作都是调用了HDFS的客户端接口来实现的。
Region:表的一部分数据。HBase是一个会自动分片的数据库。一个Region就相当于关系型数据库中分区表的一个分区,或者
MongoDB的一个分片。
HDFS:Hadoop的一部分。HBase并不直接跟服务器的硬盘交互,而是跟HDFS交互,所以HDFS是真正承载数据的载体。
ZooKeeper:ZooKeeper虽然是自成一家的第三方组件,不属于HBase体系。但是ZooKeeper在HBase中的重要性甚至超过了
Master。为什么这么说呢?你可以做一个实验:把Master服务器关掉你的业务系统照样跑,能读能写;但是把ZooKeeper关掉,
你就不能读取数据了,因为你读取数据所需要的元数据表hbase:meata的位置存储在ZooKeeper上。
逻辑结构
Hbase逻辑上和关系型数据库类似,数据都是存储在一张表中,有行有列,但是Hbase还有一个“列簇”的概念,一个列簇包含多个列,每个列必须属于一个列簇。Hbase允许一张表有多个列簇,但是实际生产中往往只会使用一个列簇。原因在下面说。Hbase的每一行都有一个唯一标识Row_key,Row_key的值必须是唯一的,可以理解为是Hbase的主键。Hbase的模型结构如下图:
列簇
每个表至少包含一个列簇,一个列簇可包含多个列。HBase会把相同列族的列尽量放在同一台机器上,所以说,如果想让某几个列被放到一起,你就给他们定义相同的列族。建表的时候是不需要制定列的,因为列是可变的,它非常灵活,唯
一需要确定的就是列族。这就是为什么说一个表有几个列族是一开始就定好的。此外,表的很多属性,比如过期时间、数据块缓存以及是否压缩等都是定义在列族上,而不是定义在表上或者列上。这一点做法跟以往的数据库有很大的区别。同一个表里的不同列族可以有完全不同的属性配置,但是同一个列族内的所有列都会有相同的属性,因为他们都在一个列族里面,而属性都是定义在列族上的。
官方提示一个表的列族应该尽量少,最好在1~2个左右。笔者认为这是因为Hbase根据列族和region划分Store,一个Store底层存储在一个或多个Hfile中,当一个Store的文件达到一定大小时,会进行Region的切分,过多的列族会导致Region在切分时数据不均匀,可能一个store已经1GB了,而另一个才100MB,如果进行切分,则这个100MB的Store也会随之切分;还有一个原因就是store多了,在底层存储时,文件数量也会增多,会增加HDFS中namenode的负担。
列
每个列必属于一个列簇,每条数据的列的个数和结构都可以不同。Hbase在建表时不需要指定列,Hbase的列非常灵活,可以随时对表添加列。
Row key
每条数据对应一个Row key,Row key的值必须唯一,相当于Hbase的主键。Row key会直接决定这条数据的存放位置,Hbase会根据你定义好的分区策略对Row key进行分区,相同分区的Row key会存放到一个Region中,所以,需要合理的设计Row key来避免热点问题。
Region
按row key进行分区,一个分区的数据对应在一个Region里。Region就是一段数据的集合。HBase中的表一般拥有一个到多个Region。Region有以下特性:
- Region不能跨服务器,一个RegionServer上有一个或者多个Region。
- 数据量小的时候,一个Region足以存储所有数据;但是,当数据量大的时候,HBase会拆分Region。
- 当HBase在进行负载均衡的时候,也有可能会从一台RegionServer上把Region移动到另一台RegionServer上。
- Region是基于HDFS的,它的所有数据存取操作都是调用了HDFS的客户端接口来实现的。
Store
每个Region的每个列簇在底层存储为一个Hfile。
存储结构
Hbase在底层是以K-V类型存储的。每条数据的每个属性都会保存一条记录,由于HDFS不支持随机写操作,所以当修改属性时,不会修改记录,而是插入一条新的记录,每条记录除了包含数据的属性外,还包含一个时间戳(TimeStamp)表示数据更新时的时间,一个数据类型(Type)表示数据时修改还是删除或者是插入等。如:
row key为row_key1的这条数据,初始情况下在底层实际上存储了三条记录,分别记录了这条数据的name、city、phone属性,当要修改数据的phone属性时,则再插入一条记录表示新的phone属性,根据时间戳判断最新的那条记录有效。
预写日志WAL
WAL(Write-Ahead Log)预写日志是一个保险机制。当向Hbase写入一条数据时,Hbase首先会将数据写入WAL,然后再把数据放入内存中(Memstore),而不会立即落盘。当查询时也是先在内存中查询,如果内存中没有查询到,才会在磁盘中进行查询。当Memstore中的数据达到一定的量时,才刷写(flush)到最终存储的HFile内。而如果在这个过程中服务器宕机或者断电了,那么数据就丢失了。但是有了WAL机制,在Hbase将数据写入Memstore前就先写入了WAL,这样当发生故障后,就可以通过WAL将丢失的数据恢复回来。
WAL是默认开启的,也可以选择关闭或延迟(异步)同步写入WAL。
WAL滚动
WAL是一个环状的滚动日志结构,这种结构写入效果最高,而且可以保证空间不会持续变大。
WAL的检查间隔由hbase.regionserver.logroll.period定义,默认值为1小时。检查的内容是把当前WAL中的操作跟实际持久化到HDFS上的操作比较,看哪些操作已经被持久化了,被持久化的操作就会被移动到.oldlogs文件夹内(这个文件夹也是在HDFS上的)。一个WAL实例包含有多个WAL文件。WAL文件的最大数量通过hbase.regionserver.maxlogs(默认是32)参数来定义。
其他的触发滚动的条件是:
- 当WAL文件所在的块(Block)快要满了。
- 当WAL所占的空间大于或者等于某个阀值,该阀值的计算公式是:
上面提到的阀值公式中的hbase.regionserver.hlog.blocksize是标定存储系统的块(Block)大小的,你如果是基于HDFS的,那么只需要把这个值设定成HDFS的块大小即可。实际上,如果你不设定hbase.regionserver.hlog.blocksize,HBase还是会自己去尝试获取这个参数的值。不过,还是建议你设定该值。
hbase.regionserver.logroll.multiplier是一个百分比,默认设定成0.95,意思是95%,如果WAL文件所占的空间大于或者等于95%的块大小,则这个WAL文件就会被归档到.oldlogs文件夹内。
WAL文件被创建出来后会放在/hbase/.log下(这里说的路径都是基于HDFS),一旦WAL文件被判定为要归档,则会被移动到/hbase/.oldlogs文件夹。
Master会负责定期地去清理.oldlogs文件夹,条件是“当这个WAL不需要作为用来恢复数据的备份”的时候。判断的条件是“没有任何引用指向这个WAL文件”。