做大数据这些年,我越来越觉得很多团队对 HDFS 的理解停留在“能存、能读”的层面。数据量小的时候无所谓,一旦集群上了规模、单日新增几个 TB 甚至几十 TB,冷数据热数据全混在一起,成本和性能的矛盾就会越来越尖锐。今天要聊的 HDFS 数据分层存储策略,就是解决这个矛盾的一把钥匙。它不复杂,也不需要额外引入一堆组件,但对集群的稳定性、成本和访问效率的影响非常直接,适合每一个正在维护生产集群、或者准备设计存储方案的工程师参考。
1. 为什么数据分层存储是大数据团队的刚需
1.1 冷热数据混存的代价
先讲一个我见过的真实场景。某个团队的 HDFS 集群有 30 个数据节点,清一色机械硬盘,总共大概 2PB 容量。他们的业务很典型:每天从业务库同步增量数据,落一份原始日志到 HDFS,然后用 Hive 跑离线分析,最后把结果同步到 MySQL 或者 Redis。集群跑了一年以后,问题开始一点点冒出来。
首先是成本。他们为了支撑每天的写入和近期的分析查询,所有节点都买的 4TB 大容量机械盘,单节点存储成本不算高,但架不住总量大。更头疼的是,很多数据三个月前生成之后就再也没有被读过,这些数据占据了超过六成的容量,却仍然和热数据一样占着同等“贵”的存储资源。其次是性能。因为所有数据都在同一个存储池里,当集群里面有大批任务在扫描历史数据时,磁盘 IO 会被大量占用,直接拖慢当天新增数据的写入和查询速度。这就是典型的冷热数据相互干扰。
这个问题本质上不是“磁盘不够用”,而是数据在生命周期不同阶段的需求不同。新鲜的数据需要高频读写,用性能好的存储介质才划算;老化之后的数据只求“放着别丢、偶尔翻出来用”,用低成本的大容量介质才是正解。如果不做区分,集群就是在不断地用高成本换取低收益。
1.2 分层存储到底解决什么
HDFS 分层存储的核心思路,是把不同访问频率的数据放到不同性能等级的存储介质上。这个思路在传统存储领域其实早有应用,比如存储阵列里的缓存层、性能层、容量层,只是 HDFS 把它下沉到了分布式文件系统层面,让数据块可以在节点之间、不同介质之间按策略自动流转。
具体来说,它解决三件事。
第一件事是成本优化。把访问频率低的数据转移到 ARCHIVE 归档盘或者大容量低成本磁盘上,把热数据保留在 SSD 甚至内存盘上,同样的容量预算,能装下的数据量完全不同。第二件事是性能隔离。热数据的读写请求被限制在高速介质上,不会被冷数据的大量扫描拖累。第三件事是数据生命周期的自动化管理。不需要人为定期去判断“这份数据该不该删”,而是靠存储策略和数据块搬运机制,自动把数据放到合适的位置。
从团队协作的角度看,分层存储还有一个额外的好处:它把“存储规划”这件事从拍脑袋变成了可配置、可审计的工程实践。数据落在什么介质上、什么时候触发迁移、迁移到哪个层级,都有明确的规则和记录,出了问题也能追溯。
2. HDFS 分层存储的核心机制解析
2.1 四种存储类型到底怎么选
HDFS 的存储类型分为四种:RAM_DISK(内存盘)、SSD(固态硬盘)、DISK(普通机械硬盘)和 ARCHIVE(归档存储)。这个概念比较好理解,但很多人容易忽略一个关键点:这些类型并不是 HDFS 自己定义的虚拟概念,而是需要你在数据节点的配置里,把实际挂载的磁盘目录“声明”为对应的存储类型。
举个例子,一台数据节点上有两块 SSD、三块机械硬盘。你可以在hdfs-site.xml里通过dfs.datanode.data.dir指定目录,并用[SSD]、[DISK]这样的标记来声明目录类型。类似这样:
<property> <name>dfs.datanode.data.dir</name> <value>[DISK]file:///data/hdfs/disk1,[DISK]file:///data/hdfs/disk2,[SSD]file:///data/hdfs/ssd1,[SSD]file:///data/hdfs/ssd2</value> </property>这里有个注意点:一个物理磁盘上的多个分区,最好不要混标成不同类型,否则 DataNode 在做块放置时可能会把同一块物理盘既当 SSD 又当 DISK,导致后续的迁移判断失真。另外,ARCHIVE 类型通常对应的是专门的冷存储节点,可以是低成本的大容量机械盘,甚至是可拔插的归档介质。生产环境里最常见的是 DISK 和 ARCHIVE 的组合,SSD 则根据预算和热点数据的规模酌情配置。
RAM_DISK 在常规生产环境里用得不多,因为它本质上依赖 DataNode 进程所在的机器内存,一旦节点重启,数据就没了。除非你的业务对延迟极其敏感、并且有完善的副本机制兜底,否则不建议把重要数据放在这一层。
2.2 存储策略与数据块放置的逻辑
有了存储类型,还需要定义“哪类数据放哪层”。HDFS 提供了若干预定义的存储策略,比如HOT、COLD、WARM、ALL_SSD、LAZY_PERSIST等。每个策略包含两部分:创建文件和写入数据块时使用的初始存储类型,以及数据块被复制或迁移时希望达到的存储类型列表。
以最常用的HOT策略为例,它要求所有副本都放在DISK上;COLD策略则要求所有副本放在ARCHIVE上;WARM策略比较灵活,允许一个副本在DISK、其余副本在ARCHIVE。这套机制的关键在于,策略是文件或目录级别的属性,可以随时调整。你今天把一个目录设成HOT,明天改成COLD,系统不会立刻把文件挪走,而是等下一次数据块复制或迁移任务触发时,再按新策略执行。
这就引申出一个很多人踩过的坑:改了目录策略之后,发现数据并没有马上移动,于是怀疑功能没生效。实际上,HDFS 的策略生效是“异步”的,必须运行数据迁移工具,或者等待 DataNode 后台的复制线程,才会真正把块从 DISK 搬到 ARCHIVE。理解这一点,你在排查问题的时候就不会白着急。
2.3 分层存储与传统存储分层有什么不一样
有人会问,这不就是存储阵列里的自动分层吗?确实有相似之处,但 HDFS 的分层有几个显著差异。
第一,HDFS 的分层是文件系统级别的,而不是块设备级别的。它对上层应用透明,Hive、Spark、Flink 这些组件完全无感知,不需要改 SQL 或业务代码。第二,HDFS 的副本机制让“数据温度”的判断可以更灵活。同一份数据可能有多个副本,你可以让一个副本留在高速介质上服务实时查询,其他副本放到归档介质上节约成本,这在传统存储里很难做到。第三,HDFS 的数据迁移是显式触发的,你可以控制迁移窗口,避开业务高峰。
理解了这些机制,再去看配置和操作,思路会清晰很多。
3. 实操:HDFS 数据分层存储的配置与管理
3.1 环境版本要求和前置检查
先说版本。分层存储功能在 Hadoop 2.6.0 之后逐步成熟,到 2.7.x、3.x 已经比较稳定。我个人的建议是生产环境至少用 Hadoop 3.x,除了分层存储本身,副本策略、纠删码等功能也更完善,操作系统的兼容性也更好。
动手之前,先做几个前置检查:
- 确认所有 DataNode 节点的磁盘挂载信息,确定哪些目录是 SSD、哪些是 DISK、哪些可以作为 ARCHIVE。
- 检查
hdfs-site.xml里的dfs.datanode.data.dir配置是否正确,最好在滚动重启 DataNode 之前就把目录类型标记好。 - 确认 NameNode 的 HA 状态正常,避免在配置期间发生主备切换。
- 确认有足够的临时空间存放迁移过程中的中间副本,某些迁移操作会触发复制,磁盘空间不够会导致任务失败。
这里我有一个经验:千万不要在业务高峰期刚配置完就立刻跑全量迁移。最好先在测试目录上验证策略生效,再逐步扩展到正式数据。
3.2 一步步完成存储类型声明
假设我们有三个 DataNode 节点,每个节点挂载了两块 1TB SSD 和四块 4TB 机械盘。我们的目标是:SSD 用于热数据,机械盘中的两块用于常规 DISK 数据,另外两块用于 ARCHIVE 归档数据。
在每台 DataNode 的hdfs-site.xml里修改dfs.datanode.data.dir,大致如下:
<property> <name>dfs.datanode.data.dir</name> <value> [SSD]file:///data/ssd1, [SSD]file:///data/ssd2, [DISK]file:///data/disk1, [DISK]file:///data/disk2, [ARCHIVE]file:///data/archive1, [ARCHIVE]file:///data/archive2 </value> </property>修改完成后,需要滚动重启 DataNode 使配置生效。重启完成后,用hdfs dfsadmin -report查看节点信息。如果配置正确,每个 DataNode 的 Storage 信息里应该能分别看到 SSD、DISK、ARCHIVE 的容量统计。这一步是很多问题的源头,如果你发现某个目录没有被识别成预期的类型,优先检查路径权限、磁盘挂载方式以及 XML 标签是否写错。
3.3 创建和设置存储策略
存储策略在 NameNode 上管理。系统自带了几种预定义策略,但生产环境有时候需要根据业务自定义。比如,我们需要一个“双副本,一个在 SSD,一个在 DISK”的策略,可以这样创建:
hdfs storagepolicies -createPolicy -policyName TWO_COPY_SSD_DISK -storageTypes SSD,DISK -replication 2创建完成后,把这个策略应用到某个目录:
hdfs storagepolicies -setStoragePolicy -path /user/warehouse/ads -policy TWO_COPY_SSD_DISK这里要注意,-setStoragePolicy设置的路径可以是目录,也可以是文件。对目录设置后,新创建的文件默认继承该策略,已有文件不会自动迁移,需要靠后续的迁移任务处理。
查看某个路径当前的策略:
hdfs storagepolicies -getStoragePolicy -path /user/warehouse/ads查看集群所有策略:
hdfs storagepolicies -listPolicies关于自定义策略,我补充一个实操细节:策略名称不要用太随意的命名,建议包含存储层信息和副本数信息,比如WARM_1D_1A表示一个 DISK 副本加一个 ARCHIVE 副本。命名规范在后期运维时非常有用,不然满屏的policy1、policy2,没人分得清谁是谁。
3.4 用 Mover 让数据“动”起来
策略设置好之后,重头戏是数据迁移。HDFS 自带的迁移工具叫mover,它有两种工作模式。
第一种是全量模式,会扫描所有策略不匹配的块并尝试迁移:
hdfs mover -p /path/to/check第二种是基于时间的模式,可以限制迁移任务在指定时间窗口内执行。生产环境我最推荐的方式是结合计划任务,在凌晨低峰期跑迁移。比如这样写一个 Cron 任务:
0 2 * * * hdfs mover -p /user/warehouse 2>&1 >> /var/log/hdfs/mover.logmover 执行过程中,会在 NameNode 上生成迁移计划,DataNode 之间开始复制和删除块。这里要注意,迁移过程会占用一定的网络带宽和磁盘 IO。所以,计划任务的时间窗口要预留足够的余量,避免和前一天的凌晨任务(比如全量导出)重合。
迁移完成后,可以用下面的命令验证块的分布是否符合预期:
hdfs fsck /user/warehouse/ads -files -blocks -locations这个命令会列出每个文件的所有块及所在节点,你可以在输出里看到DISK、ARCHIVE、SSD的分布比例,判断迁移是否执行到位。
4. 生产环境中的策略选型与最佳实践
4.1 典型场景:日志数据怎么分层
日志类数据是分层存储最典型的受益者。假设我们有一个日志平台,每天产生 10TB 的访问日志,保留 90 天。前 3 天的日志需要支持实时检索和排障,第 4 天到第 30 天的日志偶尔会被数据分析任务读取,第 31 天之后的日志基本只用来做历史审计。
在这个场景下,我的建议是这样配置:
| 数据范围 | 存储策略 | 存储层 | 原因 |
|---|---|---|---|
| 最近 3 天 | HOT(或 ALL_SSD) | SSD/DISK | 高频访问,读延迟敏感 |
| 第 4-30 天 | WARM | DISK + ARCHIVE | 低频访问,保留性能余量 |
| 第 31-90 天 | COLD | ARCHIVE | 仅审计需要,成本优先 |
具体操作是:把日志根目录按天建子目录,每天早上对超过 3 天的目录执行hdfs storagepolicies -setStoragePolicy改为WARM,对超过 30 天的目录改为COLD,然后再跑一次 mover。这个流程完全可以通过 Shell 脚本 + Cron 实现,不需要人工干预。
关键点:不要把策略生效的周期设置得太短。我遇到过有人每小时改一次策略、每小时跑一次 mover,结果集群一半的带宽都耗在搬数据上,业务任务全被拖慢。数据迁移的粒度,按天、甚至按周来控制就够了。
4.2 数据仓库表的分层策略设计
数仓场景比日志场景更复杂一点。ODS 层、DWD 层、ADS 层的访问模式和生命周期差异很大,不能一刀切。
ODS 层是原始数据,单表数据量最大,但分区一旦写入就很少被更新。我的习惯是:最近 7 天用HOT策略,超过 7 天的分区直接转COLD,不需要中间的WARM过渡。原因是 ODS 层基本只做一次性读取,偶尔的补数任务再单独恢复分区策略即可。
DWD 层是清洗后的明细数据,访问频率比 ODS 高,分析任务可能会反复扫描。建议最近 30 天用WARM,更早的数据转COLD。但要注意,如果业务上有针对历史分区的例行重跑任务,比如每月跑一次上个月的全量统计,那就要确保重跑任务的窗口内,相关分区的数据不会因为迁移而降低读取性能。
ADS 层是应用汇总数据,数据量小、访问频率极高。我倾向于把所有 ADS 表都设置为ALL_SSD策略。原因很简单:ADS 层的数据量通常只有几 GB 到几十 GB,占用不了多少 SSD 空间,但查询频率非常高,用 SSD 带来的收益非常明显。
4.3 和 Hive、Spark 等组件搭配的注意点
分层存储在组件层面的感知非常弱,Hive 里的表还是那张表,Spark 读文件还是那样读。但有三个细节值得留心。
第一,Hive 的COMPACTION(压缩合并)操作会产生新文件,新文件会继承所在目录的存储策略。如果你把表的某个分区改成COLD,后续的 compaction 可能会把合并后的文件也放到ARCHIVE上,这是正常行为,但如果你希望 compaction 后的文件回到HOT,就必须在任务前显式设置策略。
第二,Spark 的INSERT OVERWRITE或者TRUNCATE会删除旧目录并创建新目录,如果新目录没有继承到正确的存储策略,数据可能会落在默认的HOT策略下。所以,凡是跑完写任务,我建议都检查一下目标目录的策略是否符合预期。
第三,Flink 写 HDFS 时,如果使用了分桶或者分区目录结构,注意不要把策略设置在动态分区路径上。优先设置到稳定的父目录,比如/user/warehouse/ods.db/ods_log_di,避免每个分区临时去设置策略造成的额外开销。
5. 常见问题与排查技巧实录
5.1 异构存储节点配置后 DataNode 不识别
现象:修改dfs.datanode.data.dir后执行hdfs dfsadmin -report,存储类型没有变化,或者新目录始终显示为DISK。
排查思路:先看 DataNode 日志,确认配置加载是否正常。最常见的原因是 XML 格式写错了,比如中文字符、多余空格、标签未闭合。另外一个隐蔽原因:目录权限不对,DataNode 进程没有权限访问该目录,会直接把目录标记为“failed”,而不会报错退出。
解决:检查目录属主是否hdfs,执行chown -R hdfs:hadoop /data/archive1等操作修复权限,再重启 DataNode。
| 可能原因 | 判断方法 | 处理方法 |
|---|---|---|
| XML 标签错误 | 查看hdfs-site.xml是否合法 | 检查标签闭合和类型标记 |
| 目录权限不足 | ls -ld检查属主权限 | 递归修改属主 |
| DataNode 未滚动重启 | 查看启动时间 | 滚动重启 |
| 磁盘扇区异常 | dmesg查看 IO 错误 | 更换磁盘后重新挂载 |
5.2 数据迁移任务跑完但块没有搬走
现象:mover 任务正常结束,日志里也显示执行成功,但fsck查看块的分布,发现数据仍然在原来的存储层。
排查思路:这个问题的原因通常是策略没有正确设置到文件所在的目录。注意,setStoragePolicy的路径需要精确匹配文件的父目录。如果你对/user/warehouse/ads设置了策略,但文件实际路径是/user/warehouse/ads/2024/01/part-xxx,在 HDFS 的策略继承机制下,子目录默认继承父目录策略,但如果子目录本身被设置过其他策略,父目录的修改就不会生效。
解决:用hdfs storagepolicies -getStoragePolicy -path <子目录>查看子目录策略,将其改为目标策略或者删除子目录的自定义策略,然后再跑 mover。
另外一个常见情况是数据块有多个副本,其中某些副本所在节点不支持目标存储类型(比如集群里根本没有挂载 ARCHIVE 目录的节点),那么迁移任务会跳过不满足条件的块。这种情况需要检查集群是否真的规划好了归档节点。
5.3 mover 运行缓慢,占满带宽
现象:mover 运行期间,集群的写吞吐量下降明显,任务时间拉长。
原因:mover 默认会以较高并发进行块复制。集群规模小的时候,DataNode 之间复制数据和业务写入争抢带宽,影响会被放大。
解决:调整 mover 的限速参数。在hdfs-site.xml中配置:
<property> <name>dfs.datanode.balance.bandwidthPerSec</name> <value>10485760</value> </property>这里10485760表示 10MB/s,可以根据集群带宽酌情调整。另外,尽量把 mover 安排到业务低峰期,叠加限速配置,基本就不会对业务造成明显干扰。
实操心得:我通常会把带宽限制在集群总带宽的 20% 以内。比如万兆网卡的集群,单节点限速 50MB/s,这样既能让迁移在几个小时内完成,又不会拖垮正常任务。
5.4 策略设置后新文件落到错误存储层
现象:目录已经设置了COLD策略,但新写入的文件仍然落在DISK上。
排查思路:先确认客户端使用的 Hadoop 版本。老版本客户端不认识新的存储策略字段时,会按默认策略写数据,导致目录策略形同虚设。这个问题在混合版本集群中时有发生。
解决:统一客户端版本,至少保证和 NameNode 的大版本一致。如果短期无法升级,需要在写入方显式指定策略,或者通过定时任务对新增目录重新设置策略。
这个问题的另一个触发因素,是使用了不支持策略的写入工具,比如某些老版本的 Flume 或 Kafka Connect 插件。它们走的是 HDFS 的create接口但没有携带策略信息。遇到这种情况,只能靠事后修正策略和迁移来兜底。
写在最后的一点个人体会
分层存储这件事,看起来是一个技术功能,用起来却更像是一个存储策略管理的工程问题。我见过不少团队把精力花在选 SSD、调参数上,最后发现最大的收益其实来自“把冷数据及时降级”这件朴素的事。跑过几次大规模迁移之后,我个人最大的体会是:策略设计要留出余量,操作要尽可能自动化,但自动化之前一定要先在测试目录上跑通全流程,包括策略设置、迁移触发、结果验证。另外,每次调整策略和跑完 mover 后,我都会顺手执行一次hdfs dfsadmin -report和hdfs fsck -files -blocks -locations,把存储分布的变化记到运维文档里。做久了你会发现,这套流程真正帮你省下的不只是硬件成本,还有排障时花的那些冤枉时间。