HBase总结

为什么使用HBase

HBase是开源版的BigTable。

  • 高性能的列式存储
  • 高可靠的弹性伸缩

HBase VS RDBMS(传统关系数据库)

  • 数据类型
    RDBMS:关系模型,丰富的数据类型和存储方式
    HBase存的数据都是字符串,用户根据自己的需要解析字符串
  • 数据操作
    RDBMS:丰富的CRUD操作,多表连接
    HBase:不存在复杂的表与表之间的关系,只有简单的插入、查询、删除、清空等
  • 存储模式
    RDBMS:基于行模式存储,行被连续存储在磁盘页,在读取时需要顺序扫描行并筛选,如果每一行只有少量数据值对于查询是有用的,那么基于行模式的存储就会浪费许多磁盘空间和内存带宽;
    HBase:基于列存储,每个列族都由几个文件保存,不同列族的文件是分离的,可以支持更大并发的查询,因为仅需处理查询所需的列,而不需要像RDBMS那样处理整行;同一个列族的数据会被一起进行压缩,由于同一列族内的数据相似度较高,因此可以获得较高的压缩比。
  • 数据索引
    RDBMS会根据需要构建多个索引
    HBase只有一个索引:行键
  • 数据维护
    RDBMS更新后老数据会被替换
    HBase更新只会生成一个新版本,老版本数据仍然保留。
  • 可伸缩性
    RDBMS很难实现横向扩展。
    HBase可以灵活地水平扩展。
  • 事务
    HBase不支持事务,不能实现跨行更新的原子性。

使用HBase

  • Native Java API
  • HBase Shell
  • Thrift Gateway
  • REST Gateway
  • Pig
  • Hive

启动HBase

从官网下载HBase:https://hbase.apache.org/
注意兼容性:http://hbase.apache.org/book.html#hadoop
Hadoop安装后只包含HDFS和MapReduce,并不包含HBase,需要在Hadoop之上继续安装HBase。

编辑配置文件conf/hbase-site.xml,可以修改数据写入目录:

1
2
3
4
5
6
<configuration>
<property>
<name>hbase.rootdir</name>
<value>file:///DIRECTORY/hbase</value>
</property>
</configuration>

将 DIRECTORY 替换成期望写文件的目录. 默认 hbase.rootdir 是指向 /tmp/hbase-${user.name} ,重启时数据会丢失。
编辑环境变量conf/hbase-env.sh

1
2
3
4
# 如果JAVA_HOME已经有了就不用设置了
export JAVA_HOME=...
# 表示由hbase自己管理ZooKeeper,不需要单独的ZooKeeper
export HBASE_MANAGES_ZK=true

启动hbase:

1
./bin/start-hbase.sh

关闭hbase:

1
./bin/stop-hbase.sh

查看日志

如果启动失败后者后续的命令执行失败了,可以查看根目录下的日志:

1
vim logs/hbase-hgc-master-hgc-X555LD.log

Shell

使用shell连接HBase:

1
./bin/hbase shell
1
2
# 查看命令列表,要注意的是表名,行和列需要加引号
> help

create - 创建表、列族:

