开头前先说清楚:这是一篇偏实战向的整理,不是官方文档复读。我在生产环境里维护过几套HDFS集群,也帮朋友排查过“磁盘明明加了,写入还是慢”的诡异问题,最后根因基本都落在存储介质混用和存储策略没规划上。HDFS数据分层存储解决的就是这么一件事:让热数据待在快盘,让冷数据待在便宜盘,把集群的每一寸空间和每一秒响应都用在刀刃上。下面这套方法我完整跑通过,从命令到坑点都给你捋一遍。
1. 冷热数据混着存,其实是在给集群悄悄加负担
我刚接手集群那会儿,最直观的感受是:磁盘类型五花八门,但所有数据都按同一种方式存放。节点上既有NVMe SSD,也有普通SATA盘,甚至还有几块慢速大容量盘,可HDFS里的目录全都一样,没有谁告诉NameNode“这个目录下的数据应该偏爱哪种介质”。结果就是,三个月前的日志和今天的实时计算临时表住在同一批盘上,谁都不舒服。
先说成本账。SSD的单位容量价格大概是机械盘的几倍到十几倍,如果冷数据占了SSD空间,等于拿金库的位置放旧报纸。反过来,如果热数据落到慢速盘上,任务一跑就出现IO抖动,MapReduce或Spark的shuffle阶段尤其明显,可能单个作业从几分钟涨到半小时。磁盘利用率一旦超过85%,读写延迟还会非线性恶化,这时候再去排查瓶颈,往往发现CPU和内存都闲得很,就是存储拖了后腿。
再看运维层面的负担。没做分层的时候,所有数据都在同一个池子里,你要判断哪些文件该清理、哪些该归档,只能靠人工扫目录、看修改时间,效率极低。而且冷数据常年不动,却参与了DataNode的block report、心跳和副本校验,NameNode每次扫描都要过一遍,白白消耗元数据服务的内存和CPU。
HDFS数据分层存储的核心思路,就是用存储策略把“数据生命周期”这个逻辑概念映射到“物理介质”上:新写入的、高频访问的放SSD或热盘,老化的、低频访问的挪到廉价归档盘。这不需要改造上层计算框架,也不用动数据本身,只靠在NameNode上标记目录策略、在DataNode上划分存储类型就能完成,对已经跑着的业务几乎透明。
我在好几个环境里观察到的收益是:热盘利用率降下来之后,查询P99延迟普遍能改善20%到40%;冷数据迁移到ARCHIVE盘之后,集群整体存储成本降低大约一半,因为你不再需要为历史数据采购昂贵的SSD容量。当然,具体数值取决于你的数据访问模型,但这个方向是确定的——先分层,再谈优化。
2. 先弄懂Storage Type、StoragePolicy和block之间的关系
很多教程一上来就教命令,结果读者跑完发现数据根本没挪,问题就出在没搞懂HDFS分层存储的三个基本概念:Storage Type、Storage Policy、Block Storage Type。
2.1 四种存储介质类型:从内存到归档盘的“冷热阶梯”
HDFS把DataNode上挂载的目录抽象成四种存储介质类型(Storage Type):
- RAM_DISK:把数据放在内存里模拟成磁盘,通常配合内存盘使用,延迟最低,但容量小、成本极高,适合中间的临时计算结果。
- SSD:固态盘,适合热数据、频繁读写的文件。
- DISK:普通机械盘,是HDFS默认的主力存储。
- ARCHIVE:归档盘,一般用大容量但性能一般的盘,目标是“能存、便宜”,不追求快。
在DataNode的配置里,你可以通过目录前缀明确告诉HDFS这个目录属于哪类存储。例如在hdfs-site.xml中:
<property> <name>dfs.datanode.data.dir</name> <value>[DISK]file:///data1/hdfs/dn,[DISK]file:///data2/hdfs/dn,[ARCHIVE]file:///data3/hdfs/archive</value> </property>这里的[ARCHIVE]前缀非常关键,它决定了DataNode在向NameNode汇报时,这段目录属于归档存储。没有前缀的目录默认就是DISK。生产环境里我见过有人把归档盘目录写出来、忘记加[ARCHIVE]前缀,结果所有数据依然随机落盘,策略形同虚设。
2.2 存储策略目录与block之间的映射关系
存储策略(StoragePolicy)是挂在HDFS目录上的元数据属性,它定义了“这个目录下的新block优先写到哪些存储介质,以及后续可以流向哪些介质”。HDFS自带的6种策略如下:
| 策略名称 | 作用路径 | 适合场景 | 说明 |
|---|---|---|---|
| LAZY_PERSIST | RAM_DISK → DISK | 临时数据、中间结果 | 先写内存,异步落盘,重启有丢失风险 |
| ALL_SSD | SSD | 高频访问的热数据 | 所有副本都要求放SSD |
| ONE_SSD | SSD → DISK | 读热写温 | 至少一个副本在SSD,其余放DISK |
| HOT | DISK | 默认策略 | 所有副本放DISK,这是HDFS的老传统 |
| WARM | DISK → ARCHIVE | 近期可能还会访问 | 新block先写DISK,后续可迁到ARCHIVE |
| COLD | ARCHIVE | 归档、极少访问 | 所有副本都放ARCHIVE |
注意,策略定义的是“生产环境下的倾向顺序”,不是“绝对必须落在某一块盘”。ONE_SSD和WARM这类策略允许在找不到对应介质时退化到下一级介质,比如ONE_SSD下如果集群里没有SSD,老数据就只落DISK。
2.3 新block如何继承存储策略
文件block在创建时,会先去找它所在目录的StoragePolicy,如果目录没设置,就往上找父目录,一直找不到就用默认的HOT。这里有个容易忽略的细节:已经落盘的旧block不会因为目录策略改变而自动迁移。你改了目录策略,只是给“以后新建的block”下发了一个新要求;存量block要动,得靠后面讲的Mover工具。
我之所以反复强调这个点,是因为它几乎是所有分层存储实施项目里最容易踩的第一个坑。你兴冲冲执行完setStoragePolicy,跑了一个查询,发现数据还是老位置,就以为功能坏了,其实不是,是所有历史block根本没被“安排去搬家”。
一句话总结这个章节:策略管的是“新数据愿意住哪”,存储类型管的是“哪里能住”,block是真正住在里面的住户,住户搬家要靠Mover。
3. 从目录规划到storagepolicies命令的落地流程
理论通了,下面给一套可直接照抄的落地步骤。假设我们的集群目录结构叫/data,下面分/data/hot、/data/warm、/data/cold三个场景。
3.1 先把目录规划和存储介质规划好
第一步不是敲命令,是想清楚你的分层阈限。我推荐按“访问频率 + 数据年龄”组合判断:
- 实时计算依赖的ODS增量表、最近7天需要频繁查询的数据 → 走HOT,放在DISK即可,如果访问量确实大且资金允许,可以单独划一个目录走ALL_SSD。
- 7天到90天之间、偶尔被追溯查询的数据 → 走WARM,新数据先在DISK上写,老数据自动/手动迁到ARCHIVE。
- 超过90天、只保留给审计或离线分析的数据 → 走COLD,直接落到ARCHIVE盘。
这个阈值不是死的。我见过有些业务只要3天内的热数据,也见过一个月不访问就可以转冷的情况,核心是按你的真实查询分布来定,而不是照抄别人写的“90天”。
3.2 确认DataNode上已经挂好ARCHIVE盘
在跑策略之前,先确认集群里确实有节点挂载了ARCHIVE目录。如果你把COLD策略设置到一个只有DISK的集群上,Mover会一直找不到合适的迁移目标,然后报“无法满足存储策略”的异常。
检查方式:
hdfs dfsadmin -report输出里会列出每个DataNode的Storage Type及对应容量。如果ARCHIVE类型很少或没有,就得回hdfs-site.xml里看看目录前缀是不是配上了[ARCHIVE]。
3.3 查看当前策略和执行策略下发
查看集群支持的所有策略:
hdfs storagepolicies -listStoragePolicies给指定目录设置策略:
hdfs storagepolicies -setStoragePolicy -path /data/cold -policy COLD hdfs storagepolicies -setStoragePolicy -path /data/warm -policy WARM查看目录当前策略:
hdfs storagepolicies -getStoragePolicy -path /data/cold输出会显示Storage policy: COLD。如果你嫌麻烦,想在一条命令里验证多个目录,就循环跑一下。
注意:设置策略时,
-path指向的目录必须存在。HDFS不会帮你自动创建父目录。
3.4 新数据验证:写入并检查block分布
设置完策略后,随便往目录里写一个文件,再用fsck查看block位置:
hdfs fsck /data/cold -files -blocks -storagepolicies-storagepolicies参数会显示每个block当前的存储类型,以及是否符合目录策略。新写入的文件应当直接落在ARCHIVE盘上,如果落盘位置不对,就回头检查DataNode配置和NameNode的策略设置。
这里还有一个容易被忽略的细节:如果父目录是HOT,子目录是COLD,你往子目录写文件时,block会按子目录策略创建;但如果子目录没设策略,就必须逐级往上层找。所以目录层级越深,越要定期检查每一层的策略是否如你所愿。
4. 为什么改完策略数据没有立刻动?Mover与Balancer的搬运逻辑
这个章节是本文的灵魂,因为90%的分层存储疑问都集中在这里。前面说了“存量block不会自动搬家”,那到底谁负责搬家?答案是一个专用程序:Mover。
4.1 Mover的工作方式
Mover是HDFS自带的离线工具,它扫描目标路径下的所有block,比对这些block期望的存储介质类型和实际所在介质,如果发现不一致,就生成搬迁计划,然后通过DataNode之间的数据传输完成搬移。
基本命令:
hdfs mover -p /data/warm -p /data/cold-p可以指定多个路径,也可以不带-p对整个集群的block执行检查,但生产环境不建议常态化全集群执行,因为很慢、很重。我通常的做法是:按目录分别跑,优先处理大目录,小目录并行跑。
执行之后,Mover日志会输出类似“Move block ... from DISK to ARCHIVE”的进度。这个操作是异步且离线的,意味着不会阻塞正在执行的读写作业,但会占用磁盘IO和网络带宽。
4.2 Mover和Balancer要不要配合?
这里要区分两个角色:
- Mover:负责“按存储策略迁移block到正确的存储介质”。
- Balancer:负责“让各DataNode之间的磁盘空间/负载趋于均衡”。
两者独立,但经常需要配合。举个真实例子:你把一批block从DISK迁到了ARCHIVE,结果所有ARCHIVE盘集中在一台老节点上,导致那台老节点的IO被打满,而其他节点的ARCHIVE盘闲着。这时候需要跑一次Balancer,让它把block在相同存储介质类型的目录之间重新摊平。
hdfs balancer -threshold 10-threshold 10表示允许集群内节点磁盘使用率偏差在10%以内。Balancer只会在相同Storage Type之间做平衡,不会把SSD上的数据挪到ARCHIVE上去,放心用。
4.3 底层到底发生了什么?
一次block迁移背后的链路大概是:
- Mover向NameNode请求特定路径下所有block的位置和策略信息。
- NameNode返回block所在DataNode节点、副本位置、当前StorageType。
- Mover筛选出“目标介质不匹配”的block,例如策略要求ARCHIVE但现在落在DISK。
- Mover给DataNode下发迁移指令,由源节点把block数据拷贝到目标节点的ARCHIVE目录。
- 拷贝完成后,NameNode更新block的存储位置元数据,旧副本标记删除。
这个过程中,磁盘IO和跨节点网络传输是主要开销。如果大目录一次性全量迁移,一个数据副本几百GB,网卡直接被打满。所以在实操时,我的建议是:
- 低谷期执行:迁移任务尽量排在凌晨或业务低峰。
- 按分区/子目录分批次迁移:不要一次性
-p /data,而是-p /data/warm/2025-06这样一天一天分区跑。 - 配合监控观察:跑的时候用
iostat -x 1看看源盘和目标盘的%util,如果持续到90%以上,说明IO瓶颈已经显现,下一批要放缓。
5. 生产环境里的高发坑:ARCHIVE盘、小文件、策略继承与误操作恢复
再好的配置,落地过程也免不了踩坑。下面几个问题我基本都在真实集群上见过,每条都值得记进笔记本。
5.1 ARCHIVE盘用错容量统计:看着容量够,其实不够
有些运维为了省事,把一个物理盘同时挂载两个目录,一个标[DISK]一个标[ARCHIVE]。这在HDFS层面是允许的,但你会遇到一个反直觉的现象:dfsadmin -report显示ARCHIVE容量存在,但写入时总报“空间不足”。原因是同一个物理盘的空间被分成两份,两份都属于同一个物理资源;ARCHIVE目录写满了,DISK目录再大也救不了。
我的建议:ARCHIVE盘最好是独立的物理盘或至少一个独立RAID组。如果条件不允许,也要在容量预估时明确知道,所谓“ARCHIVE容量”和“DISK容量”之和才是物理真容量,别把两者当独立的资源池来计算。
5.2 小文件数量太多的目录,Mover跑到天荒地老
Mover搬移的粒度是block,不是文件。一个目录下有1000万个block和100万个block,迁移消耗差别巨大。如果你的Hive表按日分区,每天一个分区里全是一个个小文件(几KB级别),那这个分区的block数量会非常恐怖,Mover光是逐个检查block路径就耗时极长。
处理思路是在迁移前先优化文件大小。比如对一个Hive分区先做一次合并(INSERT OVERWRITE ... SELECT或CONCATENATE),让它输出为若干个128MB或256MB的block,再对该目录设策略、跑Mover。我见过有人不合并直接迁,最后跑了三天都没结束,浪费了大把IO。分层存储只是存储策略,不解决文件布局问题,小文件治理永远是前置步骤。
5.3 策略继承的“坑”:父目录设了,子目录被连坐
假设你要把/data/hive/ods/old设置成COLD,命令执行没问题,但如果这个/data/hive/ods本身被设过WARM,那么old的动作会同时影响它自己的所有子目录,包括你还没来得及处理的近期分区。
更危险的操作是:有人为了“统一管理”,直接在根目录/上设置了策略。这个操作会让整个集群所有新文件都遵循根目录策略,如果你的策略是COLD,那之后新建的所有Hive表、临时文件全都会试图落到ARCHIVE盘。一旦ARCHIVE盘没那么多容量,NameNode上报错、写失败,整个集群陷入险境。
安全做法是:
- 永远先查策略再设置:
hdfs storagepolicies -getStoragePolicy -path /data/hive/ods/old。 - 在设置前对父目录做一次
unset或者明确知道父目录策略的影响。 - 最好在自动化脚本里加一个校验:
getStoragePolicy输出不符合预期就中断,不继续执行。
如果误设了根目录政策,恢复方法也很简单:
hdfs storagepolicies -unsetStoragePolicy -path / hdfs storagepolicies -setStoragePolicy -path / -policy HOT但要注意,unset之后,新建文件会回到默认策略,而之前设置策略期间新建的block不一定自动回迁,还是要靠Mover按目标策略再跑一遍。
5.4 多副本策略的误解:COLD不是“一个副本在DISK,一个在ARCHIVE”
这里特别容易出认知偏差。很多人以为COLD策略下,可以“一个副本放DISK保证读取,一个副本放ARCHIVE保证存储”,其实不是。HDFS的副本策略是“所有副本都要符合该存储类型的倾向”,COLD就是全部副本都放在ARCHIVE,WARM则是全部副本都在DISK→ARCHIVE的流转链路里,不是说把一个副本放DISK、一个副本放ARCHIVE。
实测中,如果你设了COLD,但副本数=3,Mover会尝试把这3个副本全部挪到ARCHIVE盘上。如果ARCHIVE盘数量不足,副本会一直处于“违反策略”状态,fsck -storagepolicies会报warning。所以容量规划时,一定要按副本系数计算ARCHIVE盘需求,不要只按文件大小算一遍。
5.5 归档数据被查询拖慢:冷热也要配合计算层调度
冷数据真到了ARCHIVE盘,读取速度确实会慢一些。如果你有一个Spark或Hive作业,每天凌晨定时扫描半年前的COLD表,那这个作业的耗时大概率会从几分钟变成几十分钟。这不是HDFS分层存储设计错了,而是计算任务调度没有感知存储位置。
后续的优化路径一般有两个:一个是把这类跨冷热数据的作业尽量放在白天低峰,避免和实时任务抢IO;另一个是让计算层优先读取DISK上的热点分片,例如用Spark的spark.locality.wait参数增大本地性等待时间。如果业务上允许,也可以把COLD表的查询改为异步批处理,不在主链路里等待。
6. 把分层策略做成固定运维习惯:定时任务、监控和可视化
分层存储不是做一次就完事的,数据每天都在老化,如果不把策略管理交给定时任务,过了几个月,新的冷数据又会重新堆积在热盘上。
6.1 定时策略脚本的思路
我一般会在集群上放一个Shell脚本,按“数据年龄阈值”自动给新分区设策略:
#!/bin/bash HDFS_HOME="/data/hive/ods" TODAY=$(date +%Y-%m-%d) # 90天前的日期 COLD_DATE=$(date -d "90 days ago" +%Y-%m-%d) # 30天前的日期 WARM_DATE=$(date -d "30 days ago" +%Y-%m-%d) hdfs storagepolicies -setStoragePolicy -path ${HDFS_HOME}/day=${COLD_DATE} -policy COLD hdfs mover -p ${HDFS_HOME}/day=${COLD_DATE} hdfs storagepolicies -setStoragePolicy -path ${HDFS_HOME}/day=${WARM_DATE} -policy WARM hdfs mover -p ${HDFS_HOME}/day=${WARM_DATE}然后把脚本挂到crontab,每天凌晨2点跑一次。注意脚本里要加set -e,一旦某个分区目录不存在或策略设置失败,就退出,避免连锁误操作。
在实际集群里,我更推荐用Airflow或DolphinScheduler之类的调度平台来做,因为可以看到每次迁移任务的状态、重试和告警,而不是每天去看cron日志。
6.2 分层效果的日常监控
我最常用的三个验证手段:
hdfs dfsadmin -report:看每个DataNode的Storage Type容量使用率。如果ARCHIVE使用率螺旋上升,说明策略在生效;如果ARCHIVE常年不变,说明Mover没在跑,或者策略下发有问题。hdfs fsck /data -files -blocks -storagepolicies:直接扫描整个路径,输出“哪些block不符合策略”。这是判断存量数据是否迁移干净的唯一权威入口。- NameNode UI的“Storage Policy”页面:可以看到每个策略下的文件数和block数概览。如果COLD策略下文件数始终为0,说明策略名没对上,还是优先级没传导到目录。
6.3 大数据量迁移的节奏管理
根据个人经验,分批比全量重要一百倍。一个200TB的项目,你不可能一个晚上搬完,也没有必要。把迁移拆成“按周、按分区”的批,每次控制在总容量的10%-15%,跑完一批验证一批,发现IO异常就停一停。这样既不影响生产,也能让你随时判断哪个目录策略下错了。
最后再分享一个小技巧:在设置策略之后、跑Mover之前,先设一个虚拟的测试目录玩一遍。我每次新增存储介质类型或调整策略时,先建一个/tmp/storage-test,写入几个文件,跑一下fsck -storagepolicies,确认block确实落在目标介质上,再放心地对生产目录下手。这个习惯帮我避免过至少三次“配错了前缀或目录层级没搞对”的尴尬局面。分层存储这东西,原理不复杂,真正难的从来都是把运维节奏跑顺。