熬夜肝 - 回顾·HBase设计的实践经验(四)|Java 开发实战

572 阅读12分钟

今天分享的内容是关于HBASE相关设计的实战经验,记录学到的东西。

今儿是第二篇关于HBASE的合并、分裂和故障恢复。

- 一、HBASE简介

- 二、详解HBASE的读和写、读放大、合并、故障恢复等

- 三、HBASE在告警信息的使用

- 四、 HBASE的优化经验

HBASE是什么?

HBase是一个分布式的、面向列的开源数据库,该技术来源于 Fay Chang 所撰写的Google论文“Bigtable:一个结构化数据的分布式存储系统”。就像Bigtable利用了Google文件系统(File System)所提供的分布式数据存储一样,HBase在Hadoop之上提供了类似于Bigtable的能力。

HBase是Apache的Hadoop项目的子项目。HBase不同于一般的关系数据库,它是一个适合于非结构化数据存储的数据库。另一个不同的是HBase基于列的而不是基于行的模式。

(1)数据模型

image.png

(2)逻辑架构

image.png

(3)系统架构

image.png

(4)怎么读?

(5)Region Server 是什么,存的什么?

(6)怎么写数据?

(7)MemStore 即写缓存是个什么东西?

(8)缓存写完了怎么刷盘呢,总要写到磁盘上去吧?

(9)HFILE是什么鬼?

上一篇文章解决了这些问题!更新来啦,我直接接着写吧

二、读放大、写放大、故障恢复

(10)什么时候会触发读合并?

上一篇说了,当我们读取数据时,首先是定位,从 Meta table 获取 rowkey 属于哪个 Region Server 管理,而Region Server又有读缓存、写缓存、HFILE

因此我们读一个数据,它有可能在读缓存(LRU),也有可能刚刚才写入还在写缓存,或者都不在,则HBase 会使用 Block Cache 中的索引和布隆过滤器来加载对应的 HFile 到内存,因此数据可能来自读缓存、scanner 读取写缓存和HFILE,这就叫左HBASE的读合并

之前说过写缓存可能存在多个HFILE,因此一个读请求可能会读多个文件,影响性能,这也被称为读放大(read amplification)。

(11)既然存在读放大,那么有没有少去读多个文件的办法? --写合并

简单的说就是,HBase 会自动合并一些小的 HFile,重写成少量更大的 HFiles

它使用归并排序算法,将小文件合并成大文件,有效减少 HFile 的数量

这个过程被称为 写合并(minor compaction)

(12)写合并针对的是哪些HFILE?

1.它合并重写每个 列族(Column Family) 下的所有的 HFiles

2.在这个过程中,被删除的和过期的 cell 会被真正从物理上删除,这能提高读的性能

3.但是因为 major compaction 会重写所有的 HFile,会产生大量的硬盘 I/O 和网络开销。这被称为写放大(Write Amplification)。

4.HBASE默认是自动调度,因为存在写放大,建议在凌晨或周末进行

5.Major compaction 还能将因为服务器 crash 或者负载均衡导致的数据迁移重新移回到离 Region Server 的地方,这样就能恢复本地数据( data locality)。

(13)不是说Region Server管理多个Region,到底管理几个呢,Region什么时候会扩容? --Region分裂

我们再来回顾一下 region 的概念:

  • HBase Table 被水平切分成一个或数个 regions。每个 region 包含了连续的,有序的一段 rows,以 start key 和 end key 为边界。
  • 每个 region 的默认大小为 1GB。
  • region 里的数据由 Region Server 负责读写,和 client 交互。
  • 每个 Region Server 可以管理约 1000 个 regions(它们可能来自一张表或者多张表)。

一开始每个 table 默认只有一个 region。当一个 region 逐渐变得很大时,它会分裂(split)成两个子 region,每个子 region 都包含了原来 region 一半的数据,这两个子 region 并行地在原来这个 region server 上创建,这个分裂动作会被报告给 HMaster。处于负载均衡的目的,HMaster 可能会将新的 region 迁移给其它 region server。

(14)因为分裂了,为了负载均衡可能在多个region server,造成了读放大,直到写合并的到来,重新迁移或合并到离 region server 节点附近的地方

(15)HBASE的数据怎么备份的?

