HBase 是一个基于 Google BigTable 论文设计的高可靠性、高性能、可伸缩的分布式存储系统。 网上关于 HBase 的文章很多,官方文档介绍的也比较详细,本篇文章不介绍HBase基本的细节。
本文从 HBase 写链路开始分析,然后针对少量随机读和海量随机写入场景入手,全方面量化分析各种资源的开销, 从而做到以下两点:
HBase 的写入链路基于 LSM(Log-Structured Merge-Tree), 基本思想是把用户的随机写入转化为两部分写入:
Memstore 内存中的 Map, 保存随机的随机写入,待 memstore 达到一定量的时候会异步执行 flush 操作,在 HDFS 中生成 HFile 中。 同时会按照写入顺序,把数据写入一份到 HDFS 的 WAL(Write Ahead Log)中,用来保证数据的可靠性,即在异常(宕机,进程异常退出)的场景下,能够恢复 Memstore 中还没来得及持久化成 HFile 的数据.
上一节中,介绍了 HBase 的写路径,其中 HFile 是 HBase 数据持久化的最终形态, 本节将介绍 HBase 如何生成 HFile 和管理 HFile。关于 HFile, 主要涉及到两个核心操作:
上一节中提到,HBase 的写入最先会放入内存中,提供实时的查询,当 Memstore 中数据达到一定量的阈值(128MB),会通过 Flush 操作生成 HFile 持久化到 HDFS 中,随着用户的写入,生成的 HFile 数目会逐步增多,这会影响用户的读操作,同时也会系统占用(HDFS 层 block 的数目, regionserver 服务器的文件描述符占用), region split 操作,region reopen 操作也会受到不同程度影响。
HBase 通过 Compaction 机制将多个 HFile 合并成一个 HFile 以控制每个 Region 内的 HFile 的数目在一定范围内, 当然 Compaction 还有其他的作用,比如数据本地化率,多版本数据的合并,数据删除标记的清理等等,本文不做展开。
另外还有一点需要知道的是,HBase 中 Flush 操作和 Compaction 操作和读写链路是由独立线程完成的,互不干扰。
为了简化计算,本节针对事件类数据写吞吐型场景,对 HBase 系统中的开销做定量的分析,做以下假设:
以上 11 个参数,是本次量化分析中需要使用到的变量,系统资源方面主要量化以下两个指标:
这里只考虑磁盘空间方面的占用,相关的变量有:
HFile的磁盘容量量化公式
V = TTL 86400 T s C * R1
假设 s = 1000, TTL = 365, T = 200000, C = 0.2 , R1 = 3 的情况下,HFile 磁盘空间需求是:
V = 30 * 86400 * 200000 * 1000 * 0.2 * 3
= 311040000000000.0 bytes
= 282T
在这里我们忽略了其他占用比较小的磁盘开销,比如:
HBase中会造成巨大网络开销的主要由一下三部分组成,他们是相互独立,异步进行的,这里做个比方,HBase 这三个操作和人吃饭很像,这里做个类比
回归正题,下面按照发生顺序,从三个角度分别分析:
写路径的网络开销,主要是写 WAL 日志方面, 相关的变量有:
写路径中,产生的网络流量分为两部分,一部分是写 WAL 产生的流量,一部分是外部用户 RPC 写入的流量, In 流量和 Out 流量计算公式为:
NInWrite = T s Cwal (R2 - 1) + (T s )NOutWrite = T s Cwal * (R2 - 1)
假设 T = 20W,s = 1000, Cwal = 1.0, R2 = 3
NInwrite = 200000 * 1000 * 1 * (3-1) + 200000 * 1000
= 600000000 bytes/s
= 572MB/s
NOutwrite = 200000 * 1000* 1 * (3-1)
= 400000000 bytes/s
= 381MB/s
Flush 的网络开销,主要是生成 HFile 后,将 HFile 写入到 HDFS 的过程,相关的变量有:
Flush 产生的 In 流量和 Out 流量计算公式为:
NInWrite = s T (R1 - 1) * CNOutWrite = s T (R1 - 1) * C
假设 T = 20W, S = 1000, R1 = 3, C = 0.2
NInwrite = 200000 * 1000 * (3 - 1) * 0.2
= 80000000.0 bytes/s
=76.3MB/s
NOutwrite = 200000 * 1000 * (3 - 1) * 0.2
= 120000000.0 bytes/s
=76.3MB/s
Compaction 比较复杂,在有预分区不考虑 Split 的情况下分为两类:
两者是独立的,下面将分别针对两种 Compaction 做分析,最后取和:
Major Compaction 的定义是由全部 HFile 参与的 Compaction, 一般在发生在 Split 后发生,或者到达系统的 MajorCompaction 周期, 默认的 MajorCompaction 周期为 20 天,这里我们暂时忽略 Split 造成的 MajorCompaction 流量. 最终 Major Compaction 开销相关的变量是:
这里假设数据是有本地化的,所以 MajorCompaction 的读过程,走 ShortCircuit,不计算网络开销,并且写 HFile 的第一副本是本地流量,也不做流量计算,所以 MajorCompaction 的网络流量计算公式是:
NInMajor = D * (R1 - 1) / MNOutMajor = D * (R1 - 1) / M
假设 D = 10T, R1 = 3, M = 20
NInMajor = 10 * 1024 * 1024 * 1024 * 1024 * (3 - 1) / (20 * 86400)
= 12725829bytes/s
= 12MB/s
NOutMajor = 10 * 1024 * 1024 * 1024 * 1024 * (3 - 1) / (20 * 86400)
= 12725829bytes /s
= 12MB/s
量化之前,先问一个问题,每条数据在第一次 flush 成为 HFile 之后,会经过多少次 Minor Compaction?
要回答这个问题之前,要先了解现在 HBase 默认的 compaction 的文件选取策略,这里不展开,只做简单分析,MinorCompaction 选择的文件对象数目,一般处于 hbase.hstore.compaction.min(默认 3)和 hbase.hstore.compaction.max(默认 10)之间, 总文件大小小于 hbase.hstore.compaction.max.size(默认 Max), 如果文件的 Size 小于 hbase.hstore.compaction.min.size(默认是 flushsize), 则一定会被选中; 并且被选中的文件size的差距不会过大, 这个由参数 hbase.hstore.compaction.ratio 和 hbase.hstore.compaction.ratio.offpeak 控制,这里不做展开.
所以,在 Compaction 没有积压的情况下,每次 compaction 选中的文件数目会等于 hbase.hstore.compaction.min 并且文件 size 应该相同量级, 对稳定的表,对每条数据来说,经过的 compaction 次数越多,其文件会越大. 其中每条数据参与 Minor Compaction 的最大次数可以用公式 math.log( 32000 / 25.6, 3) = 6 得到
这里用到的两个变量是:
所以刚刚 Flush 生成的 HFile 的大小在 25.6MB 左右,当集齐三个 25.6MB 的 HFile 后,会触发第一次 Minor Compaction, 生成一个 76.8MB 左右的 HFile
对于一般情况,单个 Region 的文件 Size 我们会根据容量预分区好,并且控制单个 Region 的 HFile 的总大小 在 32G 以内,对于一个 Memstore
128MB, HFile 压缩比 0.2, 单个 Region 32G 的表,上表中各个 Size 的 HFile 数目不会超过 2 个(否则就满足了触发 Minor Compaction 的条件)
32G = 18.6G + 6.2G + 6.2G + 690MB + 230MB + 76.8MB + 76.8MB
到这里,我们知道每条写入的数据,从写入到 TTL 过期,经过 Minor Compaction 的次数是可以计算出来的。 所以只要计算出每次 Compaction 的网络开销,就可以计算出,HBase 通过 Minor Compaction 消化每条数据,所占用的总的开销是多少,这里用到的变量有:
计算公式如下:
NInMinor = S T (R1-1) C 总次数NOutMinor = S T (R1-1) C 总次数
假设 S = 1000, T = 20W, R1 = 3, C = 0.2, 总次数 = 6
NInminor = 1000 * 200000 * (3 - 1) * 0.2 * 6
= 480000000.0bytes/s
= 457.8MB/s
NOutminor = 1000 * 200000 * (3 - 1) * 0.2 * 6
= 480000000.0bytes/s
= 457.8MB/s
在用户写入 TPS 20W, 单条数据大小 1000 bytes的场景下,整体网络吞吐为:
NIntotal = NInwrite + NInflush + NInmajor + NInminor
= 572MB/s + 76.3MB/s + 12MB/s + 457.8MB/s
= 1118.1MB/s
NOuttotal = NOutwrite + NOutflush + NOutmajor + NOutminor
= 381MB/s + 76.3MB/s + 12MB/s + 457.8MB/s
= 927.1MB
当然这是理想情况下的最小开销,有很多种情况,可以导致实际网络开销超过这个理论值, 以下情况都会导致实际流量的升高:
有了这个量化分析后,我们能做什么优化呢? 这里不深入展开,简单说几点已经在有赞生产环境得到验证具有实效的优化点:
到这里,HBase 的写吞吐场景的资源定量分析和优化的介绍就算结束了,本文基于 HBase1.2.6 版本。 对很多 HBase 的细节没有做展开说明,有些地方因为作者认知有限,难免纰漏,欢迎各位同行指出。
最后打个小广告,有赞大数据团队基础设施团队,主要负责有赞的数据平台(DP), 实时计算(Storm, Spark Streaming, Flink),离线计算(HDFS,YARN,HIVE, SPARK SQL),在线存储(HBase),实时 OLAP(Druid) 等数个技术产品,欢迎感兴趣的小伙伴联系 [email protected]
参考文献