DataNode 心跳、死亡、下线,和客户端的面孔
核心机制:DataNode 推,NameNode 不轮询。 每 3 秒每个 DataNode 发一次心跳:一个小包,只报活着、剩余空间、在途传输。NameNode 不问,只更新该节点的 last-seen 时间。各节点心跳各自计时,负载就摊开了,不会一齐涌来。NameNode 还在心跳回包里捎带命令:复制某块、删除某块。所以心跳同时也是命令通道。
判死故意判得慢。 只有 10 分 30 秒没有心跳,才判节点死亡。慢是有意的:GC 停顿、网络抖动不能触发假死,因为假死会引起对根本没丢的数据做无用重复制。忍一忍,比假报警引起的修复活便宜。
块位置另有来源。 心跳不说节点存了哪些块。位置知识来自块报告:注册时全量报告、块变化时增量报告、默认每 6 小时全量报告一次。两条通道分开,是这个设计能扩容的关键。
为什么能扩容,天花板在哪。 心跳包小,重的块清单又不频繁还分开,所以能撑到几千节点。天花板在账本,不在心跳。这里有两个术语点要钉死。第一,小的是心跳包,不是数据块:块是存的单位(128MB),包是传的单位,一块传输时本身也要切包。第二,块大反而帮扩容:每节点块数、块报告大小、账本条数都有界;块一小,这三样全炸。
计划下线:先拷走,再走人。 节点要下线维护时,管理员把它列入下线(exclude)名单并跑 hdfs dfsadmin -refreshNodes。NameNode 不再往它上面放新块,把它的旧块逐块拷走,副本在别处够了才标 Decommissioned。期间它继续心跳、继续可读,移出名单就 recommission。对比突发死亡:突发死亡是先判死、再紧急补;计划下线是先拷、再允许关机。
复制是逐块,不是整盘克隆。 下线节点的 1TB 是 8192 个 128MB 块,每块的目标另选,按心跳报的剩余空间、机架多样性、负载来挑。1GB 空闲的节点最多约 8 块,近满的直接跳过;30GB 约 200 多块;3TB 节点也绝不会包下全部 8192 块。NameNode 自己不搬数据,只通过心跳命令告诉目标从存活源拉取。有一个绝对前提:集群总空闲必须盖住这批数据,否则下线一直 In Progress。
卡住的下线是故意 fail-safe,不是 bug。 注意 refreshNodes 成功只表示名单被读到了,不是完成确认,所以空间不够时命令行不会立刻报错。节点会在 -report 或 Web UI 里一直显示 Decommission In Progress;块仍在原处、可读;新副本只有完整写好并上报才算数,所以不会悄悄丢数据。只有管理员在下线完成前强行关机才危险,那等于人为制造突发死亡。卡住是“停着但安全”,好过“谎称完成却丢数据”;腾出空间或加节点后自动续跑,取消下线即恢复。
为什么核心流程没有硬预检,尽管管理员可以自己算。 用户提的第三种办法——预先检查,因为 NameNode 知道每台剩余空间——原则上是对的,但核心流程没有“拒绝下线”的硬门禁,原因有二。NameNode 掌握的只是瞬间快照,不是未来:漫长的下线过程中,其他写入、删除、补副本、Balancer 都会改变空闲。而且它按块调度,不会为整单预留空间去对抗正常的写入竞争。所以事前算术是管理员和监控在动手前做的事,不是内置的拒绝检查。
动态空间与混部。 DataNode 每次心跳报总量、HDFS 已用、非 HDFS 已用、剩余,同机其他程序突然吃盘几秒内就能看见,NameNode 随即停放新块。防线:预留空间(dfs.datanode.du.reserved)在算剩余前先扣除;写时空间没了会失败并换节点重试;已落盘的块不会被别程序的用量挤走。实际结论:生产仍偏好专用机,因为混部让容量不可预测。
慢节点与异构 IOPS。 写流水线速度由最慢一跳决定,超时的节点被踢出、流水线重建。读到慢的最近副本时用 hedged read 缓解:短等之后向另一副本发同样的读,先回者胜。老实说局限:选址只看空间、机架、负载,不测 IOPS,慢节点只能运行时发现。生产用同构节点,或 SSD/HDD 分层来回答。
客户端的面孔。 用户的面孔是客户端,不是任何 DataNode。Shell 命令(hdfs dfs -ls、-put、-get、-cat)在任意装了 Hadoop 客户端、配置指向 NameNode 地址(如 hdfs://namenode:9000)的机器上跑。背后 DFSClient 把每条命令拆成到 NameNode 的元数据 RPC 和直连 DataNode 的数据传输。管理命令同样找 NameNode(hdfs dfsadmin -refreshNodes、-report);程序用 Java FileSystem API 或 WebHDFS,底层是同一套 DFSClient 逻辑;NameNode Web UI 只看不管数据。
突发死亡:本来就有 3 副本,所以是修复,不是丢数据。 每块已有 3 副本跨节点、跨机架。心跳停了,10.5 分钟后判死,账本里 3→2,读走另两个副本,NameNode 排队从存活副本补回 3,写中流水线排除死节点、不重写已 ack 的包。唯一无解的情形是 3 个副本位置同死(比如从未跨机架),或副本因子就是 1——那只能靠集群外备份。
补副本排队、节点归来、坏副本。 死节点之后,补副本按优先级排队、限速,不是一股脑全发:最缺副本的块先补,速率封顶,以免修复流量饿死客户端流量。节点回来会重注册,缺席期间多造的副本在账本对账后删掉。而客户端报的校验和不匹配的坏副本不计入副本因子:它被标坏,读落到好副本,并另外补一个好副本。
典型节点规模与下线时长——估算,不是披露数字。 早期 Hadoop 节点是 4–12 盘 ×1–2TB,每节点 8–24TB。现代大集群是 12–24 盘 ×10–18TB,每节点 120–400TB,实践常封顶 100–200TB(重建和下线时间随节点变大);云上小集群每虚机可以只有几 TB。时长只有一条公式:时间约等于待搬数据除以集群能并行腾出的复制带宽。粗算:1TB 节点、10 节点集群、聚合约 1GB/s,理论约 17 分钟、实际约 30–60 分钟;50TB、约 2GB/s,约 7 小时;200TB、约 5GB/s,约 11 小时。经验法则:100TB 量级加 10GbE 时,下线按小时计,不是分钟、也不是天。
弹性成员。 集群规模不是固定的。新 DataNode 配好 NameNode 地址一启动就注册、心跳、报块、立即接新块,无需重启 NameNode。但扩容驱动在外部——运维、云自动扩缩、Kubernetes。HDFS 自身从不自动建机。缩容是慢弹性,不是瞬时:块拷完才能走,1TB 节点比 10GB 节点慢得多。
复盘:心跳这条线到底在管什么。 把上面十五点收成一句话:心跳管的是“节点活着吗、还有多少空间、能接什么命令”,块报告管的是“节点存了什么”,两条线各司其职,合起来才构成 NameNode 对集群的实时认知。判死慢、补副本排队限速、下线卡住不谎报,都是同一个原则的不同表现:宁可慢、宁可停,也不要基于不完整信息动手,因为动手错了的代价是丢数据或白白复制,而等一等的代价只是时间。逐块复制而不是整盘克隆,是这个原则在下线场景的落地:每块单独选目标、单独确认,任何一块没拷好都不算数,整单的进度由最慢的那块决定。动态空间、慢节点、坏副本这些细节,讲的也都是同一件事:NameNode 的决策依据永远是各节点最近一次上报的快照,快照会过时、会失真,所以机制里处处留了纠错余地——失败重试、换副本重读、回来重注册、多余副本删除。最后看规模:几千个节点的心跳即使每秒上千包,每包只有几百字节,总量也不过几百 KB 到几 MB 每秒,真正压垮系统的是账本条数和块报告,而不是心跳本身。