HBase 的存储结构和关系型数据库不一样, HBase 面向半结构化数据进行存储. 所以, 对于结构化的 SQL 语言查询, HBase 自身并没有接口支持.
1. 概述
HBase 的存储结构和关系型数据库不一样, HBase 面向半结构化数据进行存储. 所以, 对于结构化的 SQL 语言查询, HBase 自身并没有接口支持. 在大数据应用中, 虽然也有 SQL 查询引擎可以查询 HBase, 比如 Phoenix,Drill 这类. 但是阅读这类 SQL 查询引擎的底层实现, 依然是调用了 HBase 的 Java API 来实现查询, 写入等操作. 这类查询引擎在业务层创建 Schema 来映射 HBase 表结构, 然后通过解析 SQL 语法数, 最后底层在调用 HBase 的 Java API 实现.
本篇内容笔者并不是给大家来介绍 HBase 的 SQL 引擎, 我们来关注 HBase 更低层的东西, 那就是 HBase 的存储实现. 以及跨集群的 HBase 集群数据迁移.
2. 内容
HBase 数据库是唯一索引就是 RowKey, 所有的数据分布和查询均依赖 RowKey. 所以, HBase 数据库在表的设计上会有很严格的要求, 从存储架构上来看, HBase 是基于分布式来实现的, 通过 Zookeeper 集群来管理 HBase 元数据信息, 比如表名就存放在 Zookeeper 的 / hbase/table 目录下. 如下图所示:
2.1 Architecture
HBase 是一个分布式存储系统, 底层数据存储依赖 Hadoop 的分布式存储系统(HDFS).HBase 架构分三部分来组成, 它们分别是: ZooKeeper,HMaster 和 HRegionServer.
ZooKeeper:HBase 的元数据信息, HMaster 进程的地址, Master 和 RegionServer 的监控维护 (节点之间的心跳, 判断节点是否下线) 等内容均需要依赖 ZooKeeper 来完成. 是 HBase 集群中不可缺少的核心之一.
HMaster:HMaster 进程在 HBase 中承担 Master 的责任, 负责一些管理操作, 比如给表分配 Region, 和数据节点的心跳维持等. 一般客户端的读写数据的请求操作不会经过 Master, 所以在分配 JVM 内存的适合, 一般 32GB 大小即可.
HRegionServer:HRegionServer 进程在 HBase 中承担 RegionServer 的责任, 负责数据的存储. 每个 RegionServer 由多个 Region 组成, 一个 Region 维护一定区间的 RowKey 的数据. 如下图所示:
图中 Region(dn2:16030)维护的 RowKey 范围为 0001~0002.
HBase 为了保证高可用性(HA), 一般都会部署两个 Master 节点, 其中一个作为主, 另一个作为 Backup 节点. 这里谁是主, 谁是 Backup 取决于那个 HMaster 进程能从 Zookeeper 上对应的 Master 目录中竞争到 Lock, 持有该目录 Lock 的 HMaster 进程为主 Master, 而另外一个为 Backup, 当主 Master 发生意外或者宕机时, Backup 的 Master 会立刻竞争到 Master 目录下的 Lock 从而接管服务, 成为主 Master 对外提供服务, 保证 HBase 集群的高可用性.
2.2 RegionServer
HBase 负责数据存储的就是 RegionServer, 简称 RS. 在 HBase 集群中, 如果只有一份副本时, 整个 HBase 集群中的数据都是唯一的, 没有冗余的数据存在, 也就是说 HBase 集群中的每个 RegionServer 节点上保存的数据都是不一样的, 这种模式由于副本数只有一份, 即是配置多个 RegionServer 组成集群, 也并不是高可用的. 这样的 RegionServer 是存在单点问题的. 虽然, HBase 集群内部数据有 Region 存储和 Region 迁移机制, RegionServer 服务的单点问题可能花费很小的代价可以恢复, 但是一旦停止 RegionServre 上含有 ROOT 或者 META 表的 Region, 那这个问题就严重, 由于数据节点 RegionServer 停止, 该节点的数据将在短期内无法访问, 需要等待该节点的 HRegionServer 进程重新启动才能访问其数据. 这样 HBase 的数据读写请求如果恰好指向该节点将会收到影响, 比如: 抛出连接异常, RegionServer 不可用等异常.
3. 日志信息
HBase 在实现 WAL 方式时会产生日志信息, 即 HLog. 每一个 RegionServer 节点上都有一个 HLog, 所有该 RegionServer 节点上的 Region 写入数据均会被记录到该 HLog 中. HLog 的主要职责就是当遇到 RegionServer 异常时, 能够尽量的恢复数据.
在 HBase 运行的过程当中, HLog 的容量会随着数据的写入越来越大, HBase 会通过 HLog 过期策略来进行定期清理 HLog, 每个 RegionServer 内部均有一个 HLog 的监控线程. HLog 数据从 MemStore Flush 到底层存储 (HDFS) 上后, 说明该时间段的 HLog 已经不需要了, 就会被移到 "oldlogs" 这个目录中, HLog 监控线程监控该目录下的 HLog, 当该文件夹中的 HLog 达到 "hbase.master.logcleaner.ttl"(单位是毫秒)属性所配置的阀值后, 监控线程会立即删除过期的 HLog 数据.
4. 数据存储
HBase 通过 MemStore 来缓存 Region 数据, 大小可以通过 "hbase.hregion.memstore.flush.size"(单位 byte)属性来进行设置. RegionServer 在写完 HLog 后, 数据会接着写入到 Region 的 MemStore. 由于 MemStore 的存在, HBase 的数据写入并非是同步的, 不需要立刻响应客户端. 由于是异步操作, 具有高性能和高资源利用率等优秀的特性. 数据在写入到 MemStore 中的数据后都是预先按照 RowKey 的值来进行排序的, 这样便于查询的时候查找数据.
5.Region 分割
在 HBase 存储中, 通过把数据分配到一定数量的 Region 来达到负载均衡. 一个 HBase 表会被分配到一个或者多个 Region, 这些 Region 会被分配到一个或者多个 RegionServer 中. 在自动分割策略中, 当一个 Region 中的数据量达到阀值就会被自动分割成两个 Region.HBase 的表中的 Region 按照 RowKey 来进行排序, 并且一个 RowKey 所对应的 Region 只有一个, 保证了 HBase 的一致性.
一个 Region 中由一个或者多个 Store 组成, 每个 Store 对应一个列族. 一个 Store 中包含一个 MemStore 和多个 Store Files, 每个列族是分开存放以及分开访问的. 自动分割有三种策略, 分别是:
ConstantSizeRegionSplitPolicy: 在 HBase-0.94 版本之前是默认和唯一的分割策略. 当某一个 Store 的大小超过阀值时(hbase.hregion.max.filesize, 默认时 10G),Region 会自动分割.
IncreasingToUpperBoundRegionSplitPolicy: 在 HBase-0.94 中, 这个策略分割大小和表的 RegionServer 中的 Region 有关系. 分割计算公式为: Min(R*R*'hbase.hregion.memstore.flush.size','hbase.hregion.max.filesize'), 其中, R 表示 RegionServer 中的 Region 数. 比如: hbase.hregion.memstore.flush.size=256MB,hbase.hregion.max.filesize=20GB, 那么第一次分割的大小为 Min(1*1*256,20GB)=256MB, 也就是在第一次大到 256MB 会分割成 2 个 Region, 后续以此公式类推计算.
KeyPrefixRegionSplitPolicy: 可以保证相同前缀的 RowKey 存放在同一个 Region 中, 可以通过 hbase.regionserver.region.split.policy 属性来指定分割策略.
6. 磁盘合理规划
部署 HBase 集群时, 磁盘和内存的规划是有计算公式的. 随意分配可能造成集群资源利用率不高导致存在浪费的情况. 公式如下:
# 通过磁盘维度的 Region 数和 Java Heap 维度的 Region 数来推导 Disk Size/(RegionSize*ReplicationFactor)=Java Heap*HeapFractionForMemstore/(MemstoreSize/2)
公式中对应的 hbase-site.xml 文件中的属性中, 见下表:
在实际使用中, MemstoreSize 空间打下只使用了一半 (1/2) 的容量. 举个例子, 一个 RegionServer 的副本数配置为 3,RegionSize 为 10G,HBase 的 JVM 内存分配 45G,HBase 的 MemstoreSize 为 128M, 那此时根据公式计算得出理想的磁盘容量为 45G*1024*0.4*2*10G*1024*3/128M=8.5T 左右磁盘空间. 如果此时, 分配一个节点中挂载 10 个可用盘, 共 27T. 那将有两倍的磁盘空间不匹配造成浪费. 为了提升磁盘匹配度, 可以将 RegionSize 值提升至 30G, 磁盘空间计算得出 25.5T, 基本和 27T 磁盘容量匹配.
7. 数据迁移
对 HBase 集群做跨集群数据迁移时, 可以使用 Distcp 方案来进行迁移. 该方案需要依赖 MapReduce 任务来完成, 所以在执行迁移命令之前确保新集群的 ResourceManager,NodeManager 进程已启动. 同时, 为了查看迁移进度, 推荐开启 proxyserver 进程和 historyserver 进程, 开启这 2 个进程可以方便在 ResourceManager 业务查看 MapReduce 任务进行的进度. 迁移的步骤并不复杂, 在新集群中执行 distcp 命令即可. 具体操作命令如下所示:
# 在新集群的 NameNode 节点执行命令[hadoop@nna ~]$ hadoop distcp -Dmapreduce.job.queue.name=queue_0001_01 -update -skipcrccheck -m 100 hdfs://old_hbase:9000/hbase/data/tabname /hbase/data/tabname
为了迁移方便, 可以将上述命令封装成一个 Shell 脚本. 具体实现如下所示:
- #! /bin/bash
- for i in `cat /home/hadoop/hbase/tbl`
- do
- echo $i
- hadoop distcp -Dmapreduce.job.queue.name=queue_0001_01 -update -skipcrccheck -m 100 hdfs://old_hbase:9000/hbase/data/$i /hbase/data/$i
- done
- hbase hbck -repairHoles
将待迁移的表名记录在 / home/hadoop/hbase/tbl 文件中, 一行代表一个表. 内容如下所示:
- [hadoop@nna ~]$ vi /home/hadoop/hbase/tbl
- # 表名列表
- tbl1
- tbl2
- tbl3
- tbl4
最后, 在循环迭代迁移完成后, 执行 HBase 命令 "hbase hbck -repairHoles" 来修复 HBase 表的元数据, 如表名, 表结构等内容, 会从新注册到新集群的 Zookeeper 中.
8. 总结
HBase 集群中如果 RegionServer 上的 Region 数量很大, 可以适当调整 "hbase.hregion.max.filesize" 属性值的大小, 来减少 Region 分割的次数. 在执行 HBase 跨集群数据迁移时, 使用 Distcp 方案来进行, 需要保证 HBase 集群中的表是静态数据, 换言之, 需要停止业务表的写入. 如果在执行 HBase 表中数据迁移时, 表持续有数据写入, 导致迁移异常, 抛出某些文件找不到.
来源: http://stor.51cto.com/art/201810/585872.htm