V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Recommended Services
Amazon Web Services
LeanCloud
New Relic
ClearDB
shipinyun2016
V2EX  ›  云计算

网易视频云: HBase BlockCache 系列 – 走进 BlockCache

  •  
  •   shipinyun2016 · 2016-07-01 10:21:53 +08:00 · 1967 次点击
    这是一个创建于 3074 天前的主题,其中的信息可能已经有所发展或是发生改变。

    网易视频云是网易倾力打造的一款基于云计算的分布式多媒体处理集群和专业音视频技术,为客户提供稳定流畅、低时延、高并发的视频直播、录制、存储、转码及点播等音视频的 PaaS 服务。在线教育、远程医疗、娱乐秀场、在线金融等各行业及企业用户只需经过简单的开发即可打造在线音视频平台。现在,网易视频云与大家分享一下 HBase BlockCache 系列 – 走进 BlockCache.

    和其他数据库一样,优化 IO 也是 HBase 提升性能的不二法宝,而提供缓存更是优化的重中之重。最理想的情况是,所有数据都能够缓存到内存,这样就不会有任何文件 IO 请求,读写性能必然会提升到极致。然而现实是残酷的,随着请求数据的不断增多,将数据全部缓存到内存显得不合实际。幸运的是,我们并不需要将所有数据都缓存起来,根据二八法则, 80%的业务请求都集中在 20%的热点数据上,因此将这部分数据缓存起就可以极大地提升系统性能。

    HBase 在实现中提供了两种缓存结构: MemStore 和 BlockCache 。其中 MemStore 称为写缓存, HBase 执行写操作首先会将数据写入 MemStore ,并顺序写入 HLog ,等满足一定条件后统一将 MemStore 中数据刷新到磁盘,这种设计可以极大地提升 HBase 的写性能。不仅如此, MemStore 对于读性能也至关重要,假如没有 MemStore ,读取刚写入的数据就需要从文件中通过 IO 查找,这种代价显然是昂贵的! BlockCache 称为读缓存, HBase 会将一次文件查找的 Block 块缓存到 Cache 中,以便后续同一请求或者邻近数据查找请求,可以直接从内存中获取,避免昂贵的 IO 操作。 MemStore 相关知识可以戳这里,本文将重点分析 BlockCache 。

    在介绍 BlockCache 之前,简单地回顾一下 HBase 中 Block 的概念,详细介绍戳这里。 Block 是 HBase 中最小的数据存储单元,默认为 64K ,在建表语句中可以通过参数 BlockSize 指定。 HBase 中 Block 分为四种类型: Data Block , Index Block , Bloom Block 和 Meta Block 。其中 Data Block 用于存储实际数据,通常情况下每个 Data Block 可以存放多条 KeyValue 数据对; Index Block 和 Bloom Block 都用于优化随机读的查找路径,其中 Index Block 通过存储索引数据加快数据查找,而 Bloom Block 通过一定算法可以过滤掉部分一定不存在待查 KeyValue 的数据文件,减少不必要的 IO 操作; Meta Block 主要存储整个 HFile 的元数据。

    BlockCache 是 Region Server 级别的,一个 Region Server 只有一个 Block Cache ,在 Region Server 启动的时候完成 Block Cache 的初始化工作。到目前为止, HBase 先后实现了 3 种 Block Cache 方案, LRUBlockCache 是最初的实现方案,也是默认的实现方案; HBase 0.92 版本实现了第二种方案 SlabCache ,见 HBASE-4027 ; HBase 0.96 之后官方提供了另一种可选方案 BucketCache ,见 HBASE-7404 。

    这三种方案的不同之处在于对内存的管理模式,其中 LRUBlockCache 是将所有数据都放入 JVM Heap 中,交给 JVM 进行管理。而后两者采用了不同机制将部分数据存储在堆外,交给 HBase 自己管理。这种演变过程是因为 LRUBlockCache 方案中 JVM 垃圾回收机制经常会导致程序长时间暂停,而采用堆外内存对数据进行管理可以有效避免这种情况发生。

    LRUBlockCache

    HBase 默认的 BlockCache 实现方案。 Block 数据块都存储在 JVM heap 内,由 JVM 进行垃圾回收管理。它将内存从逻辑上分为了三块: single-access 区、 mutil-access 区、 in-memory 区,分别占到整个 BlockCache 大小的 25%、 50%、 25%。一次随机读中,一个 Block 块从 HDFS 中加载出来之后首先放入 signle 区,后续如果有多次请求访问到这块数据的话,就会将这块数据移到 mutil-access 区。而 in-memory 区表示数据可以常驻内存,一般用来存放访问频繁、数据量小的数据,比如元数据,用户也可以在建表的时候通过设置列族属性 IN-MEMORY= true 将此列族放入 in-memory 区。很显然,这种设计策略类似于 JVM 中 young 区、 old 区以及 perm 区。无论哪个区,系统都会采用严格的 Least-Recently-Used 算法,当 BlockCache 总量达到一定阈值之后就会启动淘汰机制,最少使用的 Block 会被置换出来,为新加载的 Block 预留空间。

    SlabCache

    为了解决 LRUBlockCache 方案中因为 JVM 垃圾回收导致的服务中断, SlabCache 方案使用 Java NIO DirectByteBuffer 技术实现了堆外内存存储,不再由 JVM 管理数据内存。默认情况下,系统在初始化的时候会分配两个缓存区,分别占整个 BlockCache 大小的 80%和 20%,每个缓存区分别存储固定大小的 Block 块,其中前者主要存储小于等于 64K 大小的 Block ,后者存储小于等于 128K Block ,如果一个 Block 太大就会导致两个区都无法缓存。和 LRUBlockCache 相同, SlabCache 也使用 Least-Recently-Used 算法对过期 Block 进行淘汰。和 LRUBlockCache 不同的是, SlabCache 淘汰 Block 的时候只需要将对应的 bufferbyte 标记为空闲,后续 cache 对其上的内存直接进行覆盖即可。

    线上集群环境中,不同表不同列族设置的 BlockSize 都可能不同,很显然,默认只能存储两种固定大小 Block 的 SlabCache 方案不能满足部分用户场景,比如用户设置 BlockSize = 256K ,简单使用 SlabCache 方案就不能达到这部分 Block 缓存的目的。因此 HBase 实际实现中将 SlabCache 和 LRUBlockCache 搭配使用,称为 DoubleBlockCache 。一次随机读中,一个 Block 块从 HDFS 中加载出来之后会在两个 Cache 中分别存储一份;缓存读时首先在 LRUBlockCache 中查找,如果 Cache Miss 再在 SlabCache 中查找,此时如果命中再将该 Block 放入 LRUBlockCache 中。

    经过实际测试, DoubleBlockCache 方案有很多弊端。比如 SlabCache 设计中固定大小内存设置会导致实际内存使用率比较低,而且使用 LRUBlockCache 缓存 Block 依然会因为 JVM GC 产生大量内存碎片。因此在 HBase 0.98 版本之后,该方案已经被不建议使用。

    BucketCache

    SlabCache 方案在实际应用中并没有很大程度改善原有 LRUBlockCache 方案的 GC 弊端,还额外引入了诸如堆外内存使用率低的缺陷。然而它的设计并不是一无是处,至少在使用堆外内存这个方面给予了阿里大牛们很多启发。站在 SlabCache 的肩膀上,他们开发了 BucketCache 缓存方案并贡献给了社区。

    BucketCache 通过配置可以工作在三种模式下: heap , offheap 和 file 。无论工作在那种模式下, BucketCache 都会申请许多带有固定大小标签的 Bucket ,和 SlabCache 一样,一种 Bucket 存储一种指定 BlockSize 的数据块,但和 SlabCache 不同的是, BucketCache 会在初始化的时候申请 14 个不同大小的 Bucket ,而且即使在某一种 Bucket 空间不足的情况下,系统也会从其他 Bucket 空间借用内存使用,不会出现内存使用率低的情况。接下来再来看看不同工作模式, heap 模式表示这些 Bucket 是从 JVM Heap 中申请, offheap 模式使用 DirectByteBuffer 技术实现堆外内存存储管理,而 file 模式使用类似 SSD 的高速缓存文件存储数据块。

    实际实现中, HBase 将 BucketCache 和 LRUBlockCache 搭配使用,称为 CombinedBlockCache 。和 DoubleBlockCache 不同,系统在 LRUBlockCache 中主要存储 Index Block 和 Bloom Block ,而将 Data Block 存储在 BucketCache 中。因此一次随机读需要首先在 LRUBlockCache 中查到对应的 Index Block ,然后再到 BucketCache 查找对应数据块。 BucketCache 通过更加合理的设计修正了 SlabCache 的弊端,极大降低了 JVM GC 对业务请求的实际影响,但也存在一些问题,比如使用堆外内存会存在拷贝内存的问题,一定程度上会影响读写性能。当然,在后来的版本中这个问题也得到了解决,见 HBASE-11425 。

    本文是 HBase BlockCache 系列文章的第一篇,主要概述了 HBase 中 MemStore 和 BlockCache ,再分别对三种 BlockCache 方案进行了基本介绍。接下来第二篇文章会主要对 LRUBlockCache 和 BucketCache 两种方案进行详细的介绍,敬请期待!

    bzzhou
        1
    bzzhou  
       2016-07-01 12:59:42 +08:00
    说实话,写的逻辑有点混乱。。。
    Xrong
        2
    Xrong  
       2016-07-01 13:42:29 +08:00
    太长,看不下去
    klmun
        3
    klmun  
       2016-07-01 23:08:43 +08:00
    这个文章写的不错。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1814 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 16:25 · PVG 00:25 · LAX 08:25 · JFK 11:25
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.