目录
WAL(Write Ahead Log):发生写操作之前的日志
简介
一、概述
- HBase原本是由Yahoo!公司开发后来贡献给了Apache的一套开源的、基于Hadoop的、分布式的、可扩展的、非关系型数据库
- 如果需要对大量数据进行随机且实时读写,那么可以考虑使用HBase
- HBase能够管理非常大的表:billions of rows * millions of columns
- HBase是仿照Google的Big Table来进行实现的,因此,HBase和BigTable的原理几乎一致,只有实现语言不同。HBase是使用Java语言实现的,BigTable使用的是C语言实现的 - HBase最终将数据落地到HDFS上
- HBase提供了2个大版本,并且2个版本都在同时更新。其中,Hadoop3.1.3版本支持的是HBase2.2.X及以上版本
- HBase作为非关系型数据库,不支持标准的SQL语法,提供了一套全新的命令
- HBase能够存储稀疏类型的数据,也因此HBase能够存储结构化(数据本身有结构,经过解析之后,能够用传统数据库中的一个或者几个表来存储)和半结构化数据(数据本身有结构,但是解析之后无法用传统数据库中的表来存储)
- HBase本身作为数据库,提供了完整的增删改查的功能。HBase基于HDFS来进行存储,HDFS的特点是允许一次写入多次读取,不允许修改而允许追加写入,但是HBase提供了"改"功能,HBase如何实现"改"功能的?- HBase实际上并没有去修改写入的数据,而是在文件末尾去追加数据。HBase会对写入的每条数据自动添加一个时间戳,当用户获取数据的时候,HBase自动返回最新的数据,那么从用户角度来看,就是发生了数据的修改
- 在HBase中,数据的每一个时间戳称之为是一个版本
- 如果要锁定唯一的一条数据,那么需要通过行键(row key)+列族+列+时间戳这四个维度来锁定,这种结构称之为是一个Cell(单元格)
- HBase中的表在创建的时候,如果不指定,那么只对外提供一个版本的数据
- 如果建好表之后再修改可以获取的版本,那么已经添加的数据不起作用
- 即使表允许对外获取多个版本的数据,在获取的时候如果不指定,依然只获取一个版本的数据
二、基本概念
- Rowkey:行键
- 在HBase中没有主键的概念,取而代之的是行键
- 不同于传统的关系型数据库,在HBase中,定义表的时候不需要指定行键列,而是在添加数据的时候来手动添加行键
- HBase默认会对行键来进行排序,按照字典序排序
- Column Family:列族/列簇
-
- 在HBase中,没有表关联的概念,取而代之的是用列族来进行设计
- 在HBase中,一个表中至少要包含1个列族,可以包含多个列族,理论上不限制列族的数量
- 在HBase中强调列族,但是不强调列 - 在定义表的时候必须定义列族,但是列可以动态增删,一个列族中可以包含0到多个列
-
- namespace:名称空间
-
- 在HBase中没有database的概念,取而代之的是namespace
- 在HBase启动的时候,自带了两个空间:default和hbase。hbase空间下放的是HBase的基本信息;在建表的时候如果不指定,则表默认是放在default空间下
-
三、基本命令
命令 | 解释 |
help | 获取命令帮助信息 |
processlist | 查看当前HBase在执行的任务 |
status | 查看HBase的运行状态 |
version | 查看HBase的版本 |
whoami | 查看HBase的当前用户 |
create 'person', {NAME => 'basic'}, {NAME => 'info'}, {NAME => 'other'} 或者 create 'person', 'basic', 'info', 'other' | 建立一个person表,包含3个列族:basic,info,other |
append 'person', 'p1', 'basic:name', 'Bob' | 在person表中添加一个行键为p1的数据,向basic列族的name列中添加数据 |
get 'person', 'p1' | 获取指定行键的数据 |
get 'person', 'p1', {COLUMN => 'basic'} 或者 get 'person', 'p1', 'basic' | 获取指定行键指定列族的数据 |
get 'person', 'p1', {COLUMN => ['basic', 'info']} 或者 get 'person', 'p1', 'basic', 'info' | 获取指定行键多列族的数据 |
get 'person', 'p1', {COLUMN => 'basic:name'} 或者 get 'person', 'p1', 'basic:name' | 获取指定行键指定列的数据 |
scan 'person' | 扫描整表 |
scan 'person', {COLUMNS => 'basic'} | 获取指定列族的数据 |
scan 'person', {COLUMNS => ['basic', 'info']} | 获取多列族的数据 |
scan 'person', {COLUMNS => ['basic:name', 'other:address']} | 获取多个列的数据 |
put 'person', 'p1', 'basic:age', 20 | 修改数据 |
delete 'person', 'p1', 'other:adderss' 或者 deleteall 'person', 'pb', 'basic:name' | 删除指定行键指定列族的指定列 |
deleteall 'person', 'p1' | 删除指定行键的所有数据 |
create 'students', {NAME => 'basic', VERSIONS => 3}, {NAME => 'info', VERSIONS => 4} | 指定每一个列族允许对外获取的版本数量 |
desc 'students' 或者 describe 'students' | 描述表 |
get 'students', 's1', {COLUMN => 'basic:age', VERSIONS => 3} | 获取指定行键指定列的指定数量版本的数据 |
scan 'students', {COLUMNS => 'basic:age', VERSIONS => 3} | 获取指定列的指定数量版本的数据 |
count 'person' | 统计person表中行键的个数 |
get_splits 'person' | 获取person表对应的HRegion的个数 |
truncate 'person' | 摧毁重建person表 |
list_namespace | 查看所有的空间 |
create_namespace 'demo' | 创建demo空间 |
create 'demo:users', 'basic' | 在demo空间下创建users表 |
list_namespace_tables 'demo' | 获取demo空间下的所有表 |
describe_namespace 'demo' | 描述demo空间 |
drop_namespace 'demo' | 删除demo空间,要求这个空间为空 |
disable 'demo:users' | 禁用表 |
drop 'demo:users' | 删除表 |
enable 'person' | 启用表 |
exists 'users' | 判断表是否存在 |
is_disabled 'person' | 判断person表是否被禁用 |
is_enabled 'person' | 判断person表是否被启用 |
list | 查看所有空间下的所有的表 |
locate_region 'person', 'p1' | 定位p1行键所在的HRegion的位置 |
show_filters | 展现所有的过滤器 |
disable_all 'demo:.*' | 禁用demo空间下的所有的表 |
drop_all 'demo.*' | 删除demo空间下的所有的表 |
enable_all 'demo:.*' | 启用demo空间下的所有的表 |
四、Hive和HBase的比较
- Hive本质上是一个用于进行数据仓库管理的工具,在实际过程中经常用于对数据进行分析和清洗,提供了相对标准的SQL结构,底层会将SQL转化为MapReduce来执行,因此Hive的效率相对较低,更适合于离线开发的场景。Hive一般针对历史数据进行分析,一般只提供增加和查询的能力,不会提供修改和删除的功能
- HBase本质上是一个非关系型数据库,在实际过程中,用于存储数据。因为HBase的读写效率较高,吞吐量较大,因此一般使用HBase来存储实时的数据,最终数据会落地到HDFS上。HBase作为数据库,提供了完整的增删改查的能力,但是相对而言,HBase的事务能力较弱。HBase不支持SQL,提供了一套完整的命令
- 总结:Hive强调的是分析能力,但是HBase强调的是存储能力,相同的地方在于两者都是利用HDFS来存储数据
安装
- 硬件环境:至少需要3台虚拟机或者云主机,Centos7.5及以上版本,至少需要双核,至少4G内存+20G磁盘
- 软件环境:JDK1.8+Zookeeper3.5.7+Hadoop3.1.3
- 进入/home/software目录下
cd /home/software |
- 上传或者下载HBase的安装包,云主机的下载地址
- 解压
tar -xvf hbase-2.4.2-bin.tar.gz |
- 进入HBase的配置目录
cd hbase-2.4.2/conf |
- 编辑文件
vim hbase-env.sh |
添加如下属性 |
export JAVA_HOME=/home/software/jdk1.8 |
export HBASE_MANAGES_ZK=false |
保存退出,重新生效 |
source hbase-env.sh |
- 编辑文件
vim hbase-site.xml |
添加如下内容 |
<!--指定HBase在HDFS上的数据存储目录--> <property> <name>hbase.rootdir</name> <value>hdfs://hadoop01:9000/hbase</value> </property> <!--开启HBase的分布式--> <property> <name>hbase.cluster.distributed</name> <value>true</value> </property> <!--配置Zookeeper的连接地址--> <property> <name>hbase.zookeeper.quorum</name> <value>hadoop01:2181,hadoop02:2181,hadoop03:2181</value> </property> <property> <name>hbase.unsafe.stream.capability.enforce</name> <value>false</value> </property> <property> <name>hbase.wal.provider</name> <value>filesystem</value> </property> |
- 编辑文件
vim regionservers |
添加当前的三台主机的主机名 |
- 需要将Hadoop的核心配置文件拷贝到当前的HBase的配置目录下
cp /home/software/hadoop-3.1.3/etc/hadoop/core-site.xml ./ |
- 回到software目录下,远程拷贝给另外两台云主机
cd /home/software/ |
scp -r hbase-2.4.2 root@hadoop02:$PWD |
scp -r hbase-2.4.2 root@hadoop03:$PWD |
- 配置三台主机的环境变量
vim /etc/profile |
在文件末尾添加 |
export HBASE_HOME=/home/software/hbase-2.4.2 |
export PATH=$PATH:$HBASE_HOME/bin |
保存退出,重新生效 |
source /etc/profile |
- 启动Zookeeper
cd /home/software/apache-zookeeper-3.5.7-bin/bin |
sh zkServer.sh start |
sh zkServer.sh status |
- 在第一台主机上启动Hadoop的HDFS
start-dfs.sh |
- 在第一台主机上启动HBase
start-hbase.sh |
- 可以通过IP:16010来访问HBase的界面
可能出现的问题:
1、hbase启动后hmaster节点启动后又挂掉。
1)虚拟机时间同步
2)在hbase安装目录的中的logs文件夹中查看启动日志
重点在这里:报错说我的master成为hmaster失败。原因是:Operation category READ is not supported in state standby。也就是读操作失败,因为处于standby状态,这时候去检查nn1(namenode)节点状态:
hdfs haadmin -getServiceState nn1
解决办法:
停掉所有的namenode:
hdfs --daemon stop namenode
然后先在server1重启namenode,然后再server2和server3重启namenode:
hdfs --daemon start namenode
具体操作步骤
报错如果出现三个standy 查看并修改配置文件
第一个节点为active
基本架构
一、HRegion
- 在HBase中,会将一个表从行键方向上进行切分,切分成1个或者多个HRegion
- 切分之后,每一个HRegion都会交给某一个HRegionServer来进行管理
- 一个表至少会包含1个HRegion,可以包含多个HRegion
- 在HBase中,行键是有序的,因此从行键方向上来进行的切分,所以HRegion之间的数据是不交叉的
- 因为HRegion会交给HRegionServer来管理,并且HRegion之间的数据相互不交叉,所以保证请求不会集中于某一个节点上而是会分散到不同的节点上
- 随着运行时间的推移,HRegion管理的数据会不断增多,达到指定条件(默认是10G)的时候,会自动的进行分裂,分裂成两个等大的HRegion。分裂之后,一般其中的一个HRegion会产生转移,转移给另外的HRegionServer来进行管理,HRegion在转移的过程中。
- 每一个HRegion包含1个到多个HStore,HStore的数量由列族的数量来决定
- 每一个HStore都会包含1个memStore以及包含0到多个StoreFile/HFile
二、Zookeeper的作用
- 在HBase中,Zookeeper充当了注册中心
- 当HBase启动之后,会自动的在Zookeeper上来注册一个/hbase节点
- 当Active HMaster启动之后,会自动的在Zookeeper上注册一个临时节点/hbase/master - 当Active HMaster宕机之后,这个临时节点就会消失,此时Zookeeper就会从Backup HMasters中选择最早注册的节点来切换为Active状态
- 当Backup HMaster启动之后,会自动的在Zookeeper的/hbase/backup-masters节点上注册一个临时子节点
- 当HRegionServer启动之后,也会自动的在Zookeeper的/hbase/rs节点下来注册子节点
三、HMaster
- 在HBase中,允许用户在任意一台安装了HBase的节点上来启动HMaster,理论上不限制HMaster的数量
- HMaster启动命令
hbase-daemon.sh start master |
- 在HBase中,如果启动了多个HMaster,那么HMaster之间就会分为Active和Backup两种状态
- 如果启动多个HMaster,那么最先注册到Zookeeper上的HMaster就会成为Active状态,后注册到Zookeeper上的HMaster就会成为Backup状态
- 当Active HMaster接收到请求之后,需要考虑将数据同步给其他的Backup HMasters。同步的节点数量越多,此时效率就会越低
- 因此在HBase中,虽然理论上不限制HMaster的个数,但是实际过程中,HMaster的个数一般不会超过3个:1个Active HMaster+2个Backup HMasters
- Active HMaster会实时监控Zookeeper上/hbase/backup-masters下的节点变化以确定需要同步的节点是哪几个
- HMaster的作用
- 管理HRegionServer。需要注意的是,HMaster对HRegionServer的管理权限并不大,只能决定HRegion交由哪一个HRegionServer来进行管理
- 记录和存储元数据。HBase中的元数据包含namespace名、table名、column family名以及属性信息等。注意,在HBase中,列不是元数据,因为列可以动态增删 - 也就意味着凡是产生元数据的操作会经过HMaster,不产生元数据的操作不会经过HMaster。DDL(Data Defination Language,数据定义语言,例如create/drop等)以及namespace操作会产生元数据,DML(Data Manipulation Language,数据操纵定义,例如put/get/scan/delete等)语言不会产生元数据
四、HBase的架构读写流程
- 当客户端要进行DML操作的时候,会首先发送请求到Zookeeper,请求获取hbase:meta表的位置,这个表中存储HBase的元数据
- Zookeeper收到请求之后,会将hbase:meta表的位置返回给客户端。hbase:meta会由某一个HRegionServer管理
- 客户端收到hbase:meta表的位置之后,会请求对应的HRegionServer,来读取hbase:meta,从这个表中获取到实际要操作的HRegion所在的位置
- 客户端获取到HRegion的实际所在位置之后,会再次发送请求给对应的HRegionServer,来操作这个HRegion
- 注意问题
- 当客户端第一次请求Zookeeper之后,会自动缓存hbase:meta文件的位置,之后客户端的每次请求就可以不用再访问Zookeeper
- 当客户端获取到HRegion的位置之后,也会自动缓存这个HRegion的位置,之后如果还操作这个HRegion,就可以直接访问
- 随着时间的推移,客户端缓存的位置越来越多,此时效率就会越来越高。但是如果客户端发生宕机,那么此时会导致缓存崩溃,那么需要重新建立缓存
五、HRegionServer
- 在实际生产过程中,一般会考虑将HRegionServer和DataNode部署在相同的节点上,避免频繁的跨集群的请求
- HRegionServer的作用是用于管理HRegion。官方文档中给定,每一个HRegionServer大概能够管理1000个HRegion。每一个HRegion默认能够最多管理10G数据
- 每一个HRegionServer包含三部分结构:1到多个WAL,1个BlockCache以及0到多个HRegion
-
WAL(Write Ahead Log):发生写操作之前的日志
- 当HRegionServer接收到写请求之后,会先试图将请求记录到WAL中,之后再将数据更新到对应的memStore中
- 通过WAL这个机制,能够有效的保证数据不会产生丢失,但是因为WAL是落地在磁盘上的,因此会导致写入效率在一定程度上会降低。因此在实际过程中,如果容忍一定程度上的数据丢失的风险而想要提高写入效率,那么此时可以考虑关闭WAL机制
- 在HBase0.94版本之前,WAL只能采用串行写机制;从HBase0.94版本开始,引入了NIO中的Channel机制,使得WAL支持使用并行写机制,从而保证效率能够提升
-
BlockCache:数据块缓存
- BlockCache本质上是一个读缓存,维系在内存中,默认大小是128M
- 在HBase中,在读取数据的时候,会将读取到的数据放到BlockCache中,从而下次再次读取数据的时候,可以从BlockCache中获取,减少对HStore的读取
- BlockCache在缓存的时候,还采用了"局部性"原理。所谓的"局部性"原理本质上就是一个猜测的过程,无非是利用时间或者空间条件来合理猜测以提高命中率:
- 时间局部性:在HBase中,如果一条数据被读取过,那么HBase会认为这条数据被再次读取的概率要高于其他的没有被读取过的数据,那么此时HBase就会将这条数据放到缓存中 - 只要是读取过的数据就会放到缓存中
- 空间局部性:在HBase中,如果一条数据被读取过,那么HBase会认为与这条数据相邻的数据被读取的概率要高于其他的不相邻的数据,那么此时HBase就会将与这条数据相邻的数据也放到缓存中
- 随着时间的推移,BlockCache会被放满,那么此时BlockCache就会采用LRU(Least Recently Used,最近最少使用的数据就会被清理掉)策略
- HRegion:HBase分布式存储和管理的基本结构,但不是数据存储的最小单位:
- 每一个HRegion会至少包含1个HStore,可以包含多个HStore,HStore的数量由列族的数量来决定
- 每一个HStore中会包含1个memStore以及0到多个HFile/StoreFile
- memStore本质上是一个写缓存,维系在内存中,大小默认是128M,可以通过hbase.hregion.max.filesize属性来调节
- 当达到一定条件的时候,就会将memStore进行冲刷,冲刷产生HFile。HFile最终会以Block形式落地到DataNode上
- memStore的冲刷条件:
- 当memStore被用满之后,会自动的进行冲刷,产生一个HFile
- 当距离上一次冲刷达到指定的时间间隔(默认是1H - 3600000ms,可以通过属性hbase.regionservers.optionalcacheflushinterval来修改,注意单位是毫秒)之后,也会自动的冲刷memStore产生HFile
- 当某一个HRegionServer上所有的memStore所占内存之和/实际物理内存>0.4,那么会冲刷当前HRegionServer上较大的几个memStore,直到这个值小于0.4为止
- 随着运行时间的推移,第三个条件更容易满足,此时会冲刷产生大量的小文件
六、Compaction机制
- 在HBase中,提供了2种Compaction机制:minor compact和major compact
- minor compact:初次合并。在合并的时候,会将当前HStore中相邻的几个小的HFile合并成一个大的HFile,原本就是大的HFile不参与合并,因此合并完成之后依然存在多个HFile
- major compact:主要合并。在合并的时候,会将当前HStore中所有的HFile进行合并,因此合并完成之后只存在一个HFile
- 相对而言,minor compact合并效率更高一些,HBase中默认采用的合并机制也是minor comapct。实际过程中,也会使用major compact,但是因为major compact的效率较低,需要对大量数据进行读写,因此一般是放在相对空闲的时间来进行
- 需要注意的是,在major compact的时候,会自动清理掉被标记为删除的数据或者过时的数据
基本流程
一、写流程
- 当HRegionServer接收到写请求的时候,会先将这个写请求记录到WAL中,记录成功之后会再将数据更新到memStore中
- 数据在memStore中会进行排序,按照行键字典序->列族字典序->列字典序->时间戳倒序来进行排序
- 当达到冲刷条件的时候,memStore会自动冲刷产生HFile。因为memStore中的数据已经排序,所以冲刷出来的单个HFile中的数据是有序的。所有的HFile之间是局部有序整体无序的
- HFile最终会以Block形式落地到HDFS的DataNode上
- HFile的v1版本的结构
-
DataBlock:数据块。用于存储数据
- 每一个HFile中包含1个到多个DataBlock,DataBlock是数据存储的基本结构/最小单位
- 因为HFile中的数据是有序的,所以切分出来的DataBlock之间的数据是不交叉的
- 每一个DataBlock大小默认是64KB。小的DataBlock利于查询(get),大的DataBlock利于遍历(scan)
- 每一个DataBlock都是由1个Magic(魔数)以及1到多个KeyValue来构成
-
- Magic:魔数。本质上就是一个随机数,用于校验的
- KeyValue:存储数据,每一条数据最终都会以键值对形式来进行存储
-
- MetaBlock:元数据块。用于存储元数据的。注意,不是所有的HFile都包含这一部分,一般只有hbase:meta表对应的HFile会包含这一部分
- FileInfo:文件信息。用于记录HFile大小、所属HStore等信息
- DataIndex:数据索引,用于记录DataBlock的索引
- MetaIndex:元数据索引,用于记录MetaBlock的索引
- Trailer:在文件末尾,占用固定的字节大小,用于记录FileInfo,DataIndex和MetaIndex在文件中的起始字节
- 在HFile中,需要先读取文件末尾,通过Trailer来锁定DataIndex的位置,然后读取DataIndex,通过DataIndex来定位DataBlock的位置
- 在HFile的v2版本中,引入了BloomFilter(布隆过滤器)
二、读流程
- 当HRegionServer结束到读请求的时候,会先考虑从BlockCache中来获取数据
- 如果BlockCache中没有数据,那么会试图从memStore中来获取
- 如果memStore中也没有数据,那么会试图从HFile中来获取。在读取HFile的时候,可以先根据行键范围进行筛选,筛选掉不符合范围的HFile,但是不代表剩余的HFile中一定有找的数据。筛选完成之后,如果开启了布隆过滤器,那么可以利用布隆过滤器再次筛选,被筛选掉的文件中一定没有要找的数据,但是不代表剩余的文件中有要找的数据
设计与优化
一、设计原则
-
行键设计
- 行键在设计的时候要尽量的散列,例如可以考虑使用哈希、加密算法等使结果散列,这样能保证请求不会集中于一个节点上
- 行键设计最好有意义,如果行键真的完全随机,会增加查询难度,例如订单的行键可以设计为:210510abj025 -> 520jba015012
- 行键在使用的时候要保证唯一
-
列族设计
- 在HBase中虽然理论上不限制列族的数量,但是实际过程中,一个表中的列族数量一般不会超过3个
- 在设计列族的时候,要尽量将具有相同特性的数据或者经常一起使用的数据放在一个列族中,尽量避免跨列族查询
二、优化
- 调节DataBlock的大小。小的DataBlock利于查询,大的DataBlock利于遍历。在建表的时候,就可以根据当前场景来确定DataBlock的大小。例如:
create 'person', {NAME => 'basic', BLOCKSIZE = '32768'} |
- 关闭BlockCache。如果HBase的遍历偏多,此时没有必要将数据放到读缓存中,此时可以考虑关闭BlockCache
create 'person', {NAME => 'basic', BLOCKCACHE => 'false'} |
alter 'person', {NAME => 'basic', BLOCKCACHE => 'false'} |
- 更改BloomFilter的级别。BloomFilter支持三种方式:NONE,ROW以及ROWCOL。NONE不使用BloomFilter,如果节点硬件性能一般,可以考虑关闭BloomFilter;ROW对行键进行过滤,BloomFilter默认就是这个值;ROWCOL表示对行键、列族和列同时过滤,如果节点硬件性能较好,可以使用这个值
- 开启数据压缩机制。如果HBase占用了大量的HDFS空间,导致HDFS空间不够,那么可以考虑对HBase的数据进行压缩。通过COMPRESSION属性来修改,支持NONE,LZO,SNAPPY和GZIP。其中NONE表示不压缩,HBase默认不对数据压缩
- 在查询的时候可以考虑显式地指定列,此时可以减少在网络中传输的数据量。例如
get 'person', 'p1' |
get 'person', 'p1', 'basic' |
get 'person', 'p1', 'basic:name' |
- 如果数据量较大,那么在读写的时候可以考虑使用批量读写
- 关闭WAL。如果想要提高写入效率,又能够容忍一定的数据丢失,那么可以考虑关闭WAL
- 预创建HRegion。当HRegion管理的数据比较多(默认是10G)的时候,会进行分裂。HRegion分裂之后可能会发生管理权的转移,此时HRegion的分裂和转移都要花费时间。因此在能够预估数据量的前提下,可以考虑在建表的时候就构建多个HRegion。例如
hbase org.apache.hadoop.hbase.util.RegionSplitter person HexStringSplit -c 15 -f basic |
- 调整Zookeeper的有效Session时长。默认情况下,HMaster和Zookeeper之间通过心跳来保证联系,心跳间隔时间默认是180s即3min,也就意味着HMaster每隔3min会给Zookeeper发送一次心跳。如果HMaster产生了故障,那么可能Zookeeper需要在3min之后才能发现故障。在业务高峰期,HBase有3min不能使用,此时会造成大量的损失。因此需要调节这个时长。通过属性zookeeper.session.timeout来调节,单位是秒,放在hbase-site.xml中