1 HBase 浅析
1.1 HBase 是啥
HBase 是一款面向列存储,用于存储处理海量数据的 NoSQL 数据库。它的理论原型是Google 的 BigTable 论文。你可以认为 HBase 是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统。
HBase 的存储是基于HDFS的,HDFS 有着高容错性的特点,被设计用来部署在低廉的硬件上,基于 Hadoop 意味着 HBase 与生俱来的超强的扩展性和吞吐量。
HBase 采用的时key/value的存储方式,这意味着,即使随着数据量的增大,也几乎不会导致查询性能的下降。HBase 又是一个面向列存储的数据库,当表的字段很多时,可以把其中几个字段独立出来放在一部分机器上,而另外几个字段放到另一部分机器上,充分分散了负载的压力。如此复杂的存储结构和分布式的存储方式,带来的代价就是即便是存储很少的数据,也不会很快。
HBase 并不是足够快,只是数据量很大的时候慢的不明显。HBase主要用在以下两种情况:
1.2 HBase 的由来
我们知道 Mysql 是一个关系型数据库,学数据库的时第一个接触的就是 MySQL 了。但是 MySQL 的性能瓶颈是很大的,一般单个table行数不宜超过500万行,大小不宜超过2G。
我们以互联网公司最核心用户表为例,当数据量达到千万甚至亿级别时候,尽管你可以通过各种优化来提速查询,但是对单条数据的检索耗时还是会超出你的预期!看下这个User表:
假如查询 id=1 这条数据对应的用户name,系统会给我们返回aa。但由于MySQL是以行为位单位存储的,当查 name 时却需要查询一整行的数据,连 age 和 email 也会被查出来!如果列非常多,那么查询效率可想而知了。
我们称列过多的表为宽表,优化方法一般就是对列进行竖直拆分:
此时查找 name 时只需要查找 user_basic 表,没有多余的字段,查询效率就会很快。如果一张表的行过多,会影响查询效率,我们将这样的表称之为高表,可以采用水平拆表的方式提高效率:
这种水平拆分应用比较多的 场景就是日志表,日志信息每天产生很多,可以按月/按日进行水平拆分,这样就实现了高表变矮。
上述的拆分方式貌似可以解决宽表跟高表问题,但是如果有一天公司业务变更,比如原来没有微信,现在需加入用户的微信字段。这时候需要改变表的结构信息,该怎么办?最简单的想法是多加一列,像这样:
但是你要知道不是所有用户都要微信号的,微信号这一列是设置默认值还是采取其他的做法就得权衡一下了。如果需扩展很多列出来,但不是所有的用户都有这些属性,那么拓展起来就更加复杂了。这时可以用下JSON格式的字符串,将若干可选择填写信息汇总,而且属性字段可以动态拓展,于是有了下边做法:
至此你可能认为这样存储数据它不挺好的嘛,用 HBase 出来干嘛?Mysql 有个致命缺点,就是当数据达到一定的阈值,无论怎么优化,它都无法达到高性能的发挥。而大数据领域的数据,动辄 PB 级数据量,这种存储应用明显是不能很好的满足需求的!并且针对上边的问题,HBase 都有很好的解决方案~~。
1.3 HBase 设计思路
接着上边说到的几个问题:高表、宽表、数据列动态扩展,把提到的几个解决办法:水平切分、垂直切分、列扩展方法 杂糅在一起。
有张表,你怕它又宽又高跟动态扩展列,那么在设计之初,就把这个表给拆开,为了列的动态拓展,直接存储JSON格式:
这样就解决了宽表跟列扩展问题,高表怎么办呢?一个表按行切分成partition,各存一部分行:
解决了高表、宽表、动态扩展列 的问题后你会发现数据量大了速度不够快咋办?用缓存呗,查询出的数据放缓存中,下次直接从缓存拿数据。插入数据怎么办呢?也可以这样理解,我把要插入的数据放进缓存中,再也不用管了,直接由数据库从缓存拿数据插入到数据库。此时程序不需要等待数据插入成功,提高了并行工作的效率。
你用缓存的考虑服务器宕机后缓存中数据没来得及插入到数据库中造成丢数据咋办?参考 Redis 的持久化策略,可以插入数据这个操作添加一个操作日志,用于持久化插入操作,宕机重启后从日志恢复。这样设计架构就变成了这个样子:
这就是 HBase 实现的大致思路。接下来正式进入 HBase 设计解析。
2 Hbase 简介
Hbase 官网:hbase.apache.org
2.1 HBase 特点
- 海量存储
- 列式存储
- 高并发
- 稀疏性
- 极易扩展
2.2 HBase 逻辑结构
逻辑思维层面 HBase的存储模型如下:
- Table(表):
- Column (列):
- Column Family(列族):
- Row(行):
- RowKey(行键):
- Region(区域):
- RegionServer:
2.3 HBase 物理存储
以上只是一个基本的逻辑结构,底层的物理存储结构才是重中之重的内容,看下图
- NameSpace:
- TimeStamp:
- Cell:
3 HBase 底层架构
3.1 Client
Client 包含了访问 Hbase 的接口,另外 Client 还维护了对应的 cache 来加速 Hbase 的访问,比如缓存元数据的信息。
3.2 Zookeeper
HBase 通过 Zookeeper 来做 Master 的高可用、RegionServer 的监控、元数据的入口以及集群配置的维护等工作。Zookeeper 职责如下:
- 通过Zoopkeeper来保证集群中只有1个Master 在运行,如果Master 发生异常会通过竞争机制产生新的Master 来提供服务。
- 通过 Zoopkeeper 来监控 RegionServer 的状态,当RegionSevrer有异常的时候,通过回调的形式通知MasterRegionServer上下线的信息。
- 通过 Zoopkeeper 存储元数据 hbase:meata 的统一入口地址。
3.3 Master
Master 在 HBase 中的地位比其他类型的集群弱很多!数据的读写操作与他没有关系,它挂了之后,集群照样运行。但是Master 也不能宕机太久,有很多必要的操作,比如创建表、修改列族配置等DDL跟Region的分割与合并都需要它的操作。
- 负责启动的时候分配Region到具体的 RegionServer。
- 发现失效的 Region,并将失效的 Region 分配到正常的 RegionServer 上。
- 管理HRegion服务器的负载均衡,调整HRegion分布。
- 在HRegion分裂后,负责新HRegion的分配。
HBase 中可以启动多个Master,通过 Zookeeper 的 Master Election 机制保证总有一个 Master 运行。
3.4 RegionServer
HregionServer 直接对接用户的读写请求,是真正的干活的节点。它的功能概括如下:
- 管理Master为其分配的Region。
- 处理来自客户端的读写请求。
- 负责和底层HDFS的交互,存储数据到HDFS。
- 负责Region变大以后的拆分。
- 负责StoreFile的合并工作。
ZooKeeper 会监控 RegionServer 的上下线情况,当 ZK 发现某个 HRegionServer 宕机之后会通知 Master 进行失效备援。下线的 RegionServer 所负责的 Region 暂时停止对外提供服务,Master 会将该 RegionServer 所负责的 Region 转移到其他 RegionServer 上,并且会对 下线RegionServer 上存在 MemStore 中还未持久化到磁盘中的数据由 WAL重播进行恢复。
3.5 WAL
WAL (Write-Ahead-Log) 预写日志是 HBase 的 RegionServer 在处理数据插入和删除的过程中用来记录操作内容的一种日志。每次Put、Delete等一条记录时,首先将其数据写入到 RegionServer 对应的HLog文件中去。只有当WAL日志写入成功的时候,客户端才会被告诉提交数据成功。如果写WAL失败会告知客户端提交失败,这其实就是数据落地的过程。
WAL是保存在HDFS上的持久化文件。数据到达 Region 时先写入WAL,然后被加载到MemStore中。这样就算Region宕机了,操作没来得及执行持久化,也可以再重启的时候从WAL加载操作并执行。跟Redis的AOF类似。
- 在一个 RegionServer 上的所有 Region 都共享一个 HLog,一次数据的提交先写入WAL,写入成功后,再写入MenStore之中。当MenStore的值达到一定的时候,就会形成一个个StoreFile。
- WAL 默认是开启 的,也可以手动关闭它,这样增删改操作会快一点。但是这样做牺牲的是数据的安全性。如果不想关闭WAL,又不想每次都耗费那么大的资源,每次改动都调用HDFS客户端,可以选择异步的方式写入WAL(默认间隔1秒写入)
- 如果你学过 Hadoop 中的 Shuffle(edits文件) 机制的就可以猜测到 HBase 中的 WAL 也是一个滚动的日志数据结构,一个WAL实例包含多个WAL文件,WAL被触发滚动的条件如下。
3.5 Region
每一个 Region 都有起始 RowKey 和结束 RowKey,代表了存储的Row的范围。从大图中可知一个Region有多个Store,一个Store就是对应一个列族的数据,Store 由 MemStore 和 HFile 组成的。
3.6 Store
Store 由 MemStore 跟 HFile 两个重要的部分。
3.6.1 MemStore
每个 Store 都有一个 MemStore 实例,数据写入到 WAL 之后就会被放入 MemStore 中。MemStore是内存的存储对象,当 MemStore 的大小达到一个阀值(默认64MB)时,MemStore 会被 flush到文件,即生成一个快照。目前HBase 会有一个线程来负责MemStore 的flush操作。
3.6.2 StoreFile
MemStore 内存中的数据写到文件后就是StoreFile,StoreFile底层是以 HFile 的格式保存。HBase以Store的大小来判断是否需要切分Region。
3.6.3 HFile
在Store中有多个HFile,每次刷写都会形成一个HFile文件落盘在HDFS上。HFile文件也会动态合并,它是数据存储的实体。
这里提出一点疑问:操作到达Region时,数据进入HFile之前就已经被持久化到WAL了,而WAL就是在HDFS上的,为什么还要从WAL加载到MemStore中,再刷写成HFile呢?
3.7 HDFS
HDFS 为 HBase 提供最终的底层数据存储服务,HBase 底层用HFile格式 (跟hadoop底层的数据存储格式类似) 将数据存储到HDFS中,同时为HBase提供高可用(Hlog存储在HDFS)的支持,具体功能概括如下:
4 HBase 读写
在HBase集群中如果我们做 DML 操作是不需要关心 HMaster 的,只需要从 ZooKeeper 中获得hbase:meta 数据地址,然后从RegionServer中增删查数据即可。
4.1 HBase 写流程
- Client 先访问 zookeeper,访问 /hbase/meta-region-server 获取 hbase:meta 表位于哪个 Region Server。
- 访问对应的 Region Server,获取 hbase:meta 表,根据读请求的 namespace:table/rowkey,查询出目标数据位于哪个 Region Server 中的哪个 Region 中。并将该 table 的 Region 信息以及 meta 表的位置信息缓存在客户端的 meta cache,方便下次访问。
- 与目标 Region Server 进行通讯。
- 将数据顺序写入(追加)到 WAL。
- 将数据写入对应的 MemStore,数据会在 MemStore 进行排序。
- 向客户端发送 ack,此处可看到数据不是必须落盘的。
- 等达到 MemStore 的刷写时机后,将数据刷写到 HFile
- 在web页面查看的时候会随机的给每一个Region生成一个随机编号。
4.2 HBase 读流程
- Client 先访问 ZooKeeper,获取 hbase:meta 表位于哪个 Region Server。
- 访问对应的 Region Server,获取 hbase:meta 表,根据读请求的 namespace:table/rowkey, 查询出目标数据位于哪个 Region Server 中的哪个 Region 中。并将该 table 的 region 信息以 及 meta 表的位置信息缓存在客户端的 meta cache,方便下次访问。
- 与目标 Region Server 进行通讯。
- 分别在 Block Cache(读缓存),MemStore 和 Store File(HFile)中查询目标数据,并将 查到的所有数据进行合并。此处所有数据是指同一条数据的不同版本(time stamp)或者不同的类型(Put/Delete)。
- 将从文件HFile中查询到的数据块(Block,HFile 数据存储单元,默认大小为 64KB)缓存到 Block Cache。
- 将合并后的最终结果,然后返回时间最新的数据返回给客户端。
4.2.1 Block Cache
HBase 在实现中提供了两种缓存结构 MemStore(写缓存) 和 BlockCache(读缓存)。写缓存前面说过不再重复。
- HBase 会将一次文件查找的 Block块 缓存到 Cache中,以便后续同一请求或者邻近数据查找请求,可以直接从内存中获取,避免昂贵的IO操作。
- BlockCache是Region Server级别的,
- 一个Region Server只有一个Block Cache,在 Region Server 启动的时候完成 Block Cache 的初始化工作。
- HBase对Block Cache的管理分为如下三种。
重点:
结论:
4.3 HBase 为什么写比读快
- HBase 能提供实时计算服务主要原因是由其架构和底层的数据结构决定的,即由LSM-Tree(Log-Structured Merge-Tree) + HTable(Region分区) + Cache决定的。
- HBase 写入速度快是因为数据并不是真的立即落盘,而是先写入内存,随后异步刷入HFile。所以在客户端看来,写入速度很快。
- HBase 存储到内存中的数据是有序的,内存数据刷写到HFile时也是有序的。并且多个有序的HFile还会进行归并排序生成更大的有序HFile。性能测试发现顺序读写磁盘速度比随机读写磁盘快至少三个数量级!
- 读取速度快是因为它使用了LSM树型结构,因为磁盘寻址耗时远远大于磁盘顺序读取的时间,HBase的架构设计导致我们可以将磁盘寻址次数控制在性能允许范围内。
- LSM 树原理把一棵大树拆分成N棵小树,它首先写入内存中,随着小树越来越大,内存中的小树会flush到磁盘中,磁盘中的树定期可以做merge操作来合并成一棵大树,以优化读性能。
4.3.1查询举例
- 根据RowKey能快速找到行所在的Region,假设有10亿条记录,占空间1TB。分列成了500个Region,那读取2G的记录,就能找到对应记录。
- 数据是按照列族存储的,假设分为3个列族,每个列族就是666M, 如果要查询的东西在其中1个列族上,1个列族包含1个或者多个 HStoreFile,假设一个HStoreFile是128M, 该列族包含5个HStoreFile在磁盘上. 剩下的在内存中。
- 内存跟磁盘中数据是排好序的,你要的记录有可能在最前面,也有可能在最后面,假设在中间,我们只需遍历2.5个HStoreFile共300M。
- 每个HStoreFile(HFile的封装),是以键值对(KV)方式存储,只要遍历一个个数据块中的key的位置,并判断符合条件可以了。一般key是有限的长度,假设KV比是1:19,最终只需要15M就可获取的对应的记录,按照磁盘的访问100M/S,只需0.15秒。加上Block Cache 会取得更高的效率。
- 大致理解读写思路后你会发现如果你在读写时设计的足够巧妙当然读写速度快的很咯。
5 HBase Flush
5.1 Flush
对于用户来说数据写到 MemStore 中就算OK,但对于底层代码来说只有数据刷到硬盘中才算彻底搞定了!因为数据是要写入到WAL(Hlog)中再写入到MemStore中的,flush有如下几个时机。
- 当 WAL 文件的数量超过设定值时 Region 会按照时间顺序依次进行刷写,直到 WAL 文件数量小于设定值。
- 当Region Server 中 MemStore 的总大小达到堆内存40%时,Region 会按照其所有 MemStore 的大小顺序(由大到小)依次进行阻塞刷写。直到Region Server中所有 MemStore 的总大小减小到上述值以下。当阻塞刷写到上个参数的0.95倍时,客户端可以继续写。
- 当某个 MemStore 的大小达到了128M时,其所在 Region 的所有 MemStore 都会阻塞刷写。
- 到达自动刷写的时间也会触发 MemStore 的 flush。自动刷新的时间间隔默认1小时。
5.2 StoreFile Compaction
由于 MemStore 每次刷写都会生成一个新的 HFile,且同一个字段的不同版本(timestamp) 和不同类型(Put/Delete)有可能会分布在不同的 HFile 中,因此查询时需要遍历所有的 HFile。为了减少 HFile 的个数跟清理掉过期和删除的数据,会进行 StoreFile Compaction。
Compaction 分为两种,分别是 Minor Compaction 和 Major Compaction。
5.3 Region Split
每个 Table 起初只有一个 Region,随着不断写数据 Region 会自动进行拆分。刚拆分时,两个子 Region 都位于当前的 Region Server,但出于负载均衡的考虑, HMaster 有可能会将某个 Region 转移给其他的 Region Server。
Region Split 时机:
- 0.94 版本之前:
- 0.94 版本之后:
举例:
官方不建议用多个列族,比如有CF1,CF2,CF3,但是 CF1数据很多而CF2跟CF3数据很少,那么当触发了region切分的时候,会把CF2跟CF3分成若干小份,不利于系统维护。
6 HBase 常见面试题
6.1 Hbase 中 RowKey 的设计原则
- RowKey 长度原则
- RowKey 散列原则
- RowKey 唯一原则
6.2 HBase 在大数据体系位置
其实就简单的把HBase当成大数据体系下的DataBase来用就行,任何可以分析HBase的引擎比如MR、Hive、Spark等框架连接上HBase都可以实现控制。比如你可以把Hive跟HBase进行关联,Hive中数据不再由HDFS存储而是存储到HBase中,并且关联后Hive中添加数据在HBase中可看到,HBase中添加数据Hive也可看到。
6.3 HBase 优化方法
6.3.1 减少调整
HBase中有几个内容会动态调整,如Region(分区)、HFile。通过一些方法可以减少这些会带来I/O开销的调整。
- Region
- HFile
6.3.2 减少启停
数据库事务机制就是为了更好地实现批量写入,较少数据库的开启关闭带来的开销,那么HBase中也存在频繁开启关闭带来的问题。
- 关闭 Compaction。
6.3.3 减少数据量
- 开启过滤,提高查询速度
- 使用压缩
6.3.4 合理设计
HBase 表格中 RowKey 和 ColumnFamily 的设计是非常重要,好的设计能够提高性能和保证数据的准确性。
- RowKey设计
- 列族的设计
6.4 HBase 跟关系型数据库区别
6.5 HBase 批量导入
- 通过 HBase API进行批量写入数据。
- 使用 Sqoop工具批量导数到HBase集群。
- 使用 MapReduce 批量导入。
- HBase BulkLoad的方式。
- HBase 通过 Hive 关联导入数据。
大数据导入用 HBase API 跟 MapReduce 写入效率会很低,因为请求RegionServer 将数据写入,这期间数据会先写入 WAL 跟 MemStore,MemStore 达到阈值后会刷写到磁盘生成 HFile文件,HFile文件过多时会发生Compaction,如果Region大小过大时也会发生Split。
BulkLoad 适合初次数据导入,以及HBase与Hadoop为同一集群。BulkLoad 是使用 MapReduce 直接生成 HFile 格式文件后,Region Servers 再将 HFile 文件移动到相应的Region目录下。