1
2
3
4
5
6
7
8
9
10
11
# 创建表
> create 'test', 't1'
# 创建表t1,列族为f1,列族版本号为5
> create 't1', {NAME => 'f1', VERSIONS => 5}
# 创建表t1,有3个列族分别为f1、f2、f3
> create 't1', {NAME => 'f1'}, {NAME => 'f2'}, {NAME => 'f3'}
> create 't1', 'f1', 'f2', 'f3'
# 创建表t1,根据分割算法HexStringSplit分布在15个Region里
> create 't1', 'f1', {NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'}
# 创建表t1,指定切分点
> create 't1', 'f1', {SPLIT => ['10', '20', '30', '40']}

list - 查询表信息

1
> list

put - 向表、行、列指定的单元格添加数据:

1
2
# 向表t1中的行row1和列f1:c1所对应的单元格中添加数据value1,时间戳为1421822284898
> put 't1', 'row1', 'f1:c1', 'value1', 1421822284898

get - 获取单元格数据

1
2
3
4
# 从表t1获取数据,行row1、列f1,时间范围为TIMERANGE,版本号为1的数据
> get 't1', 'row1', {COLUMN => 'f1:c1', TIMERANGE => [0, 1421822284900], VERSIONS => 1}
# 获取表t1、行row1、列f1上的数据
> get 't1', 'row1', 'f1'

Java API

原理

数据模型


  • HBase使用表来组织数据,表由行和列组成,列又划分为若干列族。

  • 每个表由若干行组成,每个行由行键标识。
    访问表中的行只能通过:单个行键、行键区间、全表扫描实现。
  • 列族
    列族是基本的访问控制单元。
    列族里的数据通过列限定符来定位。
  • 单元格
    在表中,可以通过行、列族和列限定符确定一个单元格Cell
    单元格中存储的数据没有数据类型,总是被视为字节数组。
    每个单元格中可以保存一个数据的多个版本,每个版本对应一个不同的时间戳。
  • 时间戳
    单元格中的数据通过时间戳进行索引,每次对一个单元格执行增删改操作都会隐式生成并存储一个时间戳。
  • 数据坐标
    HBase中可以根据<行键, 列族, 列限定符, 时间戳>的四元组来确定一条数据。

面向列存储

  • 行式数据库
    行式存储将每一行连续地存储在磁盘页中,要找一行数据就要连续地扫描磁盘。
    如果每行只有少量属性的值对查询有用,那么行式存储就会浪费非常多的磁盘空间和内存带宽。
    行式数据库主要适合于小批量的数据处理,如联机事务型数据处理,常见实现如MySQL。
  • 列式数据库
    以列为单位进行存储,关系中多个元组的同一列值会被存储到一起,而同一个元组中不同列则通常会被分别存储到不同的磁盘页中。
    列式数据库主要适用于批量数据处理Ad-Hoc Query,优点是可以降低IO开销,支持大量并发用户查询,因为仅需要处理可以回答这些查询的列,而不是分类整理与特定查询无关的数据行;具有较高的数据压缩比。
    列式数据库主要用于数据挖掘、决策支持和地理信息系统等查询密集型系统中,因为一次查询就可以得出结果,而不必每次都要遍历所有的数据库。
    缺点1:连接操作效率低,执行连接操作时需要昂贵的元组重构代价,因为一个元组的不同属性被分散到不同磁盘页中存储,当需要一个完整的元组时,就要从多个磁盘页中读取相应字段的值来重新组合得到原来的一个元组。
    缺点2:不适合频繁更新同一行元组的场景,理由同上,因为一个元组的不同属性分散到不同的磁盘页,因此写操作频繁会导致不能很好命中缓冲。因此HBase更适合数据被存储后不会发生修改的场景。

HBase架构

HBase系统架构

HBase的实现包含3个主要的功能组件:

  • 一个Master主服务器
    Master负责管理HBase表的分区(Region)信息
    比如一个表包含哪些Region,这些Region被划分到哪台Region服务器上。
    同时也负责维护Region服务器列表,实时监测集群中的Region服务器,把特定的Region分配到可用的Region服务器上,并确保整个集群内部不同Region服务器之间的负载均衡。
    负责Region集群的故障转移,当某个Region服务器因出现故障而失效时,Master会把故障服务器上存储的Region重新分配给其他可用的Region服务器。
    负责模式变化,如表和列族的创建。
  • 许多个Region服务器
    Region服务器负责存储和维护分配给自己的Region,处理来自客户端的读写请求。
  • 库函数
    链接到每个客户端
    客户端并不是直接从Master上读取数据,而是先获取Region的存储位置后再直接从Region服务器上读取数据。而且需要注意的是客户端不直接和Master交互,而是从ZooKeeper上获取Region信息,这可以保证Master的负载尽可能小。

客户端

客户端会访问HBase的服务端接口,并缓存已经访问过的Region位置信息,用来提高后续访问数据的速度。

ZooKeeper服务器

  • Master将Region服务器的状态注册到ZooKeeper
  • Master选举。
  • 保存-ROOT-表和Master的地址,然后客户端可以根据-ROOT-表来一级一级找到所需的数据

Master服务器

  • 管理对表的CRUD操作
  • 实现不同Region之间的负载均衡
  • 在Region分裂或合并后,重新调整Region的分布
  • 将发生故障失效的Region迁移到其他Region服务器

Region服务器

Region服务器的主要职责:

  • 维护分配给自己的Region
  • 响应用户的读写请求

Region一般采用HDFS作为底层文件存储系统,并依赖HDFS来实现数据复制维护数据副本的功能。

Region的定位 - 如何找到一个Region

每个Region都有一个RegionID来标识它的唯一性,要定位一个Region可以使用<表名, 开始主键, RegionID>的三元组。
HBase还会维护一张<Region标识符, Region服务器>的映射表,被称为元数据表,又名 .META.表
如果一个HBase表中的Region特别多,一个服务器存不下.META.表,则.META.表也会被分区存储到不同的服务器上,并用一张根数据表来维护所有元数据的具体位置,又名 -ROOT-表,-ROOT-表是不能被分割的,永远只会被存储到一个唯一的Region中。

HBase元数据的三层结构

Region与行

HBase中的行是根据行键的字典序进行维护的,表中包含的行的数量可能非常大,需要通过行键对表中的行进行分区(Region)。
Region包含了位于某个值区间内的所有数据,它是负载均衡数据分发的基本单位,这些Region会被Master分发到不同的Region服务器上。
每个Region的默认大小是100MB200MB,当一个Region包含的数据达到一个阈值时,会被自动分裂成两个新的Region,通常一个Region服务器上会放置101000个Region。

Region服务器的存储结构

Region服务器内部维护了一系列Region对象和一个HLog文件

  • 每个Region由多个Store组成,每个Store对应了表中的一个列族的存储。
    每个Store又包含一个MemStore和若干StoreFile,前者是内存缓存,后者是磁盘文件,使用B树结构组织,底层实现方式是HDFS的HFile(会对内容进行压缩)。

  • HLog是磁盘上的记录文件,记录着所有的更新操作。

  • 每个Store对应了表中一个一个列族,包含了一个MemStore若干个StoreFile
    其中,MemStore是在内存中的缓存,保存最近更新的数据;StoreFile由HDFS的HFile实现,底层是磁盘中的文件,这些文件都是B树结构,方便快速读取,而且HFile的数据块通常采用压缩方式存储,可以大大减少网络和磁盘IO。
    Region存储结构

流程 - 用户读写数据

  • 写入流程
    用户写入 -> 路由Region服务器 -> HLog -> MemStore -> commit()返回给客户端

    HLog是WAL(Write Ahead Log),因此在MemStore之前写入

  • 读取流程
    用户读取 -> 路由Region服务器 -> MemStore -> StoreFile

流程 - 缓存刷新

  • 周期性刷新
    周期性调用Region.flushcache() -> 将MemStore缓存中的内容写到磁盘StoreFile中 -> 清空缓存 -> 在HLog中写入一个标记表示缓存已刷到StoreFile
    每次缓存刷新都会在磁盘上生成一个新的StoreFile文件,因此每个Store会包含多个StoreFile文件
  • 启动刷新
    启动时检查HLog -> 确认最后一次刷新后是否还有发生写入 -> 如果有发生则将这些更新写入MemStore -> 刷新缓存写入到StoreFile -> 删除旧的HLog文件 -> 开始为用户提供数据访问服务

    如果最后一次刷新后没有新数据,说明所有数据已经被永久保存。

流程 - StoreFile合并

如《缓存刷新》流程所述,每次MemStore刷新都会在磁盘上生成一个新的StoreFile,这样系统中每个Store都会有多个StoreFile,要找到Store中某个值就必须查找所有这些StoreFile文件,非常耗时。
因此,为了减少耗时,系统会调用Store.compact()把多个StoreFile合并成一个大文件。
这个合并操作比较耗费资源,因此只会在StoreFile文件的数量达到一个阈值时才会触发合并操作。

Store的工作原理

如《Region存储结构》所示,Region服务器是HBase的核心模块,而Store是Region服务器的核心,每个Store对应了表中的一个列族的存储,每个Store包含一个MemStore缓存和若干个StoreFile文件。

  • 写入数据优先写入MemStore,写满时刷新到StoreFile
  • 随着StoreFile数量不断增加,达到阈值时触发文件合并操作
  • 当StoreFile文件越来越大,达到阈值时,会触发文件分裂操作,同时当前的一个父Region会被分裂成2个子Region,父Region会下线,新分裂出的2个子Region会被Master分配到相应的Region服务器上。

StoreFile的合并和分裂

HLog的工作原理

在分布式环境下,系统出错可能导致数据丢失,比如Region故障导致MemStore缓存中的数据被清空了。HBase采用HLog来保证系统故障时的恢复。

  • HLog是每个Region服务器仅配置一个
    一个Region服务器包含多个Region,这些一台Region服务器上的Region会共用一个HLog
    这样做的好处是:一台Region服务器不需要打开多个日志文件,减少磁盘寻址次数,提高写操作性能。
    这样的坏处是:如果一个Region服务器发生故障,为了恢复其上的Region对象,需要按所属Region对HLog进行拆分,然后分发到其他Region服务器上执行恢复操作。
  • HLog是WAL(Write Ahead Log)
    用户数据需要先写HLog才能写入MemStore,并且直到MemStore缓存内容对应的日志已经被写入磁盘,该缓存内容才会被刷新到磁盘。
  • 数据恢复
    ZooKeeper会实时监测每个Region服务器的状态,当某个Region服务器发生故障,ZooKeeper会通知Master。
    Master会处理该故障服务器上的HLog文件,注意HLog会包含来自多个Region对象的日志记录,系统会根据每条日志所属的Region对象对HLog数据进行拆分,分别放到对应Region对象的目录下,然后再将失效的Region重新分配到可用的Region服务器中,并把与该Region对象相关的HLog日志记录也发送给相应的Region服务器。
    Region服务器领取到分配给自己的Region对象以及与之相关的HLog日志记录以后,会重演一遍日志记录中的操作,把日志记录中的数据写入MemStore缓存,然后刷新到磁盘的StoreFile文件中,完成数据恢复。