4 HBase
摘要:HBase是一种非关系型数据库,它是基于谷歌BigTable的开源实现,和BigTable一样,支持大规模海量数据的存储,对于分布式并发数据处理的效率极高,易于扩展且支持动态伸缩,适用于廉价设备。HBase实际上就是一个稀疏、多维、持久化的映射表,它采用行键、列和时间戳即可轻松锁定数据,每个数据都是未经解释的字符串,在本文中我们都会具体学习。在本文中我们会谈及HBase系统架构、相关概念、关键流程、突出特点、性能优化以及基本shell操作。
作者:来自ArimaMisaki创作
4.1 HBase基本介绍
说明:HBase是谷歌BigTable的开源实现,是一个高可靠、高性能、面向列、可伸缩的分布式存储系统。
特点:
- 适合于存储大表数据,并且对大表数据的读写访问可以达到实时级别。
- 利用Hadoop作为其文件存储系统,提供实时读写的分布式数据库系统。
- 利用ZooKeeper作为协同服务。
与传统数据库的对比:
- 数据索引:关系数据库通常可以针对不同列构建复杂的多个索引以提高数据访问性能。HBase只有一个索引——行键,通过巧妙的设计,HBase所有的访问方法或者通过行访问,或者通过行键扫描,从而使得整个系统不会慢下来。
- 数据维护:在关系数据库中,更新操作会用最新的当前值去替换记录中原来的旧值,
4.2 HBase应用场景
特点:
- 海量数据存储
- 不需要完全拥有传统关系型数据库所具备的ACID特性
- 高吞吐量
- 需要在海量数据中实现高效的随机读取
- 需要很好的性能压缩能力
- 能够同时处理结构化和非结构化的数据
4.3 HBase数据模型
4.3.1 数据模型基本概念
说明:
- 简单来说,应用程序式以表的方式在HBase存储数据
- 表由行和列构成,所有列都是属于某一个列族的。
- 行和列的交叉点称之为cell(单元格),cell是版本化的,其内容是不可分割的字节数组。
- 表的行键也是一段字节数组,所以任何东西都可以保存进去,无论是字符串还是数字。
- HBase的表都是按key排序的,排序方式是针对字节的
- 所有的表都必须要有主键key。
表:Hbase采用表来组织数据,表由行和列组成,列划分为若干个列族。
行:每个Hbase表由若干行组成,每个行有一个行键(row key)。
列族:一个Hbase表被分组为许多的列族(Column Family)的集合,它是基本的访问控制单元。
列限定符(列):列族里的数据通过列来定位。
单元格:在Hbase表中,通过行、列族和列限定符确定一个单元格,单元格存储的数据没有数据类型,总被视为字节数组byte[]。
时间戳:每个单元格都保存着同一份数据的多个版本,这些版本用时间戳进行索引。
4.3.2 数据存储视图
说明:在HBase中,列是可以为空的,因此表可以看成是一个稀疏的行集合。但在物理视图中,它并没有列的概念,其根据列族
来存储,新的columns可以不仅过声明直接加入一个列族。换而言之,下面这个图在物理视角中,列是不存在的,所谓的列名都是概念而已,当我们要把Tom存储入表时,应该是以info.name.Tom存入表中的info列族。
4.3.3 行存储和列存储
行存储:数据按行存储在底层文件系统中,且每一行会被分配固定的空间,如关系数据库的元组,元组都代表现实世界中的某个实例,按照行的方式来存储。
行存储优点:有利于增加修改整行记录等操作,有利于整行数据的读取操作
行存储缺点:单列查询时,会读取一些不必要的数据
列存储:数据按列为单位进行存储在底层文件系统中,如非关系数据库HBase。
列存储优点:有利于面向单列数据的读取、统计等操作
缺点:整行读取时,可能需要多次IO操作
误区说明:有很多人会误以为列存储是将元组按列的方式存储,实际并不是这个样子。表中元组实际上在列族数据库别无二致,不同的是,当我们需要找到某个人的某个电话时,我们需要现在数据库中找到该元组,并投影出它的电脑号码;但在列族数据库中,我们直接找到电话号码那一列全部拿出即可。
4.4 HBase架构
4.4.1 基本架构
说明:
- 主服务器HMaster负责管理和维护HBase表的分区信息,维护HRegionServer列表,分配Region负载均衡。
- HRegionServer负责存储和维护分配给自己的Region,处理来自客户端的读写请求。
- 客户端并不是从HMaster主服务器上读取数据,而是在获得Region的存储位置信息后,直接从HRegionServer上读取数据。
- 客户端并不依赖HMaster,而是通过Zookeeper来获得Region位置信息,大多数客户端设置从来不喝HMaster通信,这种设计方式使得HMaster负载很小。
4.4.2 存储架构
说明:按照架构层级可以分为:
- Table:HBase中的表
- Region:根据表的起始Region和结束Region划分区域
- Store:根据列族存储相应的region数据。
- MemStore:缓存区,常用于临时读和临时写
- StoreFile:根据Store将region数据存储到物理表上
- Block:物理表最终落实到HDFS的Block中
表:HBase表最开始只有一个Region,后来不断分裂
Region:Region拆分速度操作非常快,接近瞬间,因为拆分之后的Region读取的仍然是原存储文件,直到分裂过程结束,把存储文件异步地写到独立的文件之后,才会读取新文件。
4.4.3 Region的定位
三级模式:在Hadoop早期版本中,HBase的架构为三级模式,即通过Zookeeper来存储root表(其中包含最开始的Region),root表中记录了若干个Meta表,Meta表存储了若干个用户Region;当需要一个region时,需要经过三级模式。
两级模式:
- Region分为元数据Region和用户Region两类
- 元数据Region(MetaRegion)记录了每一个UserRegion的路由信息
读写Region数据的路由,包括以下几步:
- 找寻MetaRegion地址
- 由MetaRegion找寻UserRegion地址
说明:为了加快访问速度,Hbase:meta表被保存在内存中。假设Hbase:meta表的每行在内存中大约占用1kb,并且每个Region限制为128MB,则两层架构可以保存的Region数目是128MB/1KB=$2^{17}$个Region。
4.4.4 HMaster高可用
说明:Zookeeper可以帮助选举一个Master作为集群的总管,并保证在任何时刻总有唯一一个Master在运行,这就避免了Master的单点失效问题。主服务器Master主要负责表和Region的管理工作。具体职能如下:
- 管理用户对表的增加、删除、修改、查询等操作
- 实现不同Region服务器之间的负载均衡
- 在Region分裂或合并后,负责重新调整Region的分布
- 对发生故障失效的Region服务器上的Region进行迁移
4.4.5 RegionServer
说明:RegionServer服务器是HBase中最核心的模块,其负责维护分配自己的Region和响应用户的读写请求,并且会利用心跳机制把自己的状态报告给Zookeeper。
4.5 HBase关键流程
用户读写数据过程:用户写入数据时,被分配到相应Region服务器去执行;用户数据首先被写入到MemStore和Hlog中,只有当操作写入Hlog之后,commit()调用才会将其返回给客户端;若用户想要读取数据,Region服务器会首先访问MemStore缓存,如果找不到再到上面的StoreFile中寻找。
缓存的刷新:系统会周期性地把MemStore缓存里的内容刷写到磁盘的StoreFile文件中,清空缓存,并在Hlog里面写入一个标记;每次刷写都生成一个新的StoreFile文件,因此,每个Store包含多个StoreFile文件;每个HRegionServer都有自己的Hlog,每次启动都检查该文件,确认最近一次执行缓存刷新操作之后是否发生新的写入操作,如果发现更新,则先写入MemStore,再刷写到StoreFile,开始为用户提供服务。
StoreFile的合并:每次进行缓存的刷新时都会生成一个新的StoreFile,随着数量的增多挨个查找StoreFile会影响查找的速度。为此,调用Store.compact()可以把多个StoreFile合并为一个;合并操作比较耗费资源,一般不会随便调用,而是当数量达到了某个阈值才启动合并。
Store工作原理:Store是Region服务器的核心。多个StoreFile可以通过Store.compact()合并成一个,但StoreFile过大时又可以触发分裂操作,将一个父Region分裂为两个子Region。
Hlog工作原理:分布式环境必须要考虑系统出错,故在HBase中采用Hlog保证系统的恢复;在HBase系统中为每个Region服务器配置了一个Hlog文件,它是一种预写式日志
,用户更新数据必须首先写入日志后,才能写入MemStore缓存,并且,只有当MemStoreH对应的log已经写入磁盘后,该缓存内容才能被刷写到磁盘;一旦RegionServer发生故障,Zookeeper会通过心跳机制检测到其状态,而后通知HMaster。HMaster首先会处理故障RegionServer上遗留的Hlog,根据Hlog上的记录和Region的对应关系对Hlog文件进行拆分,拆分后的Hlog文件分散到Region的目录下,Region服务器领取到对应的Hlog文件和Region后,Region服务器会根据Hlog重新做一遍数据操作。
4.6 HBase的特点
4.6.1 多File的影响
说明:随着File数量的增多,HBase读取的时延会变大。
4.6.2 Compaction
说明:由于多File带来的影响,采用压缩(Compaction)变得尤为重要。Compaction的目的是为了减少同一个Region中同一个ColumnFamily下面的小文件的数目,从而提升读取的性能。
Compaction分为两种,即Minor
和Major
。Minor压缩是一种小范围的压缩,它的压缩数目有固定的区间限制,通常采用这种压缩是压缩一些连续时间范围的小文件,且选取压缩文件时遵循一定的算法;Major压缩则涉及该Region下整个列族下面所有的HFile文件。
提示:压缩的时候不能读写数据。
4.6.3 OpenScanner
说明:OpenScanner的过程中,会创建两种不同的Scanner来读取Hfile、MemStore的数据,其中Hfile对应的为StoreFileScanner,而MemStore对应的Scanner为MemStoreScanner。
4.6.4 BloomFilter
说明:BloomFilter用来优化一些随机读取的场景,即通过get命令读取数据时的场景。它可以用来快速的判断一条用户数据在一个大的数据集合中是否存在。BloomFilter在判断一个数据是否存在时,拥有一定的误判率。但对于“用户数据 xxx 不存在”的判断结果是可信的;对于HBase的BloomFilter的相关数据,其被保存在HFile中。
4.7 HBase性能优化
4.7.1 行键
说明:行键是按照字典序存储,因此在设计行键时,要充分利用排序特点,将经常一起读取的数据存储到一块,将最近可能会被访问的数据放在一块。
4.7.2 构建二级索引
访问方式:访问HBase表中的行有三种方式:
- 通过单个行键访问
- 通过行键区间访问多个行
- 全表扫描
说明:HBase只有一个针对行键的索引,为了提高访问速度我们可以构建二级索引,如:
- 多个表索引
- 多个列索引
- 基于部分列值的索引
4.8 HBase常用shell命令
# 获取帮助
help
# 获取命令的详细信息
help 'status'
# 查看服务器状态
status
# 查看版本信息
version
# 查看所有表
list
# 创建表
create ‘表名称’, ‘列族名称 1’,‘列族名称 2’,‘列名称 N’
# 查看表的基本信息
desc ‘表名’
# 禁用表
disable 'Student'
# 检查表是否被禁用
is_disabled 'Student'
# 启用表
enable 'Student'
# 检查表是否被启用
is_enabled 'Student'
# 查看表是否存在
exists 'Student'
# 删除表前需要先禁用表
disable 'Student'
# 删除表
drop 'Student'
# 插入数据
put ‘表名’, ‘行键’,‘列族:列’,‘值’
# 添加列
alter ‘表名’, ‘列族名’
# 删除列族
alter ‘表名’, {NAME => ‘列族名’, METHOD => ‘delete’}
# 获取指定行中所有列的数据信息
get 'Student','rowkey3'
# 获取指定行中指定列族下所有列的数据信息
get 'Student','rowkey3','baseInfo'
# 获取指定行中指定列的数据信息
get 'Student','rowkey3','baseInfo:name'
# 删除指定行
delete 'Student','rowkey3'
# 删除指定行中指定列的数据
delete 'Student','rowkey3','baseInfo:name'
# 获取指定行中所有列的数据信息
get 'Student','rowkey3'
# 获取指定行中指定列族下所有列的数据信息
get 'Student','rowkey3','baseInfo'
# 获取指定行中指定列的数据信息
get 'Student','rowkey3','baseInfo:name'
# 查询整表数据
scan 'Student'
# 查询指定列簇数据
scan 'Student', {COLUMN=>'baseInfo'}
# 查询指定列的数据
scan 'Student', {COLUMNS=> 'baseInfo:birthday'}
# 采用BloomFilter指定条件过滤
scan 'Student', FILTER=>"ValueFilter(=,'binary:24')"