解密 云HBase 冷热分离技术原理

前言

HBase是当下流行的一款海量数据存储的分布式数据库。每每海量数据存储会涉及到一个成本问题,如何下降成本。常见的方案就是经过冷热分离来治理数据。冷数据能够用更高的压缩比算法(ZSTD),更低副本数算法(Erasure Coding),更便宜存储设备(HDD,高密集型存储机型)。算法

HBase冷热分离常看法决方案

1.主备集群

备(冷)集群用更廉价的硬件,主集群设置TTL,这样当数据热度退去,冷数据天然只在冷集群有。数据库

优势:方案简单,现成内核版本都能搞
缺点:维护开销大,冷集群CPU存在浪费数据结构

1.x版本的HBase在不改内核状况下,基本只能有这种方案。架构

2.HDFS Archival Storage + HBase CF-level Storage Policy

须要在2.x以后的版本才能使用。结合HDFS分层存储能力 + 在Table层面指定数据存储策略,实现同集群下,不一样表数据的冷热分离。分布式

优势:同一集群冷热分离,维护开销少,更灵活的配置不一样业务表的策略
缺点:磁盘配比是个很大的问题,不一样业务冷热配比是不同的,比较难整合在一块儿,一旦业务变更,集群硬件配置是无法跟着变的。oop

云HBase冷热分离解决方案

上述2套方案都不是最好的方案,对于云上来讲。第一套方案就不说了,客户搞2个集群,对于数据量不大的客户其实根本降不了成本。第二套方案,云上客户千千万,业务各有各样,磁盘配置是很难定制到合适的状态。性能

云上要作 cloud native 的方案,必须知足同集群下,极致的弹性伸缩,才能真正意义上作到产品化。云上低成本,弹性存储,只有OSS了。因此很天然的想到以下架构:测试

实现这样的架构,最直接的想法是直接改HBase内核:1)增长冷表数据标记 2)根据标记增长写OSS的IO路径。优化

这样作的缺陷很是明显,你的外部系统(如:备份恢复,数据导入导出)很难兼容这些改动,他们须要感知哪些是冷文件得去OSS哪一个位置读,哪些是热文件得去部署在云盘上的HDFS上读。这些本质上都是一些重复的工做,因此从架构设计角度来看必须抽象出一层。这一层能读写HDFS文件,读写OSS文件,感知冷热文件。这一层也就是我最后设计出的ApsaraDB FileSystem,实现了Hadoop FileSystem API。对于HBase,备份恢复,数据导入导出等系统只要替换原先FileSystem的实现便可得到冷热分离的功能。spa

下面将详细阐述,这套FileSystem设计的细节与难点。

ApsaraDB FileSystem 设计

核心难点A

1.OSS并不是文件系统

OSS并非一个真正意义上的文件系统,它仅仅是两级映射 bucket/object,因此它是对象存储。你在OSS上看到相似这样一个文件:
/root/user/gzh/file。你会觉得有3层目录+1个文件。实际上只有一个对象,这个对象的key包含了/字符罢了。

这么带来的一个问题是,你要想在其上模拟出文件系统,你必须先能建立目录。很天然想到的是用/结尾的特殊对象表明目录对象。Hadoop社区开源的OssFileSystem就是这么搞的。有了这个方法,就能判断到底存不存在某个目录,能不能建立文件,否则会凭空建立出一个文件,而这个文件没有父目录。

固然你这么作依然会有问题。除了开销比较大(建立深层目录屡次HTTP请求OSS),最严重的是正确性的问题。试想一下下面这个场景:
把目录/root/user/source rename 成 /root/user/target。这个过程除了该目录,它底下的子目录,子目录里的子文件都会跟着变。相似这样:/root/user/source/file => /root/user/target/file。这很好理解,文件系统就是一颗树,你rename目录,实际是把某颗子树移动到另外一个节点下。这个在NameNode里的实现也很简单,改变下树结构便可。

可是若是是在OSS上,你要作rename,那你不得不递归遍历/root/user/source把其下全部目录对象,文件对象都rename。由于你无法经过移动子树这样一个简单操做一步到位。这里带来的问题就是,假设你递归遍历到一半,挂了。那么就可能会出现一半目录或文件到了目标位置,一半没过去。这样rename这个操做就不是原子的了,原本你要么rename成功,整个目录下的内容到新的地方,要么没成功就在原地。因此正确性会存在问题,像HBase这样依赖rename操做将临时数据目录移动到正式目录来作数据commit,就会面临风险。

