本文主要从HBase存储系统的分布式架构设计、HBase存储机制、逻辑存储与物理存储等方面系统讲解了HBase存储系统。以及根据HBase系统的特性讲述了常用的应用场景、开发过程中需要注意的事项、优化方式(最佳实践)等。
笔者在实际开发过程中,根据HBase存储的特性及业务实际应用场景,将HBase作为用户画像的底层存储,在生产中亿级用户量,日活超千万的量级下,HBase很好的满足了性能与存储的业务需求。
希望通过这边文章与大家分享HBase存储方面的知识,若有不正确或者有出入的地方,欢迎大家指正,在此感谢!
1.HBase系统架构
HBase整体系统架构如下图所示:
组件简介
Client
HBase访问客户端,包含访问HBase的接口;Client维护着一些Cache来加快对HBase的访问,比如Region的位置信息。
主要职责:
1.使用HBase RPC机制与HMaster和HRegionServer进行通信。
2.Client与HMaster进行通信管理类操作。
3.Client与HRegionServer进行数据读写类操作。
HRegion
1.HBase中分布式存储的最小单元;但不是存储的最小单元。
2.HBase表在行的方向上分隔为多个Region。不同的Region可以分别在不同的Region Server上,但同一个Region是不会拆分到多个Server上。Region按大小分隔,当Region的某个列族达到一个阈值(默认256M)时就会分成两个新的Region。
HRegion Server
Region Server 负责处理数据的读写请求,客户端请求数据时直接和 Region Server 交互。
主要职责:
1.用来维护Master分配给他的Region,处理对这些Region的IO请求;
2.负责切分正在运行过程中变的过大的Region。
HMaster
1.HRegionServer管理者/协调者。
2.数据的读写操作与他没有关系,它挂了之后,集群照样运行。但是Master 也不能宕机太久,有很多必要的操作,比如创建表、修改列族配置等DDL跟Region的分割与合并都需要它的操作。
主要职责:
1.负责启动的时候分配Region到具体的 RegionServer。
2.发现失效的 Region,并将失效的 Region 分配到正常的 RegionServer 上。
3.管理HRegion服务器的负载均衡,调整HRegion分布。
4.在HRegion分裂后,负责新HRegion的分配。
HBase 中可以启动多个Master,通过 Zookeeper 的 Master Election(选主机制) 机制保证总有一个 Master 运行。
ZooKeeper
1.HBase 通过 Zookeeper 来做 Master 的高可用。
2.RegionServer 的监控、元数据的入口以及集群配置的维护等工作。
主要职责:
1.保证任何时候,集群中只有一个Master。
2.存储所有Region的寻址入口。
3.实时监控Region Server的上线和下线信息,并实时通知给Master。
4.存储HBase的Schema和Table元数据。
2.HMaster、HRegion Server、ZooKeeper协作原理
协作基本原理
1.HMaster 与 HRegion Server 之间通过ZooKeeper的 发布与订阅 机制进行感知与管理。
2.HMaster集群通过ZooKeeper的竞争选举机制来维护HMaster集群的高可用性。
HRegion Server 上下线
上线
1.HMaster通过Zookeeper来追踪HRegion Server的状态。
2.HRegion Server 上线时,首先在Zookeeper的Server目录中创建自己的文件,并取得文件的独占锁。
3.由于HMaser订阅了Server目录,当目录下有文件增加或者删除时,HMaster能收到来自Zookeeper的实时通知,因此当HRegion Server上线时HMaster能马上得到消息。
下线
1.HRegion Server下线时,它断掉了Zookeeper的通讯,Zookeeper便会释放代表Server的文件的独占锁。
2.HMaster轮询Zookeeper Server目录下文件的独占锁。 当HMaster发现某个Region Server丢失了自己的独占锁(或者HMaster与HRegion server连续几次通讯都不成功), HMaster将尝试获取该文件的读写锁,一旦获取成功,说明:
2.1 该HRegion server与Zookeeper通讯已经断开
2.2 该HResion server挂了
无论哪种情况,HMaster将删除Server目录下代表该Server的文件,并将该Server的所有Region,并将其分配给其他活着的Server。 如果HRegion Server因为临时网络断开丢失了锁,并很快恢复与Zookeeper的通讯,只要代表其的文件没有被删除,它会继续尝试或许该文件的锁,一旦获取成功,它就可以接着服务
HMaster 上下线
上线
HMaster启动时:
1.从 Zookeeper中获取一个代表HMaster的锁,用以阻止其他Master成为Master(竞争选主机制)。
2.扫描Zookeeper中的Server目录,获取HRegion Server的List。
3.与2中获取的Server 通讯,获取已分配的Region和Region Server的对应关系。
4.扫描hbase:meta表,记录尚未分配的Region的信息,并添加到待分配的Region列表中。
下线
1.HMaster下线时,由于它不参与Client的IO操作,所以这些操作不受影响。
2.HMaster下线仅导致元数据的操作(比如无法创建表,无法修改表结构,无法进行负载均衡,无法进行region的合并,但是split可以进行,因为split只有HRegion Server参与)受影响,用户的IO操作可以继续进行。
PS:所以短时间内的HMaster下线对HBase集群影响不大。
小结
HMaster 和 HRegion Server通过在Zookeeper中创建的Ephemeral Node(临时节点)来完成注册、监听、集群管理等工作,具体原理如下:
1.活跃的HMaster监听Region Server的信息,并在其下线后重新分配Region server来恢复相应的服务。
2.不活跃的HMaster监听活跃HMaster的信息,并在起下线后重新选出活跃的HMaster进行服务。
3.HRegion工作原理
组件简介
WAL
即 Write Ahead Log,是HDFS上的一个文件,用以存储尚未进行持久化的数据。顺序写入。
所有写操作都会先保证将数据写入这个Log文件后,才会真正去更新MemStore,最后写入HFile文件中。
WAL的优点:
1.采用这种模式,可以保证HRegionServer宕机后,依然可以从该Log文件中读取数据,Replay所有的操作,而不至于数据丢失。
2.这个Log文件会定期Roll出新的文件而删除旧的文件 - 已经持久化到HFile中的Log可以删除。
Block Cache
是一个读缓存。在内存中存放经常读的数据,提升读的性能。
当缓存满的时候,最近最少使用的数据(Least Recently Used data)被踢出。
MemStore
是一个写缓存。存储的是按键排好序的待写入硬盘的数据。在Region中每个Column Family对应一个HStore,每个HStore有一个MemStore和多个HFile文件。
HFile
HBase中的健值数据对存储在HFile中。
当MemStore中积累到指定大小的数据后(默认128M),会Flush到一个HFile文件中(每次Flush都会生成一个新的HFile);当HFile增长到一定阈值后(默认8块),会触发Compact合并操作,使多个HFile合并成一个HFile。当合并的HFile数据大小超过512M时,会对HFile进行拆分。同时将当前Region拆分成2个Region,原来的Region会下线,新拆分出的2个子Region会被HMaster分配到相应的HRegionServer上,使得原先1个Redion的压力得以分流到2个Region上。
工作原理
请详解 4.HBase 读写流程
4.HBase 读写流程
读流程
流程如下:
1)Client 先访问 zookeeper,获取 hbase:meta 表位于哪个 Region Server。
2)访问对应的 Region Server,获取 hbase:meta 表,根据读请求的 namespace:table/rowkey,查询出目标数据位于哪个 Region Server中的哪个Region 中。并将该table的region信息以及meta表的位置信息缓存在客户端的meta cache,方便下次访问。
3)与目标 Region Server 进行通讯;
4)分别在 Block Cache(读缓存),MemStore 和 Store File(HFile)中查询目标数据,并将查到的所有数据进行合并。此处所有数据是指同一条数据的不同版本(time stamp)或者不同的类型(Put/Delete)。
5)将从文件中查询到的数据块(Block,HFile 数据存储单元,默认大小为 64KB)缓存到Block Cache。
6)将合并后的最终结果返回给客户端。
写流程
流程如下:
1)Client 先访问 zookeeper,获取 hbase:meta 表位于哪个 Region Server。
2)访问对应的 Region Server,获取 hbase:meta 表,根据读请求的 namespace:table/rowkey,查询出目标数据位于哪个 Region Server 中的哪个 Region 中。并将该 table 的 region 信息以及 meta 表的位置信息缓存在客户端的 meta cache,方便下次访问。
3)与目标 Region Server 进行通讯;
4)将数据顺序写入(追加)到 WAL;
5)将数据写入对应的 MemStore,数据会在 MemStore 进行排序;
6)向客户端发送 ack;
7)等达到 MemStore 的刷写时机后,将数据刷写到 HFile。
小结
第一次读和写操作:
有一个特殊的 HBase Catalog 表叫 Meta Table(它其实是一张特殊的 HBase 表),包含了集群中所有 Regions 的位置信息。Zookeeper 保存了这个 Meta table 的位置。
当HBase第一次读或者写操作到来时:
1.客户端从 Zookeeper 那里获取是哪一台 Region Server 负责管理 Meta table。
2.客户端会查询那台管理 Meta table 的 Region Server,进而获知是哪一台 Region Server 负责管理本次数据请求所需要的rowkey。客户端会缓存这个信息,以及Meta table 的位置信息本身。
3.然后客户端回去访问那台 Region Server,获取数据。
对于以后的的读请求,客户端从可以缓存中直接获取 Meta table 的位置信息(在哪一台 Region Server 上),以及之前访问过的 rowkey 的位置信息(哪一台 Region Server 上),除非因为 Region 被迁移了导致缓存失效。这时客户端会重复上面的步骤,重新获取相关位置信息并更新缓存。
PS1:
户端读写数据,实际上分了两步:
第一步是定位,从 Meta table 获取 rowkey 属于哪个 Region Server 管理;
第二步再去相应的 Region Server 读写数据。这里涉及到了两个 Region Server,要理解它们各自的角色功能。
PS2:
HBase 保证 hbase:meta 表始终只有一个 Region,这是为了确保 meta 表多次操作的原子性。
5.HBase 存储机制
Flush 机制
MemStore刷写时机及过程
1.当某个MemStore的大小达到了hbase.hregion.memstore.flush.size(默认值 128M),其所在 region 的所有 memstore (对应的列簇)都会刷写。
当达到128M的时候会触发flush memstore,当达到128M * n还没法触发flush时候会抛异常来拒绝写入。
两个相关参数的默认值如下:
hbase.hregion.memstore.flush.size=128M(默认)
hbase.hregion.memstore.block.multiplier=4(默认)
2.当 region server 中 memstore 的总大小达到
java_heapsize(应用的堆内存)
*hbase.regionserver.global.memstore.size(默认值 0.4)
*hbase.regionserver.global.memstore.size.lower.limit(默认值 0.95),
region 会按照其所有 memstore 的大小顺序(由大到小)依次进行刷写。直到 region server中所有 memstore 的总大小减小到上述值以下。 当 region server 中 memstore 的总大小达到java_heapsize*hbase.regionserver.global.memstore.size(默认值 0.4)时,会阻止继续往所有的 memstore 写数据。
3.到达自动刷写的时间,也会触发 memstore flush。自动刷新的时间间隔由该属性进行配置 hbase.regionserver.optionalcacheflushinterval(默认 1 小时)。
4.当 WAL 文件的数量超过 hbase.regionserver.maxlogs,region 会按照时间顺序依次进行刷写,直到 WAL 文件数量减小到 hbase.regionserver.maxlogs 以下(该属性名已经废弃,现无需手动设置,最大值为 32)。
StoreFile Compaction 机制
HBase StoreFile Compaction机制:
由于memstore每次刷写都会生成一个新的HFile,且同一个字段的不同版本(timestamp)和不同类型(Put/Delete)有可能分布在不同的HFile中,因此查询时需要遍历所有的HFile。为了减少HFile的个数,以及清除掉过期和删除的数据,会进行StoreFile Compaction。
Compaction分为两种,分别时Minor Compaction和Major Compaction。
- Minor Compaction 会将临时的若干较小的HFile合并成一个较大的HFile,但不会清理过期和删除的数据。
- Major Compaction 会将一个Store下的所有HFile合并为一个大HFile,并且会清理掉过期和删除的数据。
Region Split 机制
Region Split机制:
默认情况下,每个 Table 起初只有一个 Region,随着数据的不断写入,Region 会自动进行拆分。刚拆分时,两个子 Region 都位于当前的 Region Server,但处于负载均衡的考虑,HMaster 有可能会将某个 Region 转移给其他的 Region Server。
Region Split 时机:
1)当1个region中的某个Store下所有StoreFile的总大小超过hbase.hregion.max.filesize,该 Region 就会进行拆分(0.94 版本之前)。
2)当 1 个 region 中 的 某 个 Store 下所有 StoreFile 的 总 大 小 超 过 Min(R^2 *"hbase.hregion.memstore.flush.size",hbase.hregion.max.filesize"),该 Region 就会进行拆分,其中 R 为当前 Region Server 中属于该 Table 的个数(0.94 版本之后)。
6.HBase逻辑表 与 物理存储表
逻辑表
物理存储
数据模型(相关术语)
1)Name Space
命名空间,类似于关系型数据库的database概念,每个命名空间下有多个表。HBase两个自带的命名空间,分别是hbase和default,hbase中存放的是HBase内置的表,default表是用户默认使用的命名空间。
一个表可以自由选择是否有命名空间,如果创建表的时候加上了命名空间后,这个表名字以<Namespace>:<Table>作为区分!
2)Table
类似于关系型数据库的表概念。不同的是,HBase定义表时只需要声明列族即可,数据属性,比如超时时间(TTL),压缩算法(COMPRESSION)等,都在列族的定义中定义,不需要声明具体的列。
这意味着,往HBase写入数据时,字段可以动态、按需指定。因此,和关系型数据库相比,HBase能够轻松应对字段变更的场景。
3)Row
HBase表中的每行数据都由一个RowKey和多个Column(列)组成。一个行包含了多个列,这些列通过列族来分类,行中的数据所属列族只能从该表所定义的列族中选取,不能定义这个表中不存在的列族,否则报错NoSuchColumnFamilyException。
4) RowKey
Rowkey由用户指定的一串不重复的字符串定义,是一行的唯一标识!数据是按照RowKey的字典顺序存储的,并且查询数据时只能根据RowKey进行检索,所以RowKey的设计十分重要。
如果使用了之前已经定义的RowKey,那么会将之前的数据更新掉!
5)Column Family
列族是多个列的集合。一个列族可以动态地灵活定义多个列。表的相关属性大部分都定义在列族上,同一个表里的不同列族可以有完全不同的属性配置,但是同一个列族内的所有列都会有相同的属性。
列族存在的意义是HBase会把相同列族的列尽量放在同一台机器上,所以说,如果想让某几个列被放到一起,你就给他们定义相同的列族。
官方建议一张表的列族定义的越少越好,列族太多会极大程度地降低数据库性能,且目前版本Hbase的架构,容易出BUG。
6) Column Qualifier
Hbase中的列是可以随意定义的,一个行中的列不限名字、不限数量,只限定列族。因此列必须依赖于列族存在!列的名称前必须带着其所属的列族!例如info:name,info:age。
因为HBase中的列全部都是灵活的,可以随便定义的,因此创建表的时候并不需要指定列!列只有在你插入第一条数据的时候才会生成。其他行有没有当前行相同的列是不确定,只有在扫描数据的时候才能得知!
7)TimeStamp
用于标识数据的不同版本(version)。时间戳默认由系统指定,也可以由用户显式指定。
在读取单元格的数据时,版本号可以省略,如果不指定,Hbase默认会获取最后一个版本的数据返回!
8)Cell
一个列中可以存储多个版本的数据。而每个版本就称为一个单元格(Cell)。
Cell由{rowkey, column Family:column Qualifier, time Stamp}确定。
Cell中的数据是没有类型的,全部是字节码形式存贮。
9)Region
Region由一个表的若干行组成!在Region中行的排序按照行键(rowkey)字典排序。
Region不能跨RegionSever,且当数据量大的时候,HBase会拆分Region。
Region由RegionServer进程管理。HBase在进行负载均衡的时候,一个Region有可能会从当前RegionServer移动到其他RegionServer上。
Region是基于HDFS的,它的所有数据存取操作都是调用了HDFS的客户端接口来实现的。
7.HBase特性 及 常用场景
特性
- 千万级高并发
- PB级存储(千亿数据量)
- 非结构化存储(列式数据库)
- 动态列,稀疏列
- 支持二级索引
- 强一致性,可靠性,扩展性
场景
- 写密集型应用,每天写入量巨大,而相对读数量较小的应用
- 不需要复杂查询条件来查询数据的应用
- 使用rowkey,单条记录或者小范围的查询性能不错,大范围的查询由于分布式的原因,可能在性能上有点影响。
- 使用HBase的过滤器的话性能比较差。
- 不需要关联的场景,HBase为NoSQL无法支持join
- 可靠性要求高
- master支持主备热切。
- regionServer宕机,region会分配给在线的机器。
- 数据持久化在HDFS,默认3份,HDFS保证数据可靠性。
- 内存的数据若丢失可以通过Wal预写日志恢复。
- 数据量较大,而且增长量无法预估的应用
- HBase支持在线扩展,即使在一段时间内数据量呈井喷式增长,也可以通过HBase横向扩展来满足功能。
应用
- 对象存储系统
HBase MOB(Medium Object Storage),中等对象存储是hbase-2.0.0版本引入的新特性,用于解决hbase存储中等文件(0.1m~10m)性能差的问题。这个特性适合将图片、文档、PDF、小视频存储到Hbase中。 - OLAP的存储
Kylin的底层用的是HBase的存储,看中的是它的高并发和海量存储能力。kylin构建cube的过程会产生大量的预聚合中间数据,数据膨胀率高,对数据库的存储能力有很高要求。
Phoenix是构建在HBase上的一个SQL引擎,通过phoenix可以直接调用JDBC接口操作Hbase,虽然有upsert操作,但是更多的是用在OLAP场景,缺点是非常不灵活。 - 时序型数据
openTsDB应用,记录以及展示指标在各个时间点的数值,一般用于监控的场景,是HBase上层的一个应用。 - 用户画像系统
动态列,稀疏列的特性。用于描述用户特征的维度数是不定的且可能会动态增长的(比如爱好,性别,住址等); 不是每个特征维度都会有数据; - 消息/订单系统
强一致性,良好的读性能 - feed流系统存储
8.HBase与关系型数据库的区别
指标 | 传统关系数据库 | HBase |
---|---|---|
数据类型 | 有丰富的数据类型 | 字符串 |
数据操作 | 丰富操作,复杂联表查询 | 简单CRUD |
存储模式 | 基于行存储 | 基于列存储 |
数据索引 | 复杂的多个索引 | 只有RowKey索引 |
数据维护 | 新覆盖旧 | 多版本 |
可伸缩性 | 难实现横向扩展 | 性能动态伸缩 |
9.HBase最佳实践
RowKey 的设计原则
1.RowKey 长度原则
二进制码流RowKey 最大长度 64Kb,实际应用中一般为 10-100bytes,以 byte[] 形式保存,一般设计定长。建议越短越好,因为HFile是按照KV存储的Key太大浪费空间。
2.RowKey 散列原则
RowKey 在设计时候要尽可能的实现可以将数据均衡的分布在每个 RegionServer 上,防止热点Region的产生。
Region热点问题解决
- Reverse反转
针对固定长度的Rowkey反转后存储,这样可以使Rowkey中经常改变的部分放在最前面,可以有效的随机Rowkey。 - Salt加盐
Salt是将每一个Rowkey加一个前缀,前缀使用一些随机字符,使得数据分散在多个不同的Region,达到Region负载均衡的目标。 - Hash散列或者Mod
用Hash散列来替代随机Salt前缀的好处是能让一个给定的行有相同的前缀,这在分散了Region负载的同时,使读操作也能够推断。
3.排序原则
HBase的Rowkey是按照ASCII有序设计的,我们在设计Rowkey时要充分利用这点。
4.RowKey 唯一原则
RowKey 必须在设计上保证其唯一性,RowKey 是按照字典顺序排序存储的,因此设计 RowKey 时可以将将经常读取的数据存储到一块。
HBase 优化方法
减少调整
HBase中有几个内容会动态调整,如Region(分区)、HFile。通过一些方法可以减少这些会带来I/O开销的调整。
Region
没有预建分区的话,随着Region中条数的增加,Region会进行分裂,这将增加I/O开销,所以解决方法就是根据你的RowKey设计来进行预建分区,减少Region的动态分裂。
HFile
MemStore执行flush会生成HFile,同时HFile过多时候也会进行Merge, 为了减少这样的无谓的I/O开销,建议估计项目数据量大小,给HFile设定一个合适的值。
减少启停
数据库事务机制就是为了更好地实现批量写入,较少数据库的开启关闭带来的开销,那么HBase中也存在频繁开启关闭带来的问题。
关闭 Compaction
HBase 中自动化的Minor Compaction和Major Compaction会带来极大的I/O开销,为了避免这种不受控制的意外发生,建议关闭自动Compaction,在闲时进行compaction。
减少数据量
开启过滤,提高查询速度
开启BloomFilter,BloomFilter是列族级别的过滤,在生成一个StoreFile同时会生成一个MetaBlock,用于查询时过滤数据。
使用压缩
一般推荐使用Snappy和LZO压缩