Hbase 概述

1,199 阅读21分钟

1. 什么是HBase

HBase的原型是Google的BigTable论文,受到了该论文思想的启发,目前作为Hadoop的顶级项目来开发维护,用于支持结构化的数据存储。

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

HBase的目标是存储处理大型的数据,更具体来说是仅需使用普通的硬件配置,就能够处理由成千上万的行和列所组成的大型数据。【非大勿用】

HBase是Google Bigtable的开源实现,但是也有很多不同之处。比如:Google Bigtable利用GFS作为其文件存储系统,HBase利用Hadoop HDFS作为其文件存储系统;Google运行MAPREDUCE来处理Bigtable中的海量数据,HBase同样利用Hadoop MapReduce来处理HBase中的海量数据;Google Bigtable利用Chubby作为协同服务,HBase利用Zookeeper作为对应。

简单粗暴的总结:就是一款NoSQL数据库,面向列存储,用于存储处理海量数据。

核心在于它是一个存数据的地方,可是在此之前学习过了HDFS和Mysql,那HBase为什么还会出现呢?

2. 为什么会有HBase

先说一下Mysql,我们都知道Mysql是一个关系型数据库,平时开发使用的非常频繁。一个网站或者系统最核心的表就是用户表,而当用户表的数据达到几千万甚至几亿级别的时候,对单条数据的检索将会耗费数秒甚至分钟级别。实际的清空可能更加复杂不堪。

看下边一张表: 在这里插入图片描述 假如根据id=1查询出来这条数据对应的用户姓名,很简单,会给我们返回zhangsan。但是,当我们查的时候,想一下,查名字的时候age和email会不会被查出来?答案是肯定的,Mysql的数据存储是以行为单位的,面向行存储。那问题就出现了,我只需要找出zhangsan的名字,却需要查询一整行的数据,如果列非常多,那么查询效率可想而知了。

查询的操作速度会受到以下两个因素的制约:

  1. 表被并发的插入、编辑以及删除操作。
  2. 查询语句通常不是简单的对一个表进行操作,有可能是多个表关联后的复杂查询,甚至有可能是group by或者order by操作,此时,性能下降很明显。 如果一张表的列过多,会影响查询效率,我们称这样表为宽表。怎么优化呢,拆开来,竖直拆分在这里插入图片描述 这样的情况下,我们要查找username的时候只需要查找user_basic表,没有多余的字段,查询效率就会很快。如果一张表的行过多,会影响查询效率,我们将这样的表称之为高表,可以采用水平拆表的方式提高效率:在这里插入图片描述 这种水平拆分应用比较多的 场景就是日志表,日志信息每天产生很多,可以按月进行水平拆分,这样就实现了高表变矮。

ok,这种拆分方式貌似可以解决宽表和高表的问题,但是如果有一天公司的业务变了,比如原来没有微信,现在有了微信,需要加入用户的微信字段。这时候需要改变表的结构信息,该怎么办?最简单的想法是多加一列,像这样:在这里插入图片描述 多考虑一下就知道这样做很不妥帖,比如说有些早期用户没有微信,这一列是设置默认值还是采取其他的做法就得权衡一下。如果需要扩展很多的列出来,而且不是所有的用户都有这些属性,那么拓展起来就更加复杂了。

这时候,想到了JSON格式的字符串,这是一种以字符串的形式表示的对象(将若干可选择填写信息汇总),而且属性字段可以动态拓展,于是有了下边这种做法,两种做法加以对比在这里插入图片描述 ok,这样存储数据它不挺好的嘛,HBase出来干嘛??Mysql有一点,数据达到一定的阈值(一般是500W),无论怎么优化,它都无法达到高性能的发挥。而大数据领域的数据,动辄PB级,这种存储应用明显是不能很好的满足需求的。针对上边的问题,HBase都有很好的解决方案~~

3. HBase怎么实现的

先不说为什么用,接着上边说到的几个问题:高表宽表,数据列动态扩展,把提到的几个解决办法:水平垂直切分列扩展方法杂糅在一起。

有这么一张表,怕它又宽又高,又会动态扩展列,那么在设计之初,就把这个表给他拆开,为了列的动态拓展,直接存储JSON格式: 在这里插入图片描述 这样就解决了宽表问题,高表怎么办呢?一个表的两部分,各存一部分行: 在这里插入图片描述 解决了高表,宽表,动态扩展列的问题,如果还要进一步提高性能怎么办?Mysql->Redis !!! 缓存啊!