2.OSS rename实则是数据拷贝

前面咱们提到了rename,在正常文件系统中应该是一个轻量级的,数据结构修改操做。可是OSS并无rename这个操做实际上,rename得经过 CopyObject + DeleteObject 两个操做完成。首先是copy成目标名字,而后delete掉原先的Object。这里有2个明显的问题,一个是copy是深度拷贝开销很大,直接会影响HBase的性能。另外一个是rename拆分红2个操做,这2个操做是无法在一个事物里的,也就是说:可能存在copy成功,没delete掉的状况,此时你须要回滚,你须要delete掉copy出来的对象,可是delete依然可能不成功。因此rename操做自己实现上,正确性就难以保证了。

解决核心难点A

解决上面2个问题,须要本身作元数据管理,即至关于本身维护一个文件系统树,OSS上只放数据文件。而由于咱们环境中仍然有HDFS存储在(为了放热数据),因此直接复用NameNode代码,让NodeNode帮助管理元数据。因此总体架构就出来了:

ApsaraDB FileSystem(如下简称ADB FS)将云端存储分为:主存(PrimaryStorageFileSystem)和 冷存(ColdStorageFileSystem)。由ApsaraDistributedFileSystem类(如下简称ADFS)负责管理这两类存储文件系统,而且由ADFS负责感知冷热文件。

ApsaraDistributedFileSystem: 总入口,负责管理冷存和主存,数据该从哪里读,该写入哪里(ADFS)。
主存:PrimaryStorageFileSystem 默认实现是 DistributedFileSystem(HDFS)
冷存:ColdStorageFileSystem 默认实现是 HBaseOssFileSystem(HOFS) ,基于OSS实现的Hadoop API文件系统,能够模拟目录对象单独使用,也能够只做为冷存读写数据,相比社区版本有针对性优化,后面会讲。

具体,NameNode如何帮助管理冷存上的元数据,很简单。ADFS在主存上建立同名索引文件,文件内容是索引指向冷存中对应的文件。实际数据在冷存中,因此冷存中的文件有没有目录结构无所谓,只有一级文件就行。咱们再看下一rename操做过程,就明白了:

rename目录的场景也同理,直接在NameNode中rename就行。对于热文件,至关于所有代理HDFS操做便可,冷文件要在HDFS上建立索引文件,而后写数据文件到OSS,而后关联起来。

核心难点B

引入元数据管理解决方案,又会遇到新的问题:是索引文件和冷存中数据文件一致性问题。
咱们可能会遇到以下场景:

  • 主存索引文件存在,冷存数据文件不存在
  • 冷存数据文件存在,主存索引文件不存住
  • 主存索引文件信息不完整,没法定位冷存数据文件

先排除BUG或者人为删除数据文件因素,上诉3种状况都会因为程序crash产生。也就是说咱们要想把法,把生成索引文件,写入并生成冷数据文件,关联,这3个操做放在一个事物里。这样才能具有原子性,才能保证要么建立冷文件成功,那么索引信息是完整的,也指向一个存在的数据文件。要么建立冷文件失败(包括中途程序crash),永远也见不到这个冷文件。

解决核心难点B

核心思想是利用主存的rename操做,由于主存的rename是具有原子性的。咱们先在主存的临时目录中生产索引文件,此时索引文件内容已经指向冷存中的一个路径(可是实际上这个路径的数据文件还没开始写入)。在冷存完成写入,正确close后,那么此时咱们已经有完整且正确的索引文件&数据文件。而后经过rename一把将索引文件改到用户实际须要写入到目标路径,便可。

若是中途进程crash,索引文件要么已经rename成功,要么索引文件还在临时目录。在临时目录咱们认为写入没有完成,是失败的。而后咱们经过清理线程,按期清理掉N天之前临时目录的文件便可。因此一旦rename成功,那目标路径上的索引文件必定是完整的,必定会指向一个写好的数据文件。

为何咱们须要先写好路径信息在索引文件里?由于若是先写数据文件,在这个过程当中crash了,那咱们是没有索引信息指向这个数据文件的,从而形成相似“内存泄漏”的问题。

冷热文件标记

对于主存,须要实现给文件冷热标记的功能,经过标记判断要打开怎样的数据读写流。这点NameNode能够经过给文件设置StoragePolicy实现。这个过程就很简单了,不详细赘述,下面说HBaseOssFileSystem写入优化设计。

