一、概述
1.1 定义
HBase – Hadoop Database,是一个高可靠性、高性能、面向列、可伸缩、实时读写的分布式数据库。
1.2 优点&缺点(与关系型数据库对比)
- 优点
- 海量存储
- 良好的扩展性
- 稀疏性
- 高性能
- 多版本
- TTL
- 缺点
- 不支持复杂的聚合运算(Join、GroupBy等)
- 没有二级索引 rowkey--设计
- 不支持跨行事务
1.3 版本变迁
本课程采用的版本是1.4.10,目前HBase社区推荐的最新稳定版本
1.4 数据模型
- 逻辑视图
- 逻辑图
- 使用关系型数据库的概念来描述HBase
- namespace:名称空间,组织表。
- table:表,一个表包含多行数据。
- row:行,一行数据包含一个唯一标识rowkey和多个 column。
- column
- column family(列簇):创建表的时候指定
- column qualifier(列名):可以动态添加
- 使用方式:family:qualifier
- timestamp:时间戳,每个cell在写入HBase的时候都会默认分配一个时间戳作为该cell的版本。
- cell:单元格,存储具体数据
- 物理视图
- 列簇式存储
- 同一列簇存储在同一目录下
- 同一列存储在同一个文件中
- 存储格式:多维稀疏排序Map
- key的构成:rowkey+family+qualifier+type+timestamp
- value的构成:字节形式存储
1.5 简单物理结构
- 客户端
- 提供访问接口
- Zookeeper
- Master高可用
- 管理核心元数据
- 分布式锁
- Master
- 处理用户的管理请求
- 管理集群RegionServer
- 清理过期日志以及文件
- RegionServer
- 响应用户IO请求,是HBase中最核心的模块
- HDFS
- HBase内部封装了一个名为DFSClient的HDFS客户端组件,负责对HDFS的实际数据进行读写
二、相关组件
2.1 ZooKeeper
- 概念
- 高性能、分布式的分布式应用程序的协调服务。
- 是一个树形目录服务
- 提供功能
- 配置管理
- 分布式锁
- 集群管理
- ZooKeeper集群
- 一般由奇数个(3个或者5个)节点构成
- 容灾能力:(n-1)/2,过半服务可用,整体服务就可用
- HBase中ZooKeeper核心配置
<property>
<--! zookeeper地址 -->
<name>hbase.zookeeper.quorum</name>
<value>nn1.hadoop,nn2.hadoop,dn1.hadoop</value>
</property>
<property>
<--! 2181为zookeeper的clientPort -->
<name>hbase.zookeeper.property.clientPort</name>
<value>2181</value>
</property> - ZooKeeper存储的HBase信息说明
data-draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal"> 节点名称说明
- Inspector软件查看数据
2.2 HDFS
- HDFS在HBase系统中作用
- HBase-HDFS体系是典型的计算存储分离架构。
- HBase本身不存储文件,它只规定文件的格式及文件内容,实际存储由HDFS文件实现
- HBase不提供机制保证存储数据的高可靠,数据的高可靠有HDFS的多副本机制保证
- HBase在HDFS中的目录结构
le data-draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal">
三、安装部署
3.1 本地/单机模式
- 本地模式不依赖于HDFS存储,将文件存储到操作系统本地目录。
3.2 伪分布式
- 即可以运行在本地文件系统上也可以运行在HDFS。
- 所有进程运行在同一个节点(机器)上,不同进程运行在不同的JVM当中。
3.3 完全分布式
- 进程运行在多个服务器集群中。
- 分布式依赖于HDFS系统,因此布署HBase之前一定要有一个正常工作的HDFS集群。
3.4 HA模式
- 在完全分布式的基础上达到HMaster的高可用。
- 集群规划:
nn1.hadoopnn2.hadoopdn1.hadoopdn2.hadoopdn3.hadoop
3.5 HA模式安装步骤
- 软件环境
- JDK:1.8
- ZooKeeper:3.4.8
- Hadoop:2.7.4
- HBase:1.4.10
- 解压安装文件
- 创建软连接
- 配置环境变量
- 修改配置文件
- hbase-site.xml
<configuration>
<!--开启完全分布式模式-->
<property>
<name>hbase.cluster.distributed</name>
<value>true</value>
</property>
<!--hbase 数据存放地址,这里ns1对应的是hdfs HA的地址-->
<property>
<name>hbase.rootdir</name>
<value>hdfs://ns1/hbase</value>
</property>
<!--这里注意了,只需端口即可,不必再写主机名称-->
<property>
<name>hbase.master.port</name>
<value>60000</value>
</property>
<!--本地文件系统的临时文件夹-->
<property>
<name>hbase.tmp.dir</name>
<value>/data/hbase</value>
</property>
<!--zookeeper 配置-->
<property>
<name>hbase.zookeeper.quorum</name>
<value>nn1.hadoop,nn2.hadoop,dn1.hadoop</value>
</property>
<property>
<name>hbase.zookeeper.property.clientPort</name>
<value>2181</value>
</property>
<!-- ZooKeeper的zoo.conf中的配置(dataDir所设定的位置),快照的存储位置 -->
<property>
<name>hbase.zookeeper.property.dataDir</name>
<value>/data/zookeeper</value>
</property>
</configuration> - hbase-env.sh
# JDK
export JAVA_HOME=/usr/bin/java
# hbase 安装目录下创建logs目录
export HBASE_LOG_DIR=${HBASE_HOME}/logs
# hbase 安装目录下创建pids目录
export HBASE_PID_DIR=${HBASE_HOME}/pids
# 使用外部zk,true表示使用hbase自带的zk
export HBASE_MANAGES_ZK=false - regionservers
dn1.hadoop
dn2.hadoop
dn3.hadoop
- 拷贝Hadoop的hdfs-site.xml和core-site.xml文件到HBase的conf目录
- 分发HBase文件
3.6 集群的启动和关闭
- 启动集群
# 启动HBase
start-hbase.sh 或者 hbase-daemon.sh start master/regionserver
# 启动备用HMaster
hbase-daemon.sh start master - 关闭集群
# 关闭备用HMaster
hbase-daemon.sh stop master
# 关闭HBase
stop-hbase.sh
3.7 HMaster的高可用测试
- 测试步骤
- 结束HMaster进程
- 访问集群看是否可以正常使用
3.8 HBase的WebUI
- 相关配置
<property>
<name>hbase.master.info.port</name>
<value>16010</value>
</property>
1.0版本后默认配置为16010
四、客户端-Shell
4.1 基础命令
- hbase shell 进入hbase命令行界面
- 常用基础命令
le data-draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal">
4.2 DDL
- 操作namespace
le data-draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal"> 两个内置的预定义的namespace hbase:系统namespace,用于包含hbase的内部表default:所有未指定namespace的表都自动进入该namespace le data-draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal">
4.3 DML
- 操作table中数据
le data-draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal">
4.4 查询
- 查询table中数据
le data-draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal">
五、客户端-API
5.1 API基础
- maven依赖
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>1.4.8</version>
<!--排除jdk.tools的依赖-->
<exclusions>
<exclusion>
<groupId>jdk.tools</groupId>
<artifactId>jdk.tools</artifactId>
</exclusion>
</exclusions>
</dependency> - API 连接HBase
- 创建HBaseConfiguration对象
Configuration conf = HBaseConfiguration.create(); - 创建Connectiond对象
Connection connection = ConnectionFactory.createConnection(conf);
- 注意Zookeeper集群的本地HOST配置
- 创建DDL操作对象Admin
Admin admin = connection.getAdmin() - 通过Admin对象的方法操作名称空间、表
- 关闭资源
admin.close();
- API之DDL操作
- namespace
- 创建
- 查看
- 修改
- 删除
- 抽取HBaseUtils
- table
- 创建
- 判断表是否存在
- 删除
- 总结:DDL通过HBaseAdmin操作名称空间和表
- API之DML操作
- 注意:DML的操作对象不再是HBaseAdmin而是Table对象
Table table = connection.getTable(TableName.valueOf("ns1:t1")); - 添加记录
- Table的put(Put put);
- 获取数据
- get的基本使用
- get指定的Family和Qualifier
- 扫描数据
- scan的基本使用
- scan指定的Family和Qualifier
- scan范围扫描
- 删除数据
- 删除行
- 删除指定的Qualifier
5.2 API高级
- 过滤器
- 语法格式
scan '表名', { Filter => "过滤器(比较运算符, '比较器')" } - 查看过滤器的命令,能够查到支持的所有过滤器:show_filters
- 行键过滤器
ta-draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal"> 过滤器名称作用示例
- 列簇与列过滤器
ta-draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal"> 过滤器名称作用示例
- 值过滤器
ta-draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal"> 过滤器名称作用示例
- 其他过滤器
ta-draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal"> 过滤器名称作用示例
- 比较器
e data-draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal">
六、HBase核心
6.1 架构分析
参考: HBase.ppt
- HBase客户端
- Shell命令接口
- Java API编程接口
- Thrift/REST API编程接口
- MapReduce编程接口
- 缓存hbase:meta数据
- Zookeeper
- Zookeeper为HBase选举 Master,避免单点HMaster单点故障问题,实现HMaster的高可用
- 存储所有 Region 的寻址入口:hbase:meta表在哪台服务器上。
- 实时监控 RegionServer 的状态,将 RegionServer 的上线和下线信息实时通知给 Master
- 存储 HBase 的 Schema,包括有哪些 Table,每个 Table 有哪些 Column Family
- Master
- 为任务量小的RegionServer分配Region,实现集群的负载均衡
- 维护集群中,表的元数据信息
- 发现失效的Region,指挥RegionServer将失效的Region分配到正常的RegionServer上
- 当RegionServer挂掉后,协调RegionServer将HLog进行拆分,分给剩余正常的RegionServer,故障转移
- RegionServer
- 管理Master分配的Region
- 处理来自Client的读写请求
- 和底层的HDFS交互,存储数据Flush到HDFS中
- 负责Region变大之后的拆分
- 负责StoreFile的合并工作,防止小文件过多
- HRegion
- 数据表的行方向的切割分片,分布在不用的Region Server。
- Region由一个或者多个Store组成,每个Store保存一个Column Family。
- 每个Region由以下信息标识:< 表名,StartRowkey,创建时间>
- Store
- 每一个Region由一个或多个Store组成,每个 ColumnFamily对应一个Store
- 一个Store由一个memStore和0或者 多个StoreFile组成
- HBase以store的大小来判断是否需要切分region
- MemStore
- MemStore是一个内存级别的存储,一个列族Store对应一个MemStore
- 当MemStore的大小达到一个阀值时,MemStore会被flush到文 件
- StoreFile
- StoreFile是磁盘级别存储的文件组件
- Memstore内存中的数据写到文件后就是StoreFile,StoreFile底层是以HFile的格式保存
- 当Storefile文件的数量增长到一定阈值后,系统会进行合并,
- 在合并过程中会进行版本合并和删除工作,形成更大的Storefile
- HFile
- HFile是StoreFile的存储格式,例如txt、orc等
- 查看HFile文件数据命令
hbase org.apache.hadoop.hbase.io.hfile.HFile -e -p -f 文件路径
- WAL(HLog)
- 保证数据的高可靠性:HBase随机写入数据的时候,先写入缓存,再异步刷新落盘,为了防止缓存数据丢失,数据写入缓存之前需要首先顺序写入HLog
- HDFS
- 实际存储数据的分布式文件系统
- 数据默认3副本存储策略有效保证数据的高可靠性
6.2 寻址机制
- 寻址步骤:
- 第 1 步:Client 请求 Zookeeper 获取hbase:meta元数据表所在的 RegionServer 的地址。
- 第 2 步:Client 请求hbase:meta所在的 RegionServer 再次获取访问数据所在的 RegionServer 地址,Client 会将hbase:meta的相关信息 cache 下来,以便下一次快速访问。
- Region的RowKey构成:表名,StartRowkey,创建时间.整体进行MD5 Hash值
- 第 3 步:Client 请求数据所在的 RegionServer,操作数据。
参考: HBase.ppt
6.3 写入流程
- 写流程
1、Client 先根据 RowKey 找到对应的 Region 所在的 RegionServer
2、Client 向 RegionServer 提交写请求
3、RegionServer 找到目标 Region
4、Region 检查数据是否与 Schema 一致
5、如果客户端没有指定版本,则获取当前系统时间作为数据版本
6、将更新写入 WAL Log
7、将更新写入 MemStore
8、判断 MemStore 的是否需要 flush 为 StoreFile 文件。 - 写流程源码查看
6.4 读取流程
- 流程
1、客户端通过 ZooKeeper 以及-ROOT-表和.META.表找到目标数据所在的 RegionServer(就是 数据所在的 Region 的主机地址)
2、联系 RegionServer 查询目标数据
3、RegionServer 定位到目标数据所在的 Region,发出查询请求
4、Region 先在 Memstore 中查找,命中则返回
5、如果在 MemStore 中找不到,则在 StoreFile 中扫描 为了能快速的判断要查询的数据在不在这个 StoreFile 中,应用了 BloomFilter
6.5 MemStore Flush
- 触发条件
- MemStore级别限制:当Region中任意一个MemStore的大小达到了上线,会触发MemStore刷新
hbase.hregion.memstore.flush.size 默认128M - Region级别限制:当Region中所有的MemStore的大小和总和达到了上线会触发MemStore刷新
hbase.hregion.memstore.block.multiplier*hbase.hregion.memstore.flush.size - RegionServer级别限制:当RegionServer中MemStore的大小中和超过低水位阈值,RegionServer开始强制执行flush
hbase.regionserver.global.memstore.size.lower.limit*hbase.regionserver.global.memstore.size - 当一个RegionServer中HLog数量达到上线时,系统会选取最早的HLog对应的一个或多个Region进行flush
hbase.regionserver.maxlogs - HBase定期刷新MemStore:默认周期为1小时,确保MemStore不会长时间没有持久化
- 手动执行flush:用户可以通过shell命令flush 'tablename' 或者 flush 'regionname'分别对一个表或者一个Region进行flush
6.6 Compaction
Compaction是从一个Region的一个Store中选取部分HFile文件进行合并。
- Compacton分类
- Minor Compaction 是值选取部分小的、相邻的HFile,将他们合并成一个更大的HFile。
- Major Compaction 是指将一个Store中所有的HFile合并成一个HFile,这个过程还会清理三类无意义的数据:被删除的数据、TTL过期数据、版本号超过超过设定版本号的数据
- Compaction时机
- MemStore Flush:MemStore Flush会产生HFile文件,因此在每次执行完后,都会对当前Store中的文件数进行判断,一旦Store中总文件数大于hbase.hstore.compactionThrshold,就会触发Compaction。
- 后台线程周期性检查:RegionServer会在后台启动一个线程CompactionChecker,定期触发检查对应Store是否要执行Compaction。检查周期算法
hbase.server.thread.wakefrequency*hbase.server.compactchecker.interval.multiplier - 手动触发
6.7 Region Split
默认情况下,每个Table只有一个Region。随着数据的不断写入,Region会自动进行拆分,拆分后的Region会被分配到其他RegionServer上,实现HBase的负载均衡。
- HBase已经有6种Split触发策略,常见的Split策略如下:
- ConstantSizeRegionSplitPolicy:表式一个Region中最大Stroe的大小超过设置阈值(hbase.hregion.max.filesize)之后会触发分裂。
- IncreasingToUpperBoundRegionSplitPolicy:一个Region中最大Store的大小超过设置阈值就会触发分裂。但是这个阈值并不像ConstantSizeRegionSplitPolicy是一个固定的值,而是在一定条件下不断调整,调整后的阈值大小和Region所属表在当前RegionServer上的Region个数有关系,调整后的阈值等于(#regions)*(#regions)*(#regions)* flush size * 2,当然阈值并不会无线增大,最大值为用户设置的MaxRegionFileSize。
- SteppingSplitPolicy:这种分裂阈值也发生了变化,相比第二种方式简单了一些,分裂阈值大小和待分裂Region所属表在当前RegionServer上的Region个数有关系,如果Region个数等于1,分裂阈值为 flush size * 2,否则为MaxRegionFileSize。
- Pre Split
如果知道hbase数据表的key的分布情况,就可以在建表的时候对hbase进行region的预分区。这样做的好处是防止大数据量插入的热点问题,提高数据插入的效率。
- 预切割命令
create 'ns1:tb1',{NAME => 'f1'},{NAME => 'f2'},SPLITS => ['01','02','03']
- 表预切割案例
6.8 版本
- 版本设置
HBase中的表,支持十亿行 * 百万列 * 千版本
- 创建表的时候,需要给列簇指定版本,并且只能给列簇指定版本。
create 'ecitem:Beans', {NAME => 'f1', VERSIONS => 3} - 查询指定版本数的数据
# 返回两个版本的数据,不指定版本返回一个最新版本
get 'ecitem:Beans','row001',{COLUMN=>'f1:name',VERSIONS=>2} - 指定时间戳精确查询
get 'ecitem:Beans','row001',{COLUMN =>'f1:name',TIMESTAMP=>1543325344120} - 指定时间戳范围查询
get 'ecitem:Beans','row001',{COLUMN => 'f1:name',TIMERANGE=>[1543325325769,1543325344120]}
- TTL
TTL是作用于列族的,它设置了一个基于时间戳的临界值, 内部的管理会自动检查TTL值是否达到上限,在major合并过程中时间戳被判定为超过TTL的数据会被自动删除。
TTL参数的单位是秒,默认值是Integer.MAX_VALUE,即2^31-1=2 147 483 647 秒,大约68年。使用TTL默认值的数据可以理解为永久保存。
- 建表的时候指定TTL,默认值为'FOREVER',永不过期
create 't_task_log',{NAME => 'f', TTL=>'86400'} - 原生扫描:显示包括删除的数据
scan 'table',{RAW=>true,VERSIONS=>10} - TTL&MIN_VERSION
- 即使超过TTL设置,依然要保留最小版本数相应的版本个数
- KEEP_DELETE_CELL
- 当值为false,删除列数据,执行flush ,Scan RAW发现delete标签数据存在,magor_complet后delete标签数据不存在。
- 当值为true,删除列数据,执行flush,Scan RAW发现delete标签数据存在,magor_complet后delete标签数据存在。
七、HBase高级
7.1 计数器
HBase也有一种机制可以将列当作计数器。否则,如果用户需要对一行数据加锁,然后读取数据,再对当前数据做加法,最后写回 HBase并释放该行锁,从而其他写程序可以访问该行数据。这样做会引起大量的资源竞争问题,尤其是当客户端进程崩溃之后,尚未释放的锁需要等待超时恢复——这会在一个高负载的系统中引起灾难性的后果。
计数器是面向列的操作,即每次对特定计数器的操作只会锁住一列而不是一行,然后读取数据,再对当前数据做加法操作,最后再写入HBase中并释放该列的锁,在操作的过程中用户是可以访问这一行的其他数据的
解决SQL中:update student set age = age + 1 where id = 1;
- 计数器命令
incr '<table>','<row>','<column>',[increment-value>]
- 增加值和对计数器产生的作用:
ble data-draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal">
- 单计数器
单计数器顾名思义就是一次操作只能操作一个计数器,用户需要自己设置列,方法由HTable类提供 - 多计数器
单计数器每一次只允许操作一个计数器,如果一行中多个列都是计数器则需要将代码重复编写,因此HBase的HTable类提供了另外一个方法,可以一次操作同一行的多个计数器
7.2 BloomFilter
- BloomFilter的作用
HBase利用BloomFilter来提高随机读(Get)的性能,对于顺序(Scan)而言,设置Bloomfilter是没有作用的(0.92以后,如果设置了bloomfilter为ROWCOL,对于指定了qualiter的Scan有一定的优化)。 - BloomFilter在HBase中的开销
BloomFilter是一个列族级别的配置属性,如果在表中设置了BloomFilter,那么HBase会在生成StoreFile时,包含一份BloomFilter 结构的数据,称其为MetaBlock;MetaBlock与DataBlock(真实的KeyValue数据)一起由LRUBlockCache维护,所以开启BloomFilter会有一定的存储及内存cache开销。 - BloomFilter的类型
- ROW:根据KeyValue中的row来过滤storefile。
- ROWCOL:根据KeyValue中的row+qualifier来过滤storefile。
- 总结
- 任何类型的get(基于rowkey或row+col)Bloom Filter的优化都能生效,关键是get的类型要匹配Bloom Filter的类型
- 基于row的scan是没办法走Bloom Filter的。因为Bloom Filter是需要事先知道过滤项的。对于顺序scan是没有事先办法知道rowkey的。而get是指明了rowkey所以可以用Bloom Filter,scan指明column同理。
- row+col+qualify的scan可以去掉不存在此qualify的storefile,也算是不错的优化了,而且指明qualify也能减少流量,因此scan尽量指明qualify
7.3 RowKey设计
- RowKey设计原则
- 长度原则
- Rowkey 是一个二进制码流,Rowkey 的长度被很多开发者建议说设计在 10~100 个字节,不过建议是越短越好,不要超过 16 个字节,存为byte[]字节数组,一般设计成定长的。
- 原因如下:
- 数据的持久化文件 HFile 中是按照 KeyValue 存储的,如果 Rowkey 过长比如 100 个字 节,1000 万列数据光 Rowkey 就要占用 100*1000 万=10 亿个字节,将近 1G 数据,这会极大 影响 HFile 的存储效率;
- MemStore 将缓存部分数据到内存,如果 Rowkey 字段过长内存的有效利用率会降低, 系统将无法缓存更多的数据,这会降低检索效率。因此 Rowkey 的字节长度越短越好。
- 目前操作系统是都是 64 位系统,内存 8 字节对齐。控制在 16 个字节,8 字节的整数 倍利用操作系统的最佳特性。
- 散列原则
- 如果 Rowkey 是按时间戳的方式递增,不要将时间放在二进制码的前面,建议将 Rowkey 的高位作为散列字段,由程序循环生成,低位放时间字段,这样将提高数据均衡分布在每个 Regionserver 实现负载均衡的几率。如果没有散列字段,首字段直接是时间信息将产生所有 新数据都在一个 RegionServer 上堆积的热点现象,这样在做数据检索的时候负载将会集中 在个别 RegionServer,降低查询效率。
- 唯一原则
- 必须在设计上保证其唯一性。rowkey 是按照字典顺序排序存储的,因此,设计 rowkey 的时候,要充分利用这个排序的特点,将经常读取的数据存储到一块,将最近可能会被访问 的数据放到一块。
- 解决热点问题
- 加盐
- HASH
- 反转
- 时间戳反转
八、备份与恢复
8.1 Snapshot
- 什么是Snapshot
HBase 从0.95开始引入了Snapshot,可以对table进行Snapshot,也可以Restore到Snapshot。Snapshot可以在线做,也可以离线做。Snapshot的实现不涉及到table实际数据的拷贝,仅仅拷贝一些元数据,比如组成table的region info,表的descriptor,还有表对应的HFile的文件的引用。
8.2 相关命令
# 建立快照:
snapshot 'tableName', 'snapshotName'
# 列出快照:
list_snapshots
# 删除快照
delete_snapshot 'snapshotName'
# 从快照复制生成一个新表
clone_snapshot 'snapshotName', 'newTableName'
# 用快照恢复数据,它需要先禁用表,再进行恢复
disable 'tableName'
restore_snapshot 'snapshotName'
九、HBase调优
9.1 读性能调优
9.2 写性能调优
十、高级应用
10.1 MR操作HBase
- HBase作为MR的数据源,实现聚合操作
- 使用HBase的SnapShot获取相应的HFile文件地址,MR操作HFile文件
10.2 Hive结合HBase
- HBase作为Hive的外表,实现离线分析