查询出来的数据放入到缓存中,下一次查询直接从缓存中拿数据。插入数据怎么办呢?也可以这样理解,我把要插入的数据放进缓存中,再也不用管了,直接由数据库从缓存拿数据插入到数据库。此时程序不需要等待数据插入成功,提高了并行工作的效率。

可是这样做有了很大的风险,服务器宕机的话,缓存中的数据没来得及插入到数据库中,那不就丢数据了嘛。参考Redis的持久化策略,可以给插入数据这个操作添加一个操作日志,用于持久化插入操作,宕机重启后从日志恢复。

这样设计架构就变成了这个样子:在这里插入图片描述 上边这种解决方式,实际上就是HBase实现的大致思路,详细的内容会在后边慢慢说。

简单粗暴总结:HBase就是一个面向列存储的非关系型数据库。两者的区别主要是: 在这里插入图片描述 HBase是的存储时基于HDFS的,HDFS有着高容错性的特点,被设计用来部署在低廉的硬件上,而且它提供高吞吐量以访问应用程序的数据,基于Hadoop意味着HBase与生俱来的超强的扩展性吞吐量

HBase采用的时key/value的存储方式,这意味着,即使随着数据量的增大,也几乎不会导致查询性能的下降。HBase又是一个面向列存储的数据库,当表的字段很多时,可以把其中几个字段独立出来放在一部分机器上,而另外几个字段放到另一部分机器上,充分分散了负载的压力。如此复杂的存储结构和分布式的存储方式,带来的代价就是:即便是存储很少的数据,也不会很快

HBase并不是足够快,而是数据量很大的时候它慢的不明显。什么时候使用HBase呢,主要是以下两种情况:

  1. 单表数据量超过千万,而且并发量很大;
  2. 数据分析需求较弱,或者不需要那么实时灵活。

4. HBase简介

官方网站:hbase.apache.org HBase的原型是Google的BigTable论文,受到了该论文思想的启发,目前作为Apache的顶级项目来开发维护,用于支持结构化的数据存储。

HBase 特点

  1. 海量存储

Hbase适合存储PB级别的海量数据,在PB级别的数据以及采用廉价PC存储的情况下,能在几十到百毫秒内返回数据。这与Hbase的极易扩展性息息相关。正是因为Hbase良好的扩展性,才为海量数据的存储提供了便利。

  1. 列式存储

这里的列式存储其实说的是列族存储,Hbase是根据列族来存储数据的。列族下面可以有非常多的列,列族在创建表的时候就必须指定

  1. 极易扩展

Hbase的扩展性主要体现在两个方面,一个是基于上层处理能力(RegionServer)的扩展,一个是基于存储的扩展(HDFS)。 通过横向添加RegionSever的机器,进行水平扩展,提升Hbase上层的处理能力,提升Hbsae服务更多Region的能力。 备注:RegionServer的作用是管理region、承接业务的访问,这个后面会详细的介绍通过横向添加Datanode的机器,进行存储层扩容,提升Hbase的数据存储能力和提升后端存储的读写能力。

  1. 高并发

由于目前大部分使用Hbase的架构,都是采用的廉价PC,因此单个IO的延迟其实并不小,一般在几十到上百ms之间。这里说的高并发,主要是在并发的情况下,Hbase的单个IO延迟下降并不多。能获得高并发、低延迟的服务。

  1. 稀疏

稀疏主要是针对Hbase列的灵活性,在列族中,你可以指定任意多的列,在列数据为空的情况下,是不会占用存储空间的。

HBase逻辑结构

在这里插入图片描述 先从逻辑思维上对HBase的存储有个大致的理解:

  1. Table(表):

