2026-10-31·by Sijie Wang

datanode-heartbeat-and-death

DataNode heartbeats every 3 s, death after 10 min 30 s, block reports, planned decommission, per-block replication, hedged reads, sudden death, and elastic membership.

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 每秒,真正压垮系统的是账本条数和块报告,而不是心跳本身。

about this entry

One of sijie's wiki entries. The AI on this site is grounded in the same corpus and answers in sijie's voice, with citations back to entries like this one — answering costs sijie money, so it waits behind a code: enter an access code →

datanode-heartbeat-and-death — Sijie Wang