首页 / 资讯中心 / 文章详情

HDFS DataNode 核心机制与运维实战:从启动握手到故障排查

HDFS DataNode 核心机制与运维实战:从启动握手到故障排查 ★ FEATURED ARTICLE
1. DataNode 在 HDFS 架构中的真实定位很多人学 Hadoop 的时候NameNode 和 DataNode 的关系背得滚瓜烂熟什么“一个管元数据一个管数据”但真到了线上环境DataNode 出的问题往往是最要命的——磁盘写满、块丢失、副本数对不上、DataNode 频繁掉线这些才是运维和开发日常要面对的。我写这篇东西就是想把我自己踩过的坑和排查思路整理出来给正在学 Hadoop 或者刚接手 HDFS 集群的朋友一个参考。DataNode 说白了就是 HDFS 里的“搬砖工”。NameNode 告诉你这个文件切成哪些块、每个块放在哪几个 DataNode 上真正把数据写到磁盘、从磁盘读出来、定期向 NameNode 汇报“我还活着、我手上有哪些块”的全是 DataNode 干的活。一个中等规模的集群几十上百个 DataNode 是常态每个 DataNode 上跑着几百上千个数据块任何一个环节出问题轻则影响读写性能重则丢数据。这篇文章适合谁看如果你正在学 Hadoop课本上那套理论已经看腻了想知道真实环境里 DataNode 到底怎么运转的或者你刚接手一个 HDFS 集群遇到 DataNode 相关报错不知道怎么下手再或者你在做容量规划想搞清楚 DataNode 的存储目录、心跳机制、块汇报到底怎么回事——那这篇内容应该能帮到你。我会从架构设计讲到实操配置再到常见问题排查尽量把每个“为什么”都说清楚。2. DataNode 核心机制拆解从启动到服役的完整链路2.1 DataNode 启动流程与 NameNode 握手细节DataNode 启动不是简单跑个进程就完事了它跟 NameNode 之间有一套完整的握手协议。我第一次看源码的时候也觉得绕后来画了几遍流程图才理清楚。DataNode 进程启动后第一件事是加载本地存储目录里已有的数据块信息。这里有个关键点DataNode 会扫描dfs.datanode.data.dir配置的所有目录把每个目录下的块文件blk_xxx和元数据文件blk_xxx_xxx.meta读出来在内存里建一个副本映射表。这个过程叫“目录扫描”如果磁盘上块特别多启动会非常慢我见过一个跑了三年的集群单个 DataNode 上有 80 多万个块启动花了将近 20 分钟。扫描完成后DataNode 向 NameNode 发起注册请求也就是所谓的“握手”。握手的时候会带上自己的存储容量、已用空间、块数量等信息。NameNode 收到后把这个 DataNode 加入集群成员列表然后返回一个 Namespace ID 和集群 ID 做校验。如果两边 ID 对不上DataNode 会直接拒绝启动——这是为了防止把不同集群的 DataNode 误连到同一个 NameNode 上。注意如果你在测试环境频繁格式化 NameNode一定要把 DataNode 的存储目录也清干净否则集群 ID 不一致会导致 DataNode 起不来。这个坑我踩过不止一次。握手成功后DataNode 进入正常服务状态开始周期性地向 NameNode 发送心跳和块汇报。这里有个细节刚启动时 DataNode 会做一次全量块汇报把手上所有块的信息发给 NameNode之后就是增量汇报只报告新增、删除、损坏的块。全量汇报在集群规模大的时候会给 NameNode 带来不小压力所以生产环境一般会错开各个 DataNode 的启动时间。2.2 心跳机制与超时判定为什么你的 DataNode 老是被标记为 Dead心跳是 DataNode 和 NameNode 之间最基础的通信方式默认每 3 秒发一次。NameNode 如果连续一段时间收不到某个 DataNode 的心跳就会把它标记为“Dead”。这个超时时间怎么算公式是超时时间 2 * dfs.namenode.heartbeat.recheck-interval 10 * dfs.heartbeat.interval默认配置下dfs.namenode.heartbeat.recheck-interval是 5 分钟300000 毫秒dfs.heartbeat.interval是 3 秒所以超时时间是2 * 300 10 * 3 630秒也就是 10 分 30 秒。也就是说一个 DataNode 掉线后NameNode 要等大约 10 分半才会正式判定它死亡并开始副本恢复。这个时间能不能调短可以但不建议调得太激进。我试过把 recheck-interval 调到 1 分钟结果集群里偶尔的网络抖动就会触发大量 DataNode 被标记为 Dead然后 NameNode 疯狂复制副本网络带宽直接被拉满反而影响了正常读写。后来还是老老实实改回默认值。心跳包里除了“我还活着”这个信号还携带了 DataNode 的负载信息包括当前磁盘使用率、正在进行的读写操作数、剩余可用空间等。NameNode 会根据这些信息做负载均衡决策——比如某个 DataNode 磁盘快满了NameNode 就会减少往它上面分配新块。2.3 块汇报与增量汇报NameNode 怎么知道每个块在哪块汇报Block Report是 DataNode 向 NameNode 汇报自己手上有哪些块的过程。前面说了启动时是全量汇报之后是增量汇报。增量汇报的触发条件有几个新写入了一个块、删除了一个块、块损坏了、副本数变了。这里有个容易混淆的概念块汇报和心跳是两回事。心跳是“我还活着”块汇报是“我手上有这些块”。心跳频率高3 秒一次块汇报频率低默认每小时一次全量增量是实时触发。NameNode 收到块汇报后会更新内存里的块映射表这个表记录了每个块在哪些 DataNode 上有副本。为什么 NameNode 不把块位置持久化到磁盘因为块位置信息是可以从 DataNode 重建的。NameNode 启动时先加载 fsimage 和 edits log 恢复文件系统目录树和块的元数据比如块 ID、大小、副本数但具体每个块在哪个 DataNode 上是等 DataNode 启动后汇报上来的。这就是所谓的“块位置不持久化”设计好处是 NameNode 的磁盘压力小坏处是集群重启后要等所有 DataNode 汇报完才能对外服务。实操心得如果你发现 NameNode 重启后很长时间处于 Safemode安全模式大概率是因为 DataNode 还没汇报完块信息。这时候可以调大dfs.namenode.safemode.extension参数给 DataNode 更多时间。2.4 数据块存储格式blk 文件和 meta 文件到底存了什么DataNode 存储数据的最小单位是“块”每个块在磁盘上对应两个文件一个是数据文件blk_blockid一个是元数据文件blk_blockid_generationstamp.meta。数据文件存的是实际内容元数据文件存的是校验信息、版本号、块大小等。校验信息是干什么用的HDFS 默认使用 CRC32 校验每 512 字节数据算一个校验和存在 meta 文件里。读取数据时DataNode 会重新计算校验和跟 meta 文件里的对比如果不一致就说明数据损坏了。这时候 DataNode 会向 NameNode 报告“块损坏”NameNode 会从其他副本复制一份过来。我遇到过一种情况某台机器的磁盘出现坏道导致部分块文件读取时校验失败。NameNode 收到损坏报告后自动从其他副本恢复了数据但那个损坏的块文件还留在磁盘上需要手动清理。后来我写了个脚本定期扫描 DataNode 日志里的“corrupt”关键字发现有损坏块就自动触发清理。存储目录的结构大概是这样的/data/dfs/dn/current/ ├── BP-xxx-192.168.1.1-1234567890/ │ ├── current/ │ │ ├── blk_1073741825 │ │ ├── blk_1073741825_1001.meta │ │ ├── blk_1073741826 │ │ ├── blk_1073741826_1002.meta │ │ └── ... │ └── tmp/ └── ...其中BP-xxx是 Block Pool ID每个 NameNode 对应一个 Block Pool。如果是 HA 集群两个 NameNode 各有自己的 Block Pool IDDataNode 会同时服务两个 Block Pool。3. DataNode 关键配置与实操调优3.1 存储目录配置多磁盘挂载的正确姿势dfs.datanode.data.dir这个参数决定了 DataNode 把数据存在哪些目录下。生产环境一般会挂多块盘每块盘一个目录用逗号分隔property namedfs.datanode.data.dir/name value/data1/dfs/dn,/data2/dfs/dn,/data3/dfs/dn,/data4/dfs/dn/value /property这里有个关键点每个目录最好对应一块独立的物理磁盘。如果多块盘做了 RAID那写一个目录就行但如果是 JBODJust a Bunch Of Disks模式一定要每块盘单独配一个目录。为什么因为 DataNode 会轮询往各个目录写数据如果多块盘共用一个目录实际上还是写在同一块盘上起不到分散 IO 的作用。还有一个容易忽略的细节目录的权限。DataNode 进程通常以hdfs用户运行所以这些目录的属主必须是hdfs否则启动时会报权限错误。我见过有人用 root 创建了目录然后忘了改属主DataNode 一直起不来日志里就一行“Permission denied”排查了半天。注意如果你要新增磁盘直接把新目录加到dfs.datanode.data.dir里然后重启 DataNode 就行。DataNode 会自动识别新目录并开始往上面写数据。但已经存在的块不会自动迁移到新盘上需要手动做数据均衡。3.2 磁盘容量预留为什么你的 DataNode 明明有空间却写不进去DataNode 有个参数叫dfs.datanode.du.reserved默认是 0意思是磁盘空间全部可用。但生产环境一定要留一部分空间给非 HDFS 使用比如系统日志、临时文件等。一般建议预留 10% 到 20%。我遇到过一种情况某台 DataNode 的磁盘使用率到了 99%但 HDFS 还在往上面写数据结果系统日志写不进去整个机器卡死。后来查原因发现du.reserved没配DataNode 以为磁盘还有空间实际上已经被系统文件占满了。配置示例property namedfs.datanode.du.reserved/name value10737418240/value !-- 预留 10GB -- /property这个值要根据磁盘总容量来算。比如 4TB 的盘预留 10% 就是 400GB 左右。但也不能留太多否则浪费存储空间。我的经验是系统盘预留 20%数据盘预留 10%基本够用。3.3 数据传输线程数与带宽限制DataNode 处理读写请求的线程数由dfs.datanode.max.transfer.threads控制默认是 4096。这个值在集群规模大、并发读写高的时候可能需要调大。但也不是越大越好线程太多会导致上下文切换开销增加反而降低性能。我一般会观察 DataNode 的线程池使用情况如果经常接近上限就适当调大。调整的时候要同步考虑机器的 CPU 核数和内存大小一般建议不超过CPU核数 * 512。带宽限制方面dfs.datanode.balance.bandwidthPerSec控制数据均衡时每个 DataNode 可用的带宽默认是 10MB/s。这个值在集群空闲时可以调大加快均衡速度但在业务高峰期要调小避免影响正常读写。我通常会在凌晨低峰期临时调到 100MB/s早上再改回来。4. DataNode 常见故障与排查实录4.1 DataNode 频繁掉线从日志到根因的排查路径DataNode 频繁掉线是最常见的问题之一。表现是 NameNode 日志里不断出现“Datanode Registration”和“Datanode Decommission”记录或者 Web UI 上看到 DataNode 状态在 Live 和 Dead 之间反复横跳。排查思路我一般按这个顺序来先看 DataNode 自己的日志。日志路径通常在$HADOOP_HOME/logs/hadoop-hdfs-datanode-*.log。重点看有没有 OOM、GC 时间过长、磁盘 IO 错误、网络超时等关键字。检查系统资源。用top、free、iostat看 CPU、内存、磁盘 IO。如果 DataNode 进程的 GC 时间占比超过 20%说明内存不够需要调大堆内存。检查网络。用ping和telnet测试 DataNode 到 NameNode 的网络连通性和延迟。如果延迟超过 1 秒心跳就可能超时。检查磁盘。用df -h看磁盘使用率用dmesg看有没有磁盘硬件错误。我遇到过一次典型的掉线问题某台 DataNode 每隔十几分钟就掉线一次日志里全是“SocketTimeoutException”。查了半天发现是交换机端口有问题网络间歇性丢包。换了端口之后就好了。这种问题从软件层面很难查必须结合网络监控。4.2 块丢失与副本数不足NameNode 的自动恢复逻辑块丢失是指某个块的所有副本都不可用。NameNode 在块汇报时发现某个块的实际副本数小于配置的副本数就会触发副本复制。但如果所有副本都丢了NameNode 也无能为力只能标记为“缺失块”。副本数不足的原因通常有几个DataNode 磁盘故障导致块文件损坏、DataNode 被误删除、人为删除了块文件。我见过最离谱的一次是有人手动删了 DataNode 存储目录下的块文件以为 HDFS 会自动恢复结果 NameNode 直接报“Missing Blocks”整个文件读不了。提示如果发现块丢失第一时间用hdfs fsck /path -files -blocks -locations命令查看具体是哪些块、在哪些 DataNode 上。如果还有至少一个副本存活NameNode 会自动恢复如果全部丢失只能从备份恢复或者接受数据丢失。4.3 磁盘写满导致 DataNode 拒绝服务磁盘写满是另一个高频问题。DataNode 在磁盘使用率达到du.reserved阈值后会停止往该目录写数据但不会停止服务。如果所有目录都写满了DataNode 就会拒绝新的写请求客户端会收到“DiskOutOfSpaceException”。处理办法先清理临时文件和不必要的日志释放空间如果确实容量不够就加盘或者迁移数据。我一般会设置磁盘使用率告警到 80% 就通知到 90% 就紧急处理避免真的写满。4.4 常见问题速查表问题现象可能原因排查命令解决方案DataNode 频繁掉线网络抖动、GC 过长、磁盘 IO 阻塞ping、jstat -gc、iostat检查网络设备、调大堆内存、更换磁盘块丢失磁盘故障、误删除、副本未及时恢复hdfs fsck / -files -blocks从其他副本恢复、从备份恢复磁盘写满数据增长过快、预留空间不足df -h、du -sh清理临时文件、加盘、调大预留DataNode 启动失败集群 ID 不一致、目录权限错误查看 DataNode 日志清理存储目录、修改目录属主读写性能差磁盘 IO 瓶颈、网络带宽不足iostat、iftop增加磁盘、限制并发、调大带宽5. DataNode 运维中的经验与避坑指南5.1 磁盘故障后的数据恢复流程磁盘故障在规模化集群里是常态。一块盘坏了上面可能有几百个块受影响。正确的处理流程是先把故障盘从dfs.datanode.data.dir里移除重启 DataNode。这样 DataNode 就不会再往坏盘上写数据也不会因为读坏盘而卡住。NameNode 会检测到副本数不足自动从其他 DataNode 复制副本过来。这个过程是后台进行的不需要人工干预。等副本恢复完成后再更换物理磁盘把新盘挂载到原目录重新加入dfs.datanode.data.dir重启 DataNode。这里有个坑如果你直接换盘而不先移除目录DataNode 启动时会尝试读坏盘上的块可能导致启动超时甚至进程崩溃。所以顺序不能乱。5.2 扩容与缩容时的注意事项扩容就是加新的 DataNode这个比较简单配好环境、启动进程就行。但缩容下线 DataNode要小心。HDFS 提供了“退役”Decommission机制通过dfs.hosts.exclude文件指定要下线的节点NameNode 会把这个节点上的块逐步复制到其他节点复制完成后节点自动进入 Decommissioned 状态。退役过程可能很慢取决于数据量和网络带宽。我一般会提前几天开始退役避免影响业务。退役完成后一定要确认该节点上的块已经全部转移再关机下架。注意千万不要直接 kill 掉 DataNode 进程然后关机这样会导致大量块需要重新复制NameNode 压力骤增严重时可能引发集群雪崩。5.3 监控指标与告警配置建议DataNode 的监控指标我重点关注这几个心跳延迟超过 3 秒就要关注超过 30 秒就要告警。磁盘使用率80% 警告90% 严重告警。块汇报延迟超过 1 小时没汇报就要检查。JVM GC 时间Full GC 超过 5 秒就要调优。线程池使用率超过 80% 考虑调大线程数。这些指标可以通过 JMX 或者 Hadoop 自带的 Metrics 系统采集接入 Prometheus 或者 Zabbix 做可视化。我自己的做法是写了个简单的脚本每 5 分钟拉一次 DataNode 的 JMX 数据发现异常就发邮件告警。5.4 版本升级时的 DataNode 兼容性处理HDFS 的版本升级通常支持滚动升级也就是一个 DataNode 一个 DataNode 地升级不影响集群整体可用性。但要注意几点升级前确认新版本对旧版本的兼容性特别是块格式和通信协议。升级过程中 NameNode 和 DataNode 的版本可能不一致要确保 NameNode 先升级或者同时升级。升级完成后要观察一段时间确认没有块损坏或者副本异常。我经历过一次从 2.x 升级到 3.x 的过程整体还算顺利但升级后发现部分 DataNode 的磁盘使用率统计不准后来查出来是新版本的du.reserved计算方式有变化调整配置后恢复正常。5.5 个人实操体会DataNode 这东西理论看着简单真到运维层面全是细节。我的体会是配置要保守监控要到位故障处理要有预案。不要等到磁盘写满了才想起来加盘不要等到 DataNode 掉线了才去查网络不要等到块丢失了才去做备份。平时多看看日志多关注监控指标把问题消灭在萌芽状态比出了事再救火要轻松得多。另外DataNode 的很多参数都是跟硬件和业务场景强相关的别人的配置不一定适合你。最好的办法是先小规模测试观察一段时间再逐步推广到整个集群。我见过有人直接抄了网上的配置结果集群性能反而下降了就是因为没考虑自己的实际情况。最后再分享一个小技巧如果你不确定某个参数该设多少可以先设一个保守值然后通过监控观察效果再逐步调整。比如dfs.datanode.max.transfer.threads可以先保持默认的 4096如果发现线程池经常满再往上调。这样比一上来就设个很大的值要稳妥得多。
阅读完成 · 觉得有帮助?
咨询建站