HBase缓存漫谈

背景

用户/内容画像的对存储的要求其实是比较高的:

  1. 能批量更新(比如更新所有用户某个属性)
  2. 大量随机读取(甚至可能没有热点数据)
  3. 随机属性更新/添加
  4. 可持久化
  5. 易于横向扩展解决性能问题

上一次重度使用HBase已经是两年前了。HBase能够满足上面五个要求,所以用HBase作为画像体系的主要存储引擎便水到渠成。

问题

因为有批量更新,随机属性的更新/添加,那么必然会缓存失效,从而触发磁盘IO导致读取响应时间受到影响。在画像体系里,随机读取量大,比如召回了1000个id,然后你需要取这1000个id的属性集合,并且要求响应时间能够控制在100ms。基本只要碰到磁盘IO就歇菜了。所以现在我们希望HBase能够把所有数据都缓存住。

HBase读缓存特色

HBase的缓存目前我所了解的是Block Cache. Block Cache是什么概念的呢,我们知道HBase的最小文件单元是HFile, HFile是有结构的,主要包含:

  1. 索引,可以是多层
  2. 数据
  3. 元数据

当然还有一个布隆过滤器,方便确定一个元素是不是在HFile里。

并且只会有三个动作:

  1. 新增HFile
  2. HFile 合并
  3. HFile的分裂

这里需要注意HFile一旦生成里面的元素就不会被改变。

一个HFile的数据会被切分成多个Block,每个Block一般而言都会有一些元信息。当然这些切分其实是逻辑上的。Block Cache就是前面三部分的Cache. 在HBase里,当打开一个HFile时,默认会cache住一些索引信息,文件信息,读取时则会连数据都会Cache起来。当然你也可以通过参数让HBase在打开时就把数据Cache住。

如果是传统意义上的缓存,如果有更新,那么必然会导致一个问题:缓存失效。但是HBase其实并没有这个问题。一个简单的Get请求,HBase的读取方式是读MemStore 和HFile,读HFile的时候会看数据是不是已经在BlockCache里。

  1. MemStore,这个是写缓存,但是是有结构的,可以直接查。
  2. BlockCache, 这个是读缓存,也是有结构的,可以直接查。
  3. HFile,这个就是HDFS文件,会touch到IO。

假设我在读取的时候,MemStore没有进行flush,那么可能不会触碰到磁盘,虽然BlockCache的数据已经是老版本的了,MemStore里却有最新版本的数据,所以HBase简单的到MemStore/BlockCache都拿一遍。第二次再来拿,假设正好碰到MemStore flush生成新的HFile,这个时候就触发磁盘IO了。当然,其他如Compaction也会导致触发磁盘。

解决方案

下面解决方案的前提是,可用于BlockCache的内存大于你的数据。在画像体系里,这点可以做到的,因为内容和用户都是有限的。

根据前面的描述,为了能够保证随机读不触发磁盘IO操作,那么我们在生成新HFile的同时,也需要让它写进BlockCache,HBase也提供了相关参数让你完成这个功能:

hfile.block.index.cacheonwrite  写HFile时把索引加入到Cache
hfile.block.bloom.cacheonwrite 写HFile时把布隆过滤器加入到Cache
hbase.rs.cacheblocksonwrite 写HFile时把数据也写入到Cache里

这里我们基本知道写Cache的几个时机点:

  1. 打开HFile
  2. 读HFile
  3. 写HFile
我来评几句
登录后评论

已发表评论数()