HBaseOssFileSystem 写入优化

在说HOFS写设计以前,咱们先要理解Hadoop社区版本的OssFileSystem设计(这也是社区用户能直接使用的版本)。

社区版本写入设计

Write -> OutputStream -> disk buffer(128M) -> FileInputStream -> OSS

这个过程就是先写入磁盘,磁盘满128M后,将这128M的block包装成FileInputStream再提交给OSS。这个设计主要是考虑了OSS请求成本,OSS每次请求都是要收费的,可是内网流量不计费。若是你1KB写一次,费用就很高了,因此必须大块写。并且OSS大文件写入,设计最多让你提交10000个block(OSS中叫MultipartUpload),若是block过小,那么你能支持的最大文件大小也是受限

因此要攒大buffer,另一个因素是Hadoop FS API提供的是OutputStream让你不断write。OSS提供的是InputStream,让你提供你要写入内容,它本身不断读取。这样你必然要经过一个buffer去转换。

这里会有比较大的一个问题,就是性能慢。写入磁盘,再读取磁盘,多了这么两轮会比较慢,虽然有PageCache存在,读取过程不必定有IO。那你确定想,用内存当buffer不就行了。内存当buffer的问题就是前面说的,有费用,因此buffer不能过小。因此你每一个文件要开128M内存,是不可能的。更况且当你提交给OSS的时候,你要保证能继续写入新数据,你得有2块128M内存滚动,这个开销几乎不能接受。

HBaseOssFileSystem 写入设计

咱们既要解决费用问题,也要解决性能问题,同时要保证开销很低,看似不可能,那么怎么作呢?

这里要利用的就是这个InputStream,OSS让你提供InputStream,并从中读取你要写入的内容。那么咱们能够设计一个流式写入,当我传入这个InputStream给OSS的时候,流中并不必定得有数据。此时OSS调read读取数据会block在read调用上。等用户真的写入数据,InputStream中才会有数据,这时候OSS就能顺利读到数据。当OSS读了超过128M数据时候,InputStream会自动截断,返回EOF,这样OSS会觉得流已经结束了,那么这块数据就算提交完成。

因此咱们本质只要开发这么一个特殊的InputStream便可。用户向Hadoop API提供的OutputStream中写入数据,数据每填满一个page(2M)就发给InputStream让其可读取。OuputStream至关于生产者,InputStream至关于消费者。这里的内存开销会很是低,由于生产者速度和消费者速度相近时,也就2个page的开销。最后将这整套实现封装成OSSOutputStream类,当用户要写入冷文件时,实际提供的是OSSOutputStream,这里面就包含了这个特殊InputStream的控制过程。

固然实际生产中,咱们会对page进行控制,每一个文件设置最多4个page。而且这4个page循环利用,减小对GC对影响。因此最后咱们获得下面一个环形缓冲的写入模式:

性能对比1:社区版本 vs 云HBase版

由于不用写磁盘,因此写入吞吐能够比社区的高不少,下图为HBase1.0上测试结果。在一些大KV,写入压力更大的场景,实测能够接近1倍。这个比较是经过替换ADFS冷存的实现(用社区版本和云HBase版本),都避免了rename深拷贝问题。若是直接裸用社区版本而不用ADFS那性能会差数倍。

性能对比2:热表 vs 冷表

热表数据在云盘,冷表数据在OSS。

得益于上述优化,加上冷表WAL也是放HDFS的,而且OSS相对HBase来讲是大集群(吞吐上限高),冷表的HDFS只用抗WAL写入压力。因此冷表吞吐反而会比热表略高一点点。

无论怎么说,冷表的写入性能和热表至关了,这样的表现已经至关不错了。基本是不会影响用户灌数据,不然使用冷存后,吞吐掉不少,那意味着要更多机器,那这功能就没什么意义了。

总结

这大约是1年多前落地的项目,已经稳定运行好久。以前也有出去分享过,可是没有今天这么细致。如今写出来主要是自我总结下。当设计一个服务去解决某个问题的时候,上下游的关系,可能存在的问题得考虑清楚。在作这个项目最初的时候,想把HBase直接架设在社区版本OssFileSytem上,发现性能不行外,正确性也存在很大风险。不断思考,考虑各类状况后才有了今天这套方案。



本文做者:郭泽晖

阅读原文

本文为云栖社区原创内容,未经容许不得转载。

相关文章
相关标签/搜索