1. 背景

HDFS存储的数据,一般情况下,创建时间越新的数据,访问次数越频繁;创建时间越久远的数据,访问频次越低。在HDFS集群中,默认情况下,所有数据都存放在同一类型介质中,大量访问频次低的数据没有被访问,浪费磁盘的性能。

为了合理的降低成本,可以将访问次数频繁的数据存放在高速存储介质中,这样用户访问这部分数据很快;将访问频率低的数据存放到低速存储介质中,即使读取速度慢,但是频次低,对业务使用体验影响较小。

上述描述中,高速存储介质也叫热存储,低速存储介质也叫冷存储。冷热存储方案在国内外不同:

  1. 国内机器硬件较便宜,可以自主采购,可以使用SSD、SATA等不同类型的磁盘作为热存储和冷存储。
  2. 在国外,主机和磁盘都是使用云厂商,为了节省磁盘带来的大额开支,可以使用JuiceFS on HDFS,将热数据写到S3中,Juicefs通过并发上传S3,读写S3的速度较快;冷数据直接上传到S3中。

2. DataNode异构磁盘存储方案

2.1 HDFS存储策略介绍

在HDFS中,将磁盘的类型分为四种,如下所示:

存储类型 存储速度 存储成本 备注
RAM_DISK 最快 最贵
SSD
DISK 一般 一般 HDFS默认为DISK配置
ARCHIVE 较慢 便宜

对于HDFS文件,通过设置不同的存储策略,可以将文件存放到对应类型的磁盘中。例如,默认的Hot存储策略会将数据存放到DISK类型的磁盘中:

Untitled.png

DataNode中,通过dfs.datanode.data.dir配置存储数据的目录,每个目录都有对应的磁盘类型。例如:/disk1/dfs/data默认就是DISK类型;/disk3/dfs/data就设置了ARCHIVE类型。如果将文件存储策略设置为Hot类型,Block就会存储到/disk1/dfs/data这类目录中;如果将文件设置为Cold策略,Block就会存储到/disk2/dfs/data这类目录中。

因此,运维人员需要根据磁盘硬件规格,设置为HDFS对应的磁盘类型。例如,将SATA类型的磁盘对应的HDFS存储目录设置为ARCHIVE:

  <property>
    <name>dfs.datanode.data.dir</name>
     <value>/disk1/dfs/data,/disk2/dfs/data,[ARCHIVE]/disk3/dfs/data,[ARCHIVE]/disk4/dfs/data</value>
  </property>

2.2 存储策略命令

如果需要将集群中的文件从一类磁盘迁移到另一类磁盘,需要变更文件对应的存储策略,并通过mover工具进行迁移:

#将/src目录的存储策略从Hot改为Cold
hdfs storagepolicies -setStoragePolicy -path /src -policy Cold
#开始迁移数据
hdfs mover -p /src
#查询存储策略
hdfs storagepolicies -getStoragePolicy -path /src
#迁移后检查/src目录是否在ARCHIVE磁盘对应的机器中
hdfs fsck /src -files -blocks

关注NameNode WebUI中ARCHIEVE存储类型容量是否变化:

Untitled 1.png

2.3 代码解析