所有的读写都发生在 HDFS 的主 DataNode 节点上。HDFS 会自动备份 WAL(写前日志) 和 HFile 的文件 blocks。HBase 依赖于 HDFS 来保证数据完整安全。当数据被写入 HDFS 时,一份会写入本地节点,另外两个备份会被写入其它节点。

WAL 和 HFiles 都会持久化到硬盘并备份。那么 HBase 是怎么恢复 MemStore 中还未被持久化到 HFile 的数据呢?

(16)HBASE的数据怎么宕机恢复的?

  1. 当某个 Region Server 发生 crash 时,它所管理的 region 就无法被访问了,直到 crash 被检测到,然后故障恢复完成,这些 region 才能恢复访问。Zookeeper 依靠心跳检测发现节点故障,然后 HMaster 会收到 region server 故障的通知。

  2. 当 HMaster 发现某个 region server 故障,HMaster 会将这个 region server 所管理的 regions 分配给其它健康的 region servers。为了恢复故障的 region server 的 MemStore 中还未被持久化到 HFile 的数据,HMaster 会将 WAL 分割成几个文件,将它们保存在新的 region server 上。每个 region server 然后回放各自拿到的 WAL 碎片中的数据,来为它所分配到的新 region 建立 MemStore。

  3. WAL 包含了一系列的修改操作,每个修改都表示一个 put 或者 delete 操作。这些修改按照时间顺序依次写入,持久化时它们被依次写入 WAL 文件的尾部。

  4. 当数据仍然在 MemStore 还未被持久化到 HFile 怎么办呢?WAL 文件会被回放。操作的方法是读取 WAL 文件,排序并添加所有的修改记录到 MemStore,最后 MemStore 会被刷写到 HFile。

HBASE基本讲解就到此结束了,明天开始讲解实战演练吧!


三、实战经验

怎么设计rowkey

加班回来才能开始写文章,好了,开始了。

在告警业务场景中,一般分为两类场景

  1. 瞬时事件类型 --通常开始就结束。
  2. 持续事件类型 --通常开始一段时间在结束。

针对这两种情况,我们可以涉及rowkey为 : 唯一标识id + 时间 + 告警类型

我们对id做一个md5,做一个哈希,这样可以保证数据的分配均衡.

指标平台

第二个场景叫做指标平台,我们使用kylin做了一层封装,在这上面可以选择我们在HBase存储好的数据,可以选择哪些维度,去查询哪些指标。比如这个成交数据,可以选择时间,城市。就会形成一张图,进而创建一张报表。然后这张报表可以分享给其他人使用。

为什么会选择Kylin呢,因为Kylin是一个molap引擎,他是一个运算模型,他满足我们的需求,对页面的相应的话,需要亚秒级的响应。

第二,他对并发有一定的要求,原始的数据达到了百亿的规模。另外需要具有一定的灵活性,最好有sql接口,以离线为主。综合考虑,我们使用的是Kylin。

Kylin简介

Kylin给他家简单介绍一下,Apache Kylin™是一个开源的分布式分析引擎,提供Hadoop之上的SQL查询接口及多维分析(OLAP)能力以支持超大规模数据,最初由eBay Inc.开发并贡献至开源社区。它能在亚秒内查询巨大的Hive表。他的原理比较简单,基于一个并运算模型,我预先知道,我要从那几个维度去查询某个指标。在预定好的指标和维度的情况下去把所有的情况遍历一遍。利用molap把所有的结果都算出来,在存储到HBase中。然后根据sql查询的维度和指标直接到HBase中扫描就行了。为什么能够实现亚秒级的查询,这就依赖于HBase的计算。

kylin架构

和刚才讲的逻辑是一致的,左边是数据仓库。所有的数据都在数据仓库中存储。中间是计算引擎,把每天的调度做好,转化为HBase的KY结构存储在HBase中,对外提供sql接口,提供路由功能,解析sql语句,转化为具体的HBase命令。

image.png

Kylin中有一个概念叫Cube和Cubold,其实这个逻辑也非常简单,比如已经知道查询的维度有A,b,c,d四个。那abcd查询的时候,可以取也可以不取。一共有16种组合,整体叫做,cube。其中每一种组合叫做Cuboid。