一个表由一个或者多个`列族构成。数据的属性。比如:name、age、TTL(超时时间)等等都在列族里边定义。定义完列族的表是个空表,只有添加了数据行以后,表才有数据。

  1. Column Family(列族):

在HBase里,可以将多个列组合成一个列族。建表的时候不用创建列,因为列是可增减变化的,非常灵活。唯一需要确定的就是列族,也就是说一个表有几个列族是一开始就定好的。此外表的很多属性,比如数据过期时间、数据块缓存以及是否使用压缩等都是定义在列族上的,而不是定义在表上或者列上。这一点与以往的关系型数据库有很大的差别。列族存在的意义是:HBase会把相同列族的列尽量放在同一台机器上,所以说想把某几个列放在一台服务器上,只需要给他们定义相同的列族。

  1. Row(行):

一个行包含多个列,这些列通过列族来分类。行中的数据所属的列族从该表所定义的列族中选取,不能选择这个表中不存在的列族。由于HBase是一个面向列存储的数据库,所以一个行中的数据可以分布在不同的服务器上

  1. RowKey(行键):

rowkey和MySQL数据库的主键比起来简单很多,rowkey必须要有,如果用户不指定的话,会有默认的。rowkey完全是由用户指定的一串不重复的字符串,另外,rowkey按照字典排序。一个rowkey对应的是一行数据!!!

  1. Region:

Region就是一段数据的集合。之前提到过高表的概念,把高表进行水平切分,假设分成两部分,那么这就形成了两个Region。注意一下Region的几个特性:

Region不能跨服务器,一个RegionServer可以有多个Region。 数据量小的时候,一个Region可以存储所有的数据;但是当数据量大的时候,HBase会拆分Region。 当HBase在进行负载均衡的时候,也有可能从一台RegionServer上把Region移动到另一服务器的RegionServer上。 Region是基于HDFS的,它的所有数据存取操作都是调用HDFS客户端完成的。

  1. RegionServer:

RegionServer就是存放Region的容器,直观上说就是服务器上的一个服务。负责管理维护Region。

HBase物理存储

以上是一个基本的逻辑结构,底层的物理存储结构才是重中之重的内容,看下图,并且将尝试换个角度解释上边的几个概念: 在这里插入图片描述

  1. NameSpace:

命名空间,类似于关系型数据库的 DatabBase概念,每个命名空间下有多个表。HBase有两个自带的命名空间,分别是hbasedefault,hbase 中存放的是 HBase 内置的表,default 表是用户默认使用的命名空间。

  1. Row:

HBase 表中的每行数据都由一个 RowKey 和多个 Column(列)组成,数据是按照 RowKey 的字典顺序存储的,并且查询数据时只能根据 RowKey 进行检索,所以 RowKey 的设计十分重要。

  1. Column :

HBase 中的每个列都由 Column Family(列族)和 Column Qualifier(列限定符)进行限 定,例如 info:name,info:age。建表时,只需指明列族,而列限定符无需预先定义。

  1. TimeStamp:

时间戳,用于标识数据的不同版本(version),每条数据写入时,如果不指定时间戳,系统会自动为其加上该字段,其值为写入 HBase 的时间。并且读取数据的时候一般只拿出数据的Type符合,时间戳最新的数据。HBase中通过Type来标识数据是否可用。因为HBase是基于HDFS的而HDFS是可以增删查而不支持改的

  1. Cell:

单元格,由{rowkey, column Family:column Qualifier, time Stamp} 唯一确定的单元。cell 中的数据是没有类型的,全部是字节码形式存储。

HBase与关系型数据库的对比

传统关系型数据库的表结构图如下: 在这里插入图片描述

其中每行都是不可分割的,也正是体现了数据库第一范式的原子性,也就是说三个列必须在一起,而且要被存储在同一台服务器上,甚至是同一个文件里面。

HBase的表架构如图所示:

在这里插入图片描述 HBase的每一个行都是离散的,因为列族的存在,所以一个行里面不同的列甚至被分配到了不同的服务器上。行的概念被减弱到了一个抽象的存在。在实体上,把多个列定义为一个行的关键词rowkey,也就是行这个概念在HBase中的唯一体现。

HBase的存储语句中必须·精确·的写出要将数据存放到哪个单元格,单元格由表:列族:行:列来唯一确定。用人话说就是要写清楚数据要被存储在哪个表的哪个列族的哪个行的哪个列。如果一行有10列,那么存储一行的数据就需要写明10行的语句。

HBase架构解析

1. 宏观图

在这里插入图片描述 从这张图上可以看到是一个HBase集群由一个Master(也可配置成多个,搞成HA)和多个RegionServer组成,之后再详细介绍RegionServer的架构。上面的图示说明了HBase的服务器角色构成,下边给出具体的介绍:

  • Master:

负责启动的时候分配Region到具体的RegionServer,执行各种管理操作,比如Region的分割合并。在HBase中,master的角色地位比其他类型的集群弱很多。数据的读写操作与他没有关系,它挂了之后,集群照样运行。具体的原因后边后详细介绍。但是master也不能宕机太久,有很多必要的操作,比如创建表、修改列族配置主要是DDL,以及更重要的分割合并都需要它的操作。

  • RegionServer:

RegionServer就是一台机器,在它上边有多个region。我们读写的数据就存储在Region中。

  • Region:

它是表拆分出来的一部分,HBase是一个会自动切片的数据库。当数据库过高时,就会进行拆分。

  • HDFS:

HBase的数据存储是基于HDFS的,它是真正承载数据的载体。

  • Zookeeper:

在本集群中负责存储hbase:meata的位置存储信息,客户端在写数据时需要先读取元数据信息。

2. RegionServer

在宏观架构图的最后一个RegionServer中可以看到 ,它的内部是多个Region的集合: 在这里插入图片描述 现在我们放大一下这个RegionServer的内部架构: 在这里插入图片描述

  • 一个WAL:

WAL时Write-Ahead Log的缩写,翻译为预写入日志。从名字大概也能猜出它的作用,当操作到达Region的时候,HBase先把操作写入到WAL中,然后把数据放入到基于内存实现的MemStore中,等到一定的时机再把数据刷写形成HFile文件,存储到HDFS上。WAL是一个保险机制,数据在写到MemStore之前,先写到WAL中,这样如果在刷写过程中出现事故,可以从WAL恢复数据。

  • 多个Region:

Region已经多次提到了,它就时是数据库的一部分,每一个Region都有起始的rowkey和结束的rowkey,代表了它存储的row的范围。

我们再放大Region的内部结构: 在这里插入图片描述 从图中可以看的出来,一个Region包含 多个Store:一个Region有多个Store,一个Store就是对应一个列族的数据,如图就有三个列族。再从最后一个Store中我们又可以看出,Store是由MemStore和HFile组成的。

3. WAL

预写入日志就是设计来解决宕机之后的操作恢复问题的,WAL是保存在HDFS上的持久化文件。数据到达Region的时候,先写入WAL,然后被加载到MemStore中。这样就算Region宕机了,操作没来得及执行持久化,也可以再重启的时候从WAL加载操作并执行。

  1. 如何启用WAL? WAL是默认开启的,也可以手动关闭它,这样增删改操作会快一点。但是这样做牺牲的是数据的安全性,所以不建议关闭。 关闭方法:
  • ounter(line
Mutation.setDurability(Durability.SKIP_WAL)
  1. 异步写入WAL 如果不惜通过关闭WAL来提高性能的话,还可以考虑一下折中的方案:异步写入WAL

正常情况下,客户端提交的put、delete、append操作来到Region的时候,先调用HDFS的客户端写到WAL中。哪怕只有一个改动,也会调用HDFS的接口来写入数据。可以想象到,这种方式尽可能的保证了数据的安全性,代价这种方式频繁的消耗资源。

如果不想关闭WAL,又不想每次都耗费那么大的资源,每次改动都调用HDFS客户端,可以选择异步的方式写入WAL:

  • ounter(line
Mutation.setDurability(Durability.ASYNC_WAL)

这样设定以后,Region会等到条件满足的时候才将操作写到WAL。这里的条件指的是间隔多久,写一次,默认的时间间隔是1s。

如果异步写入数据的时候出错了怎么办呢?比如客户端的操作现在在Region内存中,由于时间间隔未到1s,操作还没来得及写入到WAL,Region挂了(邪门不?就差那么一丢丢不到1s)。出错了是没有任何事务可以保证的。

  1. WAL滚动 之前学习过MapReduce的shuffle机制,所以猜得到WAL是一个唤醒的滚动日志数据结构,因为这种结构不会导致占用的空间持续变大,而且写入效率也最高。

通过wal日志切换,这样可以避免产生单独的过大的wal日志文件,这样可以方便后续的日志清理(可以将过期日志文件直接删除)另外如果需要使用日志进行恢复时,也可以同时解析多个小的日志文件,缩短恢复所需时间。

WAL的检查间隔由hbase.regionserver.logroll.period定义,默认值是一个小时。检查的内容是把当前WAL中的操作跟实际持久化到HDFS上的操作做比较,看哪些操作已经被持久化了,如果已经被持久化了,该WAL就会被移动到HDFS上的.oldlogs文件夹下。

一个WAL实例包含多个WAL文件。WAL文件的最大数量可以手动通过参数配置。

其它的触发滚动的条件是:

  1. WAL的大小超过了一定的阈值。
  2. WAL文件所在的HDFS文件块快要满了。
  1. WAL归档和删除 归档:WAL创建出来的文件都会放在/hbase/.log下,在WAL文件被定为归档时,文件会被移动到/hbase/.oldlogs下 删除:判断:是否此WAL文件不再需要,是否没有被其他引用指向这个WAL文件

会引用此文件的服务:

  1. TTL进程:该进程会保证WAL文件一直存活直到达到hbase.master.logcleaner.ttl定义的超时时间(默认10分钟)为止。
  2. 备份(replication)机制:如果你开启了HBase的备份机制,那么HBase要保证备份集群已经完全不需要这个WAL文件了,才会删除这个WAL文件。这里提到的replication不是文件的备份数,而是0.90版本加入的特性,这个特性用于把一个集群的数据实时备份到另外一个集群。如果你的手头就一个集群,可以不用考虑这个因素。 只有当该WAL文件没有被以上两种情况引用的时候,才会被系统彻底清除掉
4. Store

解释完了WAL,放大一下Store的内部架构:在这里插入图片描述 Store有两个重要的部分:

  1. MemStore:

每个Store都有一个MemStore实例。数据写入到WAL之后就会被放入MemStore中。MemStore是内存的存储对象,只有到达一定的时机才会被刷写到HFile中去。

  1. HFile:

在Store中有多个HFile,每次刷写都会形成一个HFile文件落盘在HDFS上。HFile直接跟HDFS打交道,它是数据存储的实体。

这里提出一点疑问: 客户端的操作到达Region时,先将数据写到WAL中,而WAL是存储在HDFS上的。所以就相当于数据已经持久化了,那么为什么还要从WAL加载到MemStore中,再刷写形成HFile存到HDFS上呢?

简单的说就是:数据进入HFile之前就已经被持久化了,为什么还要放入MemStore

这是因为HDFS支持文件的创建、追加、删除,但是不能修改。对于一个数据库来说,数据的顺序是非常重要的。第一次WAL的持久化是为了保证数据的安全性,无序的。再读取到MemStore中,是为了排序后存储。所以MemStore的意义在于维持数据按照rowkey的字典序排列,而不是做一个缓存提高写入效率。 补一张图,对比着来看,关于MemStore刷写: 在这里插入图片描述

HBase架构图解析

在这里插入图片描述 从图中可以看出Hbase是由ClientZookeeperMasterHRegionServerHDFS等几个组件组成,下面来介绍一下几个组件的相关功能:

  1. Client

Client包含了访问Hbase的接口,另外Client还维护了对应的cache来加速Hbase的访问,比如cache的.META.元数据的信息。

  1. Zookeeper

HBase通过Zookeeper来做master的高可用、RegionServer的监控、元数据的入口以及集群配置的维护等工作。具体工作如下: 通过Zoopkeeper来保证集群中只有1个master在运行,如果master异常,会通过竞争机制产生新的master提供服务 通过Zoopkeeper来监控RegionServer的状态,当RegionSevrer有异常的时候,通过回调的形式通知MasterRegionServer上下线的信息 通过Zoopkeeper存储元数据的统一入口地址

  1. Hmaster master节点的主要职责如下:
  1. 为RegionServer分配Region
  2. 维护整个集群的负载均衡
  3. 维护集群的元数据信息
  4. 发现失效的Region,并将失效的Region分配到正常的RegionServer上
  5. 当RegionSever失效的时候,协调对应Hlog的拆分
  1. HregionServer HregionServer直接对接用户的读写请求,是真正的干活的节点。它的功能概括如下:
  1. 管理master为其分配的Region
  2. 处理来自客户端的读写请求
  3. 负责和底层HDFS的交互,存储数据到HDFS
  4. 负责Region变大以后的拆分
  5. 负责Storefile的合并工作
  1. HDFS HDFS为Hbase提供最终的底层数据存储服务,同时为HBase提供高可用(Hlog存储在HDFS)的支持,具体功能概括如下:
  1. 提供元数据和表数据的底层分布式存储服务
  2. 数据多副本,保证的高可靠和高可用性

参考

大数据技术之HBase-李海波 HBase概述