在BlockReceiver初始化时,如果此时正在创建Pipeline,就创建根据storageType存储策略在对应类型磁盘中创建rbw目录:

    switch (stage) {
        case PIPELINE_SETUP_CREATE:
          replicaHandler = datanode.data.createRbw(storageType, storageId,
              block, allowLazyPersist);

FsDatasetImpl.createRbw方法获取storageType对应的目录:

ref = volumes.getNextVolume(storageType, storageId, b.getNumBytes());

如下,匹配存储策略,获取一个对应目录,FsVolumeImpl中就保存了目录信息:

FsVolumeReference getNextVolume(StorageType storageType, String storageId,
      long blockSize) throws IOException {
    final List<FsVolumeImpl> list = new ArrayList<>(volumes.size());
    for(FsVolumeImpl v : volumes) {
      if (v.getStorageType() == storageType) {
        list.add(v);
      }
    }
    return chooseVolume(list, blockSize, storageId);
  }

3. Router挂载冷热子集群方案

DataNode异构磁盘存储方案有两个缺陷:

  1. 每次新增异构磁盘需要更改dfs.datanode.data.dir配置,一旦人工操作有误,将目录配置错误,但是部分目录没有上报数据,重启DN时NameNode在在其他DN中恢复副本,可能产生大量网络流量,影响正常业务的读写。
  2. 由于不同磁盘都放在一个集群中,冷数据磁盘存储的数据量更大,它们共用一个集群的网络。客户端访问冷数据存盘时,可能产生大量网络流量,影响正常业务的读写。

为了解决网络带宽抢占对于正常热数据读写的影响。可以将冷数据单独放在一个集群中,该集群中的磁盘都是SATA大盘。这样,即使冷数据集群因为各种原因网络带宽打满,也不会影响热数据集群。

当然,该方案还是有一个问题:对于冷数据,它在另外一个集群,这会导致用户使用姿势改变,需要用户记住哪部分数据在冷集群,对用户十分不友好。为了解决这个问题,可以新建Router的WRITE_FIRST多挂载类型,将目录同时挂载到冷热数据集群。

3.1 WRITE_FIRST代码

如下,增加WRITE_FIRST的挂载类型:

Untitled 2.png

WRITE_FIRST策略优先选择第一个子集群访问:

Untitled 3.png

使用WRITE_FIRST时,尽量避免两个集群同时存在同一份数据。

3.2 WRITE_FIRST策略介绍

当执行router进行挂载时,可以指定子集群的挂载策略,如下,使用WRITE_FIRST策略将router中的/data/gamein/xx路径同时挂载到了冷热集群hotCluster1,coldCluster2的/data/gamein/xx目录下:

hdfs dfsrouteradmin -add /data/gamein/xx hotCluster1,coldCluster2 /data/gamein/xx -order WRITE_FIRST

通过测试方法:

  1. router mkdir创建目录时,会在冷热两个子集群中创建目录。
  2. router rm删除目录时,会删除冷热两个子集群中创建目录。注意要将Trash目录也挂载到两个子集群中,否则一个冷子集群删除失败。
  3. router create / put / touch/ cp文件时,只会在热集群中创建文件。
  4. router ls,优先会查看冷子集群中的文件,这个命令有不符合预期,但是影响不大。
  5. router cat/ get,优先会访问热子集群中的文件。
  6. router truncate/appendToFile文件时,如果热集群中有该文件,就append热集群;如果热集群没有该文件,冷集群中有该文件,就append冷集群。
  7. router rm / mv / delete删除文件时,只会删除热集群中的文件。

3.3 挂载操作执行流程

  1. 将数据通过distcp迁移到冷子集群。
  2. router中创建数据目录。
  3. Router多挂载。
  4. hive元数据中修改路径对应的表的元数据。
#将数据通过distcp迁移到冷子集群。
hadoop distcp -pugbt -direct -strategy dynamic -m 200  hdfs://cluster1/data/gamein/xxx   hdfs://coldcluster/data/gamein/xx 
#router中创建数据目录。
hdfs dfs -mkdir /data/gamein/xx
#Router多挂载。
hdfs dfsrouteradmin -add /data/gamein/xx hotCluster,coldcluster /data/gamein/xx -order WRITE_FIRST
#hive元数据中修改路径对应的表的元数据。

4. 海外S3冷叔数据存储

4.1 热数据存储

海外机器一般使用AWS的EC2机器,它的SSD和DISK磁盘价格一般是国内的5倍以上,成本非常高。为了节省存储,可以选择将DataNode的dfs.datanode.data.dir目录改成juicefs的挂载目录。当DataNode写入数据时,会写入到Juicefs中,Juicefs底层挂载S3,最终写入到S3中。这样,就解决了直接写入S3非常慢的问题。

如下,是Juicefs On HDFS的架构图。部分DataNode可以直接挂载AWS SSD磁盘,其他都接入到Juicefs中:

Untitled 4.png

当Juicefs收到写File文件的请求时,它会将每个File文件切分成为预设好的block文件,通过并发的方式上传到S3中。这就是Juicefs写S3快的原因:

Untitled 5.png

不过,并发写入小文件到s3,会频繁执行put操作,会增加s3的op成本。

4.2 Alluxio+S3存储冷数据

Alluxio架构不会像Juicefs一样将文件切分成block分批上传,因此写性能没有juicefs高。但是juicefs也有缺陷,在s3上的文件都是切分出来的block,必须通过DataNode访问,juicefs通过存储的元数据获取s3上的block才能完整访问一个HDFS上的Block文件。这样,如果把冷数据放到juicefs中,那么产生的流量可能会把HDFS集群带宽打满,影响正常业务。

也了隔离流量上的压力。可以选择通过distcp直接把整个hdfs文件distcp到s3中,通过alluxio读取s3数据,同时alluixo还支持ranger鉴权,提高了安全性。

如下所示,Alluxio还支持各种文件系统的挂载,可扩展性非常高:

Untitled 6.png