做了一个两年多的Hadoop集群,最让我睡不着的不是NameNode挂掉,而是某天半夜磁盘利用率告警短信像机关枪一样打过来。那时候集群里SSD被一堆建表时图省事打了ALL_SSD策略的热表占满,HDD和一批冷归档盘却闲得能跑马。后来认真把 HDFS 的存储策略体系吃透,才意识到所谓“数据分层存储”不是简单的把文件拷到不同目录,而是要学会让 HDFS 自己按规矩把不同温度的数据,放到不同介质的盘上。这篇就把 HDFS 数据分层存储策略从原理到实操完整拆给你听,里面的命令和坑都是我实际踩过的,拿来就能用。
这篇文章适合三类人:正在啃 Hadoop 基础、准备大数据面试的同学;要在毕业设计里做一个数据仓库或者离线数仓项目、想加亮点的学生;以及生产环境里被磁盘成本逼到不得不做冷热分离的大数据开发或运维。全程不整虚的,直接讲清楚分层存储为什么有效、命令怎么敲、数据怎么流转、遇到问题怎么排查。
1. 先想明白:HDFS为什么要做数据分层存储
1.1 一次真实的磁盘利用率事故
我接手过一个分析型集群,每台 DataNode 是 2 块 960G SSD 加 6 块 4T HDD。刚上线那阵子,所有表都没改策略,默认落在 DISK 上,集群跑得四平八稳。后来业务方抱怨跑批慢,开发同学一上来就把几张核心大表全设置了 ALL_SSD,觉得“SSD 快,全部放 SSD 肯定没错”。
三个月后问题就来了:SSD 平均使用率 92%,HDD 使用率才 48%。NameNode 日志里全是块放置失败的重试记录,因为 ALL_SSD 策略要求三副本都在 SSD 上,而新写入的块已经没有足够的 SSD 空间可以放置。与此同时,一堆半年没被查询过的历史分区还稳稳躺在 SSD 上占着坑。
当时的第一反应是手工迁移:用 distcp 把冷表拷到临时目录、改掉 Hive 表的 location、再把老数据删掉。一套流程走下来,光一个库就要协调业务停读写两小时,根本没法和数据增长的速度赛跑。
这个场景几乎是所有中大型 Hadoop 集群的必经之路。表面上缺的是磁盘空间,深层缺的是一套“让数据根据访问热度自动落到合适介质”的机制,也就是 HDFS 数据分层存储策略。
1.2 分层存储到底解决什么问题
先打个比方。你家的衣柜不会把所有衣服都挂在最方便拿的那根杆子上,羽绒服过了冬天就收进顶层储物箱,常穿的 T 恤才挂在随手能取的位置。HDFS 分层存储做的事情一模一样:把访问频率高的“热数据”放在读写速度快的存储介质上,把很少访问的“冷数据”挪到成本更低的盘上。
在分布式存储里,不同介质的成本差异是数量级的。一块 NVMe SSD 的单位容量价格可能是普通 HDD 的 5 到 10 倍,而 HDD 又是归档型大容量盘的好几倍。与其让所有数据都挤在最快也最贵的盘上,不如把存储成本花在刀刃上。
这里要强调清楚,分层存储不等于压缩数据,更不等于减少副本数。它本质上是同一套 HDFS 命名空间、同一套副本机制内部,对副本所在物理介质做区分管理。数据还在同一批 DataNode 上,只是“住在不同价位的房间里”。这样既保证了原有的大数据可靠性模型不变,又让成本结构更合理。
1.3 存储类型与存储策略的完整模型
HDFS 把 DataNode 上的物理磁盘目录按类型打标,一共四种主流存储类型:
- RAM_DISK:DataNode 节点的内存,速度最快,容量最小,一般用于瞬时写入缓冲
- SSD:固态硬盘,适合高 IO 的温热血数据
- DISK:普通机械硬盘,默认存储类型,性价比均衡
- ARCHIVE:大容量归档盘,通常是低速但便宜的老盘,专门放冷数据
有了存储类型,HDFS 再通过“存储策略”来决定一个文件或目录的块应该优先落到哪种类型上。每个存储策略本质上由两部分组成:首选存储类型,以及一个回退(fallback)类型列表。当集群里找不到满足首选类型的节点时,就按 fallback 顺序降级放置。
HDFS 内置了 6 种常用策略,我做成了下面这张对照表:
| 策略名 | 块存储分布 | 典型用途 |
|---|---|---|
| LAZY_PERSIST | 先写 RAM_DISK,异步落盘到 DISK | 实时采集的原始日志、低延迟写入缓冲 |
| ALL_SSD | 全部副本放 SSD | 高频查询的少量热表 |
| ONE_SSD | 1 个副本在 SSD,其余副本在 DISK 或更高一层 | 跑批中间结果、读写比例悬殊的数据 |
| HOT | 全部副本放 DISK(默认策略) | 日常频繁读写的主数据 |
| WARM | 1 个副本在 DISK,其余副本在 ARCHIVE | 月度报告、偶尔查询的温数据 |
| COLD | 全部副本放 ARCHIVE | 历史明细、审计日志、长期不访问的备份 |
这里有个很容易忽略的细节:策略是打在目录上的,不是打在文件上的。你给一个目录设置了 COLD 策略,之后在这个目录下新建的文件会继承 COLD 布局;但目录里已经存在的历史文件并不会自动搬走,必须要靠 mover 工具扫描并触发块迁移。理解这一点,后面所有实操都会顺很多。
2. 落地实操:存储策略的配置与常用命令
2.1 磁盘类型声明与目录打标
配置分层存储的第一件事,是让 HDFS 知道每个 DataNode 节点上有哪些类型的盘。这一步是在hdfs-site.xml里通过dfs.datanode.data.dir指定的,目录前面的[类型]标签就是磁盘类型的声明。一个典型的配置长这样:
<property> <name>dfs.datanode.data.dir</name> <value>[SSD]file:///data1/ssd,[DISK]file:///data2/disk,[ARCHIVE]file:///data3/archive</value> </property>没有加任何标签的目录,默认会被当作 DISK。多个目录之间用逗号分隔,方括号和路径之间不要顺手敲空格,不然 DataNode 启动时解析会出现奇怪的问题。改完配置后如果不想滚动重启,可以用hdfs dfsadmin -reconfig对 DataNode 做动态刷新,这在生产环境里非常实用。
磁盘标签配好之后,还只是具备了“物理基础”。要让某类数据真的按分层逻辑存放,还需要在命名空间里创建目录,并对目录打上存储策略标签。整个过程我习惯分三步走。
第一步,创建业务目录。假设我们要把历史数据仓库独立出来:
hdfs dfs -mkdir -p /user/hive/warehouse/dw_ods_history第二步,给目录设置 COLD 策略:
hdfs storagepolicies -setStoragePolicy -path /user/hive/warehouse/dw_ods_history -policy COLD第三步,确认策略是否生效:
hdfs storagepolicies -getStoragePolicy -path /user/hive/warehouse/dw_ods_history返回结果里会显示Storage policy: COLD,说明目录级别的策略已经绑定成功。
这套流程是典型的“先物理打标、后逻辑打标”,很多新手会漏掉第一步,直接在 DataNode 上没有 ARCHIVE 盘的集群上设置 COLD 策略,然后发现数据根本没往期望的盘上放。记住,存储策略是逻辑,磁盘类型标签是物理,缺一不可。
2.2 策略设置命令速查
官方提供的hdfs storagepolicies命令下挂了不少子命令,我把日常最常用的整理成一张速查表,方便你直接抄作业:
| 命令 | 作用 |
|---|---|
hdfs storagepolicies -listStoragePolicies | 查看集群当前支持的全部策略列表 |
hdfs storagepolicies -setStoragePolicy -path <dir> -policy <name> | 给目录设置存储策略 |
hdfs storagepolicies -getStoragePolicy -path <dir> | 查看目录当前绑定的策略 |
hdfs storagepolicies -unsetStoragePolicy -path <dir> | 解除目录的自定义策略,恢复为默认 HOT |
hdfs fsck <path> -files -blocks -locations | 查看文件块的物理分布和存储类型 |
listStoragePolicies这个命令我建议每次做变更前都先跑一遍,因为不同 Hadoop 发行版内置的策略名称和 fallback 顺序可能有差异,自己确认过才不会想当然。
验证物理分布时,fsck的输出会精确到每个块落在哪台 DataNode、哪个存储类型上。假设我们把某个表目录设成了 WARM 策略,跑完fsck后,你会看到块的存储类型分布里 DISK 和 ARCHIVE 各占一部分。这正是判断“策略到底有没有生效”的唯一标准,不是看目录属性,而是看块到底住在哪里。
2.3 场景化选型:冷-温-热数据分别怎么定
很多人配置完存储策略后问我的第一个问题是:我到底该把哪些目录设成什么策略?这事没法拍脑袋定,最好根据数据访问频率和容量成本来定。我整理了下面这套选型思路,照着套基本不会错:
- 每日高频访问的数据,比如最近 7 天的订单明细、用户日活明细,建议用 HOT 或 ONE_SSD。如果对延迟极其敏感且数据量不大,可以上 ALL_SSD,但要接受三份 SSD 副本带来的成本翻倍。
- 跑批任务产生的中间结果、Spark Shuffle 文件这类生命周期短、写一次读一次的数据,建议用 ONE_SSD。一个副本走 SSD,能明显提升跑批速度,又不会让 SSD 容量压力过大。
- 月度或季度报表、经常被扫描但不那么要求实时的汇总数据,用 WARM。这样大部分副本在归档盘,查询偶尔访问的那一份 DISK 副本,容量和性能都平衡。
- 超过 90 天没人查的历史明细、审计日志、临时备份,直接设 COLD。这些数据一辈子就为了“偶尔捞一次”,把它们全部挪到归档盘,是性价比最高的操作。
- 实时采集端写入的原始日志,比如 Flume 正在采集的日志目录,可以考虑 LAZY_PERSIST。写内存、异步落盘能显著降低写入延迟,但要注意进程崩溃时内存中还没落盘的数据会丢。
选型背后的逻辑其实就一句话:查询越频繁的数据,副本里“快盘”的比例要越高;容量越紧张、查询越少的数据,越应该整体往“慢盘”下沉。分层存储不是非黑即白,ONE_SSD、WARM 这种“一部分快一部分慢”的策略往往才是性价比最高的。
3. 存储策略对读写流程的影响与数据流转机制
3.1 写入流程:策略落在哪个环节
很多教程会直接罗列 HDFS 写入流程:客户端调 create,NameNode 返回 DataNode 列表,客户端以 Pipeline 方式逐包写入。但很少有人告诉你,存储策略是在哪个环节发挥作用的。
实际过程是这样的:客户端发起写请求时,NameNode 会检查目标父目录当前绑定的存储策略,然后由内部策略引擎算出这个新文件每个块应该使用的目标存储类型。接下来,NameNode 在挑选 DataNode 时,会优先选择具备对应存储类型的节点加入写入管线。如果满足条件的节点不够,再按策略的 fallback 顺序往下找。
也就是说,客户端自己并不知道块要写成 SSD 还是 ARCHIVE,这个安排完全由 NameNode 主导。这也是为什么“目录打标”那么重要,因为 NameNode 判断新文件该往哪放,就是靠目录继承来的策略。
LAZY_PERSIST 是一个特殊的存在。普通策略的块在 DataNode 上落地后就算写入完成,而 LAZY_PERSIST 的块先写入 DataNode 节点内存,再由 DataNode 后台异步地把数据 flush 到 DISK。这个设计把“网络传输完成”和“磁盘落盘完成”解耦了,对实时采集这类高吞吐写入非常友好。代价就是内存一旦失电,没来得及落盘的数据会丢,所以它只适合可以容忍少量丢失的原始日志场景。
3.2 读取流程:读哪份副本更划算
HDFS 的读取流程里,客户端拿到的是块副本所在 DataNode 的列表,然后按照“本机 > 同机架 > 跨机架”的网络距离规则挑一个节点去读。分层存储对读取的影响不体现在网络距离上,而是体现在“你读到的副本究竟在什么介质上”。
举两个具体例子。一个 ONE_SSD 策略的文件,副本分布是 1 个 SSD 加 2 个 DISK,客户端如果和数据所在的 SSD 节点同机,读的就是 SSD,延迟很好看。而一个 COLD 策略的文件,三份副本全在归档盘,即使客户端和节点同机,物理读取速度也受限于归档盘的转速。
所以做分层存储时,一定要把“高频读取的目录保持足够数量的快盘副本”这个原则刻在脑子里。常见的设计是把交互式查询的数据设为 ONE_SSD,把批量扫描的历史表设为 COLD。这样交互查询走 SSD 副本,批量任务哪怕读得慢一点也无所谓,反正它跑得久也等得起。反过来,如果把高频访问目录误设成 COLD,那你省下的钱都会以“查询变慢、报表超时”的方式还回去。
3.3 冷数据回迁与均衡:mover 和 balancer 的分工
对已有数据做分层迁移,靠的是hdfs mover命令。刚才说过,设置策略只影响新写入的块,老块必须扫描触发搬迁。mover 会按照文件当前绑定的策略,找出物理存储类型不符合策略要求的块,然后把它们逐渐搬到目标介质上。
我常用的执行方式是按目录细化:
hdfs mover -p /user/hive/warehouse/dw_ods_history -t 20-p指定只处理某个目录,-t控制迁移线程数。生产环境我建议不要全集群一把梭hdfs mover,而是写一个脚本,按库、按分区维度分批执行,避免迁移风暴把集群 IO 打满。
跑完 mover 之后还要注意一点:块的物理分布变化不会瞬间反映出来,需要等 DataNode 向 NameNode 汇报更新后才能看到准确结果。所以我一般会在 mover 执行完 30 分钟后,再用hdfs fsck去核对目标目录的块存储类型分布。
容量均衡则交给hdfs balancer:
hdfs balancer -threshold 10 -D dfs.balancer.movedwinbytes=1073741824balancer 的本质是让集群中每个 DataNode 的存储使用率趋于平均,它管的是“容量分布”,不管“存储类型是否符合策略”。所以正确姿势是先用 mover 保证存储类型合规,再用 balancer 做全集群容量均衡。两者配合使用,才能真正做到既不违背策略,又让磁盘利用率均匀。
4. 卧槽,怎么又出问题:分层存储常见的坑
4.1 新策略只影响新块,老数据不动怎么办
这是所有刚上手分层存储的人都会踩的坑。你执行了setStoragePolicy,命令返回成功,打开 Hive 一看旧分区的数据还是在原来的盘上,瞬间以为自己操作错了。
其实没操作错,只是没触发迁移。存储策略是“面向未来”的:目录设置策略后,新创建的文件按新策略落盘;已经存在的文件,必须由 mover 扫描后才能搬家。换句话说,策略标签相当于搬家公司的下单通知,mover 才真正干搬家具的活。
实操中我建议把迁移动作变成日常运维的一部分,而不是一次性操作。比如每天凌晨低峰期定时执行一次针对指定历史库的 mover,既能消化当天新增的冷数据,又不会造成 IO 高峰。开个 crontab 就行:
0 3 * * * hdfs mover -p /user/hive/warehouse/dw_ods_archive -t 10 >> /data/logs/hdfs_mover.log 2>&14.2 COLD策略不等于删除副本
接着上面说,有同事看到我把历史表全部设成 COLD 策略,还以为这样可以“让冷数据自动减少副本、省更多空间”。这是对分层存储非常危险的误读。
COLD 策略只是把全部副本迁到 ARCHIVE 盘,副本数量依然是默认的 3 份。HDFS 的可靠性模型建立在多副本机制上,少一个副本就多一分丢失风险。数据因为访问少被判成“冷”,待遇上只是换更便宜的“房子”,该有的可靠性保障一分都不能少。
真要减少副本数量,那是另外一套操作,比如调整 HDFS 文件复制因子或使用纠删码,那是另一个级别的话题,和分层存储不要混在一起玩。我的建议是:分层存储解决的是“放哪”的问题,副本数解决的是“放几份”的问题,动手前先分清楚。
4.3 归档盘容量不足导致迁移失败
还有一次,我把一个 40T 的历史库目录设成 COLD,满心期待 mover 能把它们从 SSD 上清出去。结果跑了半天,fsck 一看,大部分块还在 DISK 上,而且 mover 日志里全是块放置超时的记录。原因很扎心:集群里 ARCHIVE 介质总容量只有 10T,要迁移的数据量接近 40T,根本装不下。
这种情况的坑在于,HDFS 的 fallback 机制会“帮忙”:找不到足够的 ARCHIVE 盘时,块会被放到次优的 DISK 上。表面上看迁移任务没有报错,实际上数据根本不符合 COLD 策略。你以为自己做了冷热分离,其实只是给一堆 DISK 数据挂了个 COLD 标签。
所以规划阶段一定要先统计冷数据体积和归档盘容量,比例至少要留出 30% 的余量。执行完迁移后,千万别省掉 fsck 验证这一步:
hdfs fsck /user/hive/warehouse/dw_ods_archive -files -blocks -locations | grep "Storage type" | sort | uniq -c看一眼每种存储类型的块数量到底符不符合预期,比什么都靠谱。
4.4 fsck 与权限问题排查
用 fsck 验证块分布时,经常有人遇到各种莫名其妙的报错。最常见的几类我整理了一个速查表,遇到直接对号入座:
| 现象 | 原因 | 解决 |
|---|---|---|
Permission denied/AccessControlException | 当前执行用户对 HDFS 路径没有读权限 | 检查 HDFS 目录权限,使用有权限的用户执行 |
| 提示未授权或票据过期 | Kerberos 环境下 ticket 过期或未 kinit | 重新kinit,或确认用户身份 |
| fsck 扫描特别慢 | 整个集群数据量太大,命令没加路径限制 | 用-path参数缩小扫描范围 |
Replica not placed correctly | 块物理存储类型与目录策略不符 | 跑一次 mover 后复核 fsck 结果 |
| 命令语法报错 | 参数顺序写错或少了必填项 | hdfs fsck -help查看用法 |
这些报错大多不是分层存储本身的问题,而是 HDFS 权限、认证和命令使用层面的基础功。但如果你不会看 fsck 的输出,那么任何存储策略的调整都像蒙着眼睛开车,所以宁可多花十分钟把工具用熟,也别等出了问题再临时抱佛脚。
5. 从命令到工程:毕业设计、面试与生产落地
5.1 毕业设计里如何设计一个分层存储方案
如果你正在做大数据方向的毕业设计,比如校园数据分析、网约车运营分析这类经典题目,分层存储是一个非常值得写进方案里的亮点。
设计思路不复杂。把数仓分层和数据温度对应起来:ODS 层存原始数据,保留最近一小段活跃窗口,设成 HOT 或 ONE_SSD;DWD 层做清洗后的明细数据,查询频率中等,设 WARM;DWS、ADS 层的汇总结果和过了活跃期的历史分区,直接设成 COLD。
在此基础上,再加一个每天定时执行的 mover 任务,就形成了一个完整的冷热数据生命周期闭环。写论文或者做答辩 PPT 时,可以重点展示三样东西:存储策略设置前后各层磁盘占用对比图、mover 调度脚本的代码片段、以及 fsck 验证输出截图。这几样东西比干巴巴讲概念有说服力得多。
如果还想再加一点工作量,可以做一个简单的分层存储可视化页面,用定时任务读取fsck或dfsadmin -report的结果,把各层数据量、存储类型分布展示成图表。这个功能既是练手的好素材,也是项目报告里的加分项。
5.2 面试官爱问的HDFS分层存储考点
面试场景里,谈到 HDFS,存储策略相关的问题出场率越来越高。我问过不少候选人,也帮人模拟过面试,常见的问题基本固定在这几个方向上:
- HDFS 有哪几种存储策略?副本分别怎么分布?
- 给一个目录设置了 COLD 策略,历史数据会立刻迁移吗?
- mover 和 balancer 有什么区别?
- LAZY_PERSIST 策略的写入流程是怎么样的,有哪些风险?
- DataNode 上没有 SSD 时,ALL_SSD 策略会发生什么?
这些问题背后的考察点其实很统一:你有没有真正理解“策略是逻辑标签、块分布是物理事实、搬迁是异步动作”这三层关系。回答的时候可以按照这个思路展开,先说策略包括首选存储类型和 fallback 列表,再强调策略打在目录上、新文件才生效,最后说明必须通过 mover 触发迁移。
比如一道经典追问:“ALL_SSD 设置后,如果集群没有足够 SSD 怎么办?”其实考察的就是 fallback 机制。答 “NameNode 会沿着 fallback 类型列表找,如果最终没有合适的存储类型,块写入就会失败或降级到其他类型” 就能抓住要点。面试官要的不是背概念,而是你有没有真正在集群上操作过、踩过坑。
5.3 生产环境落地前要提前规划的三件事
如果你准备在真实生产环境启用分层存储,动手之前先把这三件事规划好,否则后面会反复返工:
第一,目录规范。从建库建表开始就约定好哪些库属于热、温、冷,统一命名,比如_hot、_warm、_archive后缀。避免业务表建得到处都是,然后每个人凭感觉去 set 策略,最后策略体系乱成一锅粥。
第二,容量预算。统计全集群各类数据总量,按“热数据占快盘、温冷数据归档”的思路估算各层介质需要的容量。扩容不是按总数据量算,而是按“快盘只放该快的内容”来算,否则分层就失去了意义。
第三,监控手段。把每层存储类型的使用率、mover 执行时长、未合规块数量这几个指标接入监控。最简单的方式是每日跑一次 fsck 统计未合规块数,超过阈值就告警。没有监控的分层存储等于没有仪表盘的汽车,开出去心里没底。
另外还有一点提醒:不要手痒给 Yarn、Spark 拖到 HDFS 的临时目录设置奇怪的策略,那些中间结果用完就删,没有分层的必要。分层存储的投入要花在真正长期存在的业务数据上,别浪费在临时文件上。
我在实际运维中养成了一个比较土的习惯:每次设置完策略,一定先记下时间点,等 mover 跑完后再用 fsck 核对目标目录里块的存储类型分布,确认热点表和归档表的物理分布跟预期一致才会收工。这个习惯帮我抓住了好几次“策略看起来生效了、实际块都 fallback 到 DISK”的隐形问题。分层的价值不在那一行配置命令,而在你对数据温度的感知和对磁盘介质的合理安排上,多跑几次 fsck,多盯几轮 mover 日志,比看十篇原理文章都管用。