image.png

Kylin如何在Hbase中进行物理存储的 --这里引用的是贝壳的用例

首先定义一张原始表,有两个维度,year和city。

在定义一个指标,比如总价。下面是所有的组合,刚才说到Kylin里面有很多cuboid组合,比如说前面三行有一个cuboid:00000011组合,他们在HBase中的RowKey就是cuboid加上各维度的取值。

这里面会做一点小技巧,对维度的值做一些编码,如果把程式的原始值放到Rowkey里,会比较长。

Rowkey也会存在一个sell里任何一个版本的值都会存在里面。如果rowkey变的非常长,对HBase的压力会非常大,所有通过一个字典编码去减少长度,通过这种方法,就可以把kylin中的计算数据存储到HBase中。

image.png

用比特位来做一些特征,多个维度统计分析,速度是比较快的,也是大数据分析常用的手段!!!

当然我们在实际中也配合apache Phoenix提供查询功能

四、优化经验

我先给出一个图

image.png

1. scan缓存是否设置合理?

优化原理:在解释这个问题之前,首先需要解释什么是scan缓存,通常来讲一次scan会返回大量数据,因此客户端发起一次scan请求,实际并不会一次就将所有数据加载到本地,而是分成多次RPC请求进行加载,这样设计一方面是因为大量数据请求可能会导致网络带宽严重消耗进而影响其他业务,另一方面也有可能因为数据量太大导致本地客户端发生OOM。在这样的设计体系下用户会首先加载一部分数据到本地,然后遍历处理,再加载下一部分数据到本地处理,如此往复,直至所有数据都加载完成。数据加载到本地就存放在scan缓存中,默认100条数据大小。通常情况下,默认的scan缓存设置就可以正常工作的。但是在一些大scan(一次scan可能需要查询几万甚至几十万行数据)来说,每次请求100条数据意味着一次scan需要几百甚至几千次RPC请求,这种交互的代价无疑是很大的。因此可以考虑将scan缓存设置增大,比如设为500或者1000就可能更加合适。笔者之前做过一次试验,在一次scan扫描10w+条数据量的条件下,将scan缓存从100增加到1000,可以有效降低scan请求的总体延迟,延迟基本降低了25%左右。

优化建议:大scan场景下将scan缓存从100增大到500或者1000,用以减少RPC次数

2. get请求是否可以使用批量请求?

优化原理:HBase分别提供了单条get以及批量get的API接口,使用批量get接口可以减少客户端到RegionServer之间的RPC连接数,提高读取性能。另外需要注意的是,批量get请求要么成功返回所有请求数据,要么抛出异常。

优化建议:使用批量get进行读取请求

3. 请求是否可以显示指定列族或者列?

优化原理:HBase是典型的列族数据库,意味着同一列族的数据存储在一起,不同列族的数据分开存储在不同的目录下。如果一个表有多个列族,只是根据Rowkey而不指定列族进行检索的话不同列族的数据需要独立进行检索,性能必然会比指定列族的查询差很多,很多情况下甚至会有2倍~3倍的性能损失。

优化建议:可以指定列族或者列进行精确查找的尽量指定查找

4. 离线批量读取请求是否设置禁止缓存?

优化原理:通常离线批量读取数据会进行一次性全表扫描,一方面数据量很大,另一方面请求只会执行一次。这种场景下如果使用scan默认设置,就会将数据从HDFS加载出来之后放到缓存。可想而知,大量数据进入缓存必将其他实时业务热点数据挤出,其他业务不得不从HDFS加载,进而会造成明显的读延迟毛刺

优化建议:离线批量读取请求设置禁用缓存,scan.setBlockCache(false)

剩下的优化将放到下一篇做一个全总结,今天有点晚了,先休息了~~~

❤️❤️❤️❤️

非常感谢人才们能看到这里,如果这个文章写得还不错,觉得有点东西的话 求点赞👍 求关注❤️ 求分享👥 对帅气欧巴的我来说真的 非常有用!!!

如果本篇博客有任何错误,请批评指教,不胜感激 !

文末福利,最近整理一份面试资料《Java面试通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。获取方式:GitHub github.com/Tingyu-Note…,更多内容关注公号:汀雨笔记,陆续奉上。