存储选型

Blob(binary large object)存储系统主要用来存储二进制的大对象,比如图片,视频。这样的数据不太适合存储在类LSM系统例如HBase中,原因在于这种数据尺寸相对较大,写入RegionServer的memstore后很快会触发region的flush,在磁盘上形成大量的数据文件,而读操作需要对这些磁盘文件以及memstore进行多路归并,可见读性能较差。当然,LSM系统通常使用Compaction机制来定期合并这些数据文件以保证读性能,但是,compaction也会占用大量IO,这就是这类系统的一个很大的痛点。并且,LSM每次写WAL时都会将这块数据写入,写放大过于严重,写性能低下。另外,图片这样的数据又不太适合直接存储在HDFS中,每个图片一个HDFS文件对namenode内存又是一个挑战。每个文件的元数据大概占用150字节,64GB内存大概存储4亿图片。解决小文件存储最普遍的一个思路就是将小文件合并成一个大文件存储,将每个小文件的位置记在另外一个地方,每次读先从这个地方读出小文件的位置,然后去大文件中相对应的位置取即可。以Facebook的Haystack为代表。

核心设计

从 Hadoop 0.20开始,hdfs支持append特性,看.这个特性刚好可以实现将多个小文件合并成一个大文件这个功能。位置信息可以用MySQL,HBase等结构化存储系统。所以,一个最简单的设计就是应用写一个图片时,首先向HDFS申请一个文件进行append,写完数据后,将HDFS文件名,偏移和大小记录在HBase中,最后关闭这个HDFS文件即可。这里需要一个中间的代理进程,这个进程每次分配给客户端一个HDFS文件,保证同一个HDFS文件同一时刻只能分配给一个客户端,保证数据不给写乱,客户端写完成后告诉代理进程写完了,这个HDFS文件就可以再次分配给其他客户端。另外代理进程还需要和客户端之间维护一个lease,以防止客户端拿了HDFS文件后挂掉导致这个文件永远不能再分配给其他的客户端。同时代理进程也需要写WAL持久化目前已经分配了哪些文件,以便宕机恢复。实测发现,每次写一个小文件时都需申请一个HDFS文件打开后再进行写这个过程overhead太大,原因主要在于建立写Pipeline时多次和namenode的交互,在QJM HA的部署下,namenode写edit log开销比较大,具体看。优化的方法是客户端申请HDFS文件后,一直拿住,写完数据后不还给代理进程,而是调用hflush保证写入的数据可以被后续的reader读到,看,这样优化的原因是HDFS写数据时,pipeline是block级别建立的。这种优化下,客户端需要和代理进程定期renew lease。

小文件删除

在HBase中标记删除即可。写操作时,除了写一份位置信息,还要写一份索引信息,记录一个HDFS文件中哪段数据是哪个小文件。这样回收空间时,以HDFS文件为单位,读这个HDFS相对应的索引表就可以进行compaction回收空间了。compaction期间文件内的数据仍然可以被读,只是这个HDFS文件不能写,但是这不会影响客户端的写,因为客户端可以写别的文件。

高可用

HBase和Hadoop本身高可用。代理进程的HA通过ZooKeeper来选主,WAL状态信息直接写入HBase,HBase作为共享存储,方便代理进程切换后恢复状态。

可扩展性

小文件/object可以通过Bucket进行隔离,不同的业务使用不同的Bucket,每个Bucket被一个代理进程服务,代理进程通过监听ZooKeeper,可以实现Bucket的动态加载/卸载/迁移.