做大数据的人,十有八九都在跟 HDFS 打交道。但说实话,很多人对 HDFS 的印象就是“能存文件、三副本、掉盘不丢数据”,再往深一点问——它是怎么保证写进去的数据不会半路丢、读出来不会读到一半的数据、NameNode 挂了以后整个集群为什么还能恢复?能讲清楚的人就不多了。HDFS 的数据一致性,是分布式存储最基础也最容易被忽略的一条线,不管你是做数据开发、平台运维还是架构设计,这块理解不到位,出了问题排查起来会非常痛苦。
这篇文章我把 HDFS 里和数据一致性相关的机制从头到尾捋一遍,包括写入链路怎么保证所有副本一致、NameNode 的元数据怎么保证不丢、快照和纠删码在一致性上有什么取舍,以及我自己在线上遇到过的一些坑和排查方法。内容会偏底层一些,但我会尽量用实际场景来讲,保证不是对着文档念概念。
1. 数据一致性在 HDFS 里到底指什么
1.1 分布式文件系统要面对的三个“一致”问题
先理解一个前提:HDFS 不是一个单机文件系统,数据要分散在多台机器上。单机环境下,写文件写到一半断电,重启后系统可以通过日志恢复原状,这是文件系统层面的一致性保证。但分布式环境下,数据分布在若干个 DataNode 上,同一个文件可能被拆成多个 Block 放在不同机器上,同一个 Block 又有多份副本放在不同的机架或节点上。这时候就引入了一致性的三个层面。
第一层是“写一致性”,说的是数据写入时,多个副本之间要保证同一份数据内容一致。HDFS 采用的策略是“写全部副本成功才算成功”,而不是像很多分布式系统那样先写主副本、异步复制到从副本。这种策略牺牲了一部分写入延迟,但换来一个最重要的特性:客户端从任意 DataNode 读取任意一个副本,拿到的数据都一样。
第二层是“元数据一致性”,说的是 NameNode 上记录的目录结构、文件块信息、副本位置信息必须一致且持久化。NameNode 不存文件数据,但它是整个集群的“索引表”,如果索引表挂了,数据就算都在,也找不回来。
第三层是“读一致性”,指的是客户端在读文件的过程中,不会因为某个副本损坏、某个 DataNode 宕机或者文件正在被写入,而读到部分数据或错误数据。HDFS 通过校验和、副本冗余和读时多副本选择来保证这一点。
这三层不是独立的。写路径上如果某个副本写失败了,就可能造成副本之间数据不一致;NameNode 的 EditLog 如果丢了一条记录,可能整个文件的 block 映射就对不上了;DataNode 上报的块信息如果和 NameNode 记录不一致,就会进入安全模式或者引发块复制风暴。
1.2 HDFS 的“最终一致”到底体现在哪里
很多人一上来就问:HDFS 是强一致还是最终一致?这个问题要分场景回答。对于读已提交的数据,也就是一个文件正常 close 之后,HDFS 是强一致的。因为写进 Block 的副本必须全部落盘并返回确认,NameNode 才会把 block 标记为已提交,客户端才能读到这个 block 的完整数据。
但文件正在写入的过程中,情况就复杂了。HDFS 允许客户端在文件处于 open 状态、还没有 close 的时候读取该文件的部分内容,这时候读到的是当前已写完的部分。如果此时你尝试读取,拿到的可能是部分数据,还没写完的后续内容读不到。这其实是流式读文件的常见语义,不是 bug,但如果你在上面跑一个强一致性的数据分析任务,就需要等文件 close 完成。
另一个典型的最终一致场景是 DataNode 宕机后副本的重建。假设一个 block 的三个副本分别在 A、B、C 三个节点上,A 节点宕机了,NameNode 会把这个 block 标记为 under-replicated,然后调度其他节点新增副本。但从宕机发生到新副本被复制完成,这中间有一段时间窗口,集群实际上处于“副本不满足预期数量”的中间态。这个过程中读取该 block 不会出错,因为 B、C 还有副本,但写新文件时可能会遇到 pending replication 的情况,NameNode 会等待副本复制完成再返回。
所以更准确地说,HDFS 对已提交数据是强一致的,对集群健康状态(副本数量、块状态)是最终一致的。理解这个区别,很多运维中的奇怪现象就能解释了。
1.3 一致性出问题,往往是“写入流程”先出问题
在我实际接触的集群故障里,直接触发数据不一致的很少是因为 HDFS 本身的 bug,更多是使用方式出了问题。比如多个客户端并发写同一个文件,没有拿租约;或者追加写一个已经 close 的文件;或者客户端写入过程中网络闪断,客户端重试逻辑处理不当。这些场景下,HDFS 的副本协调机制会尝试自愈,但如果用户代码的容忍度不高,最终表现就是读到了校验和不对的数据,或者文件大小与预期不符。
所以接下来我想重点拆一下写入路径。因为 HDFS 的一致性核心其实全在写路径上,读路径反而是相对简单的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 写入路径上的一致性保障机制
2.1 流水线写入:三个副本不是各写各的
HDFS 写文件的流程,网上一搜一大堆,我只讲跟一致性相关的关键点。客户端往 HDFS 写一个文件时,先调用 create 向 NameNode 发起请求,NameNode 在文件系统树里创建文件 inode,返回一个租约给客户端,然后客户端开始把数据切分成 packet,逐包发送。
第一个关键点是数据包的流向。HDFS 的写副本不是客户端把一个数据包分别发给三个 DataNode,而是客户端先发给第一个 DataNode,第一个DataNode收到后再转发给第二个,第二个转发给第三个,形成一条流水线(Pipeline)。这种设计的好处是减少客户端带宽压力,坏处是中间节点转发失败时,数据包在整条管道里的状态需要协调。
第二个关键点是确认机制。每个 DataNode 收到数据包并写入本地数据盘,同时生成校验和之后,会向上游返回一个 acknowledgment(ack)。只有整条 Pipeline 上所有 DataNode 都 ack 了一个 packet,客户端才能继续发下一个 packet。也就是说,一份数据如果任何一个副本没有写成功,这个包在严格意义上都是“未提交”的。
第三个关键点是 block 的提交。一个 block 的所有 packet 发送完后,客户端会调用 complete 通知 DataNode 将 block 标记为 finalized,DataNode 再通过 blockReceivedAndDeleted 上报给 NameNode。NameNode 只有收到至少一个副本的上报信息,才会把 block 状态改成 committed。对读一致性影响最大的是:NameNode 不会让客户端读到尚未 committed 的 block。
这三个机制合起来,保证了“同一时刻,一个 block 的所有副本要么内容完全相同,要么这个 block 根本不可读”。这比很多异步复制系统要稳得多。
2.2 租约机制:防止两个客户端同时写同一个文件
除了副本之间的数据一致性,还有一个语义上的问题:如果两个客户端同时往同一个文件里写数据,那文件内容到底算谁的?HDFS 的答案是:谁持有租约,谁才有资格写。
客户端 create 一个文件的时候,NameNode 会给客户端发放一个租约。租约里带一个过期时间,分为 soft limit 和 hard limit。soft limit 是 60 秒,hard limit 是 1 小时。客户端在写文件过程中需要周期性地续租,如果客户端 60 秒内没有续租,其他客户端就可以强制等待;hard limit 一到,NameNode 就会强制收回租约。
这种机制看起来简单,但实际上很容易踩坑。我在生产上遇到过这样的情况:某个作业因为网络问题与 NameNode 失联,客户端进程没死,但租约已经过期了。作业配置了自动重试,重新调用 create 却报 “lease mismatch” 或者 “file already exists” 之类的错误。这时候你不能直接删文件,因为文件里可能有别人正在读的数据,也不能盲目等 1 小时。正确的处理方式是用 hdfs debug recoverLease -path <path> -retries 3 手动触发租约恢复,让 NameNode 把另一个客户端的租约回收掉。
租约机制影响一致性的一面在于:任何写操作都必须在租约有效期内完成。如果一个文件在写入过程中租约过期,NameNode 会回收租约,这个文件会被标记为 lease mismatch。此时文件数据还在,但客户端已经无法继续写,必须 close 或者删除重建。这个状态下的文件如果被强制读取,可能读到不完整的数据块,所以数据一致性要求严格的任务,发现租约异常后最好让文件失败重跑,而不是硬着头皮读。
2.3 读路径上的遮羞布:校验和机制
前面说的机制都是防止“写坏”数据,但现实中磁盘也可能因为坏道、静默损坏、断电等原因产生位翻转。HDFS 对付这种问题靠的是 Checksum。
HDFS 在写入数据时,每个 Block 的每个 512 字节(默认 chunk size)都会计算一个 CRC32 校验和,存储在一个单独的 .meta 文件中。DataNode 在读取本地 block 时,会把数据按照 chunk 读出来,重新计算校验和,跟. meta 文件里的值对比。如果不一致,就说明这个 chunk 损坏了。
DataNode 发现校验和不匹配后,不会直接把坏数据返回给客户端,而是把该 block 标记为 corrupt,同时报告给 NameNode。NameNode 收到报告后会替换新的副本,安排另一个存有该 block 的节点复制一份副本到健康节点上。对客户端侧来说,如果读取时发现一个 block 校验失败,HDFS 会尝试从另一个副本读取。所以只要不是所有副本都损坏,读端通常感知不到数据损坏。
但校验和不是万能的。它只能在“读”的时候发现数据损坏,如果写的时候因为内存位翻转、网络传输丢包造成数据本身就是错的,并且校验和算法没有察觉(概率极低但存在),那 HDFS 会把“错误的数据”当作正确的数据存储和读取。另外,校验和本身也是存储介质上的一串字节,.meta 文件如果也损坏了,DataNode 会把它当作 block 损坏处理。所以校验和机制解决的是存储介质的偶发损坏,不是业务层面的数据正确性问题。
3. NameNode 元数据一致性:集群的“大脑”不能乱
3.1 EditLog 与 FsImage:元数据怎么持久化
NameNode 不存数据,但存的元数据比数据还重要。数据丢了还能从副本恢复,元数据丢了,整个集群都找不到数据的位置,跟数据全丢没区别。
NameNode 的元数据持久化靠两张文件:FsImage 和 EditLog。FsImage 是文件系统元数据在某个时间点的全量快照,EditLog 是所有修改操作的增量日志。每次写操作(创建文件、删除文件、修改权限等)都会往 EditLog 里追加一条记录。
这里的关键点是:NameNode 接到一个元数据变更请求后,会先把 EditLog 落盘,然后才把结果返回给客户端。也就是说,客户端收到的“写成功”信号,意味着这个变更已经写入持久化日志,而不是仅仅改了内存。
FsImage 和 EditLog 是周期性合并的。SecondaryNameNode / Standby NameNode 会定期从 active NameNode 拉取 EditLog,然后跟 FsImage 合并,生成新的 FsImage,再推回 active。这个过程是后台异步的,所以 active 节点上总会积攒一定量的 EditLog。如果 active NameNode 进程挂掉,重启时就会加载最近一次 FsImage,然后重放剩余的 EditLog,把元数据恢复到最后一刻。
这条机制要想不出问题,就需要保证两个前提:一是 EditLog 不能丢,二是 EditLog 不能乱。第一点靠本地磁盘 + JournalNode;第二点靠写操作的有序性。任何一个环节出错,NameNode 重启后元数据都可能不一致,严重时直接进 safe mode 不让你操作。
3.2 Quorum Journal Manager:多写少,避免单点脑裂
HDFS 高可用架构里,两个 NameNode 一主一备,active 节点写 EditLog 到 JournalNode,standby 节点从 JournalNode 读日志并实时重放,保持元数据同步。那 JournalNode 本身如果挂了一台怎么办?
HDFS 采用 Quorum 机制,JournalNode 通常是 3 台或 5 台。active NameNode 每次写 EditLog,必须成功写入多数派(例如 3 台里写 2 台)才认为成功。这个设计说白了跟 ZooKeeper 一样,用多数派避免单点故障。
这里有一个值得讲清楚的点:Quorum Journal Manager(QJM)为什么能防止“脑裂”?因为两个 NameNode 同时认为自己是 active 的场景下,如果都往 JournalNode 写日志,而 QJM 只认多数派,就会导致一部分节点被污染。所以 HDFS 在故障转移时引入了 fencing 机制:新 active 上任前,会先尝试让旧 active 自己主动退位,或者通过隔离手段(比如杀掉进程)确保旧 active 不再写入日志。QJM 内部还会记录 epoch 号,每个新的 active 都会用新的 epoch,JournalNode 会拒绝低 epoch 的写请求。通俗点说,就是靠“辈分”来压制上一任 leader 的写操作。
这个机制在正常运维中,你只要记住一条经验:不要手动 kill 掉 active NameNode 后又马上手动触发 failover,一定要先确认旧节点已经不再写 EditLog。否则一旦出现两个 active 都尝试写日志,虽然 QJM 会拒绝低 epoch 的请求,但你可能已经看到一堆 “Epoch mismatch” 的错误日志,吓得以为集群废了。
3.3 安全模式:元数据不一致时,先把门关上
NameNode 启动或故障恢复时,如果发现数据块的副本情况与元数据不一致,比如大量 block 处于 missing 状态,或者 DataNode 上报的 block 信息与 FsImage 对不上,NameNode 会进入安全模式(Safe Mode)。
安全模式下,文件系统对外只读不接受写操作,NameNode 持续等待 DataNode 上报 block report,同时将这些上报信息和 FsImage 里的元数据对比。当上报的 block 比例达到阈值(默认是 99.9% 的 block 满足最小副本数),NameNode 才会自动退出安全模式。
实操中,如果集群的 dfs.namenode.safemode.threshold-pct 设置得比较高,而集群里有大量损坏的块,NameNode 可能迟迟不退出 safe mode。这时候不能简单执行 hdfs dfsadmin -safemode leave 强制退出,因为元数据本身可能不一致,强退只会让客户端读到更混乱的数据。正确做法是先跑 hdfs fsck 检查 missing 和 corrupt block 的数量,搞清楚原因,恢复副本,再让集群自然退出安全模式。
4. 快照、纠删码与存储层的一致性设计
4.1 快照:复制成本为零的“时间冻结”
HDFS 的快照机制经常被人忽视,但它其实是数据一致性在时间维度上的一个重要保障。创建快照时,HDFS 并不会拷贝任何数据块,它只是在 NameNode 的元数据层把当前 inode 和 block 映射关系记录下来,形成一个只读视图。
文件后续被修改或删除时,HDFS 会检查文件是否在某个快照里被引用。如果被引用,就保留旧版本的数据块,新写入的数据块另建,数据块本身在 HDFS 层面是 immutable 的(写完之后不能改),天然适合这种“写时复制”的设计。
从一致性角度讲,快照能解决一个很尴尬的问题:当你需要做数据恢复,或者对比不同时间点的数据时,不需要停写、不需要做全量拷贝,只需要创建一个快照,然后去读快照里的旧数据。比如有人误删了一个业务表目录,如果你提前建了目录级快照,可以直接从快照路径 .snapshot/snapshot_name/table_name 里把数据捞回来,比从磁带恢复或者从其他集群同步数据靠谱得多。
一个重要的经验是:快照不是用来替代备份的,它的价值在于“快速回滚”和“低成本挂载”。如果没有快照,误删数据后唯一能做的就是找备份或者忍受损失。我在实际工作中对核心业务目录都会默认开启快照功能,并设置周期快照策略,这算是成本最低的容灾手段。
4.2 纠删码:存储效率提升后,读一致性的代价变了
HDFS 3.0 之后开始支持纠删码(EC),默认策略是 RS-6-3,意思是把数据分成 6 个数据块,额外生成 3 个校验块,总共 9 个块,允许任意 3 个块丢失。相比三副本 3 倍的存储开销,纠删码的存储利用率可以达到 1.4 倍左右,省了很多磁盘。
但是纠删码在数据一致性上带来一个容易被忽略的问题:副本模式下,任何一个副本都是完整数据,读文件时客户端可以就近读任意一个副本,损坏一个副本从其他副本读就行。纠删码模式下,一个文件的数据被分散到多个块里,任何一个块损坏,恢复数据都需要读 6 个数据块,通过矩阵运算重建。这意味着读取时如果有块损坏,延迟会明显增加,而且重建过程对 CPU 有额外消耗。
另外,纠删码对于小文件很不友好。创建小文件会“浪费”校验块的存储成本,因为每个文件都要独立分布,不会跟其他文件共享校验块。这也是 HDFS 官方建议 EC 适用于大中型文件的原因。如果你的集群大量存的是几 KB 的小文件,用 EC 不仅省不了多少空间,反而可能比三副本占得更多。
从一致性维护角度看,EC 集群也需要定期跑 hdfs fsck 检查块的损坏情况。有一点要注意:fsck 对 EC 文件的检查结果跟副本文件不一样,命令里会显示类似于 ECPolicy=RS-6-3 的信息,看到 damaged block 的时候需要结合重建时间来判断是否需要人工介入,不能像副本文件那样默认有多个完整副本可以兜底。
4.3 副本不平衡:数据都还在,但一致性也讲究“分布合理”
数据一致性还有个容易被忽略的维度:副本在集群里的分布是否均匀。如果大量 block 的副本都集中在某个机架上,一旦这个机架断电,即使其他副本都在,读数据也需要跨机架访问,网络开销增大,数据恢复时间变长。从“可用性一致性”的角度看,这同样会让人感觉“集群不太正常”。
HDFS 有自动负载均衡机制,但默认情况下不是很激进。如果你往一个目录里传了一大批数据,而这批数据正好被分配到某些 DataNode 上,可能会造成热点。此时可以手动执行 hdfs balancer -threshold 10 做一次平衡迁移,系统会把副本从高负载节点迁移到低负载节点,这个过程不会影响正在写入的文件的可靠性,因为副本数始终保持在配置值。
但有一点必须注意:balancer 在运行时会占用网络带宽和磁盘 IO,如果集群正在跑重要的写入任务,最好限制 balancer 的带宽,比如 -Ddfs.datanode.balance.bandwidthPerSec=20971520 限制到 20MB/s。否则可能因为带宽抢占导致写入超时,反而引发副本复制任务异常。
5. 实操中常见的一致性故障排查与处理
5.1 写文件时报错:租约冲突和文件未关闭
写文件时的报错,常见的有这么几类:
Lease mismatch:说明文件被另一个客户端持有租约,当前客户端没有写权限。Recovering lease:NameNode 正在等待租约过期并恢复文件。File could not be opened for write:文件已经 close 或正在被读取。
出现这类错误,首先用如下命令查看文件当前状态:
bash复制hdfs debug recoverLease -path /user/data/important.dat -retries 3
这就是手动触发租约恢复。执行前最好确认没有活跃写入任务还在用这个文件,如果业务还在写,强制恢复租约会触发写入端失败。我在实战中的习惯是:先用 hdfs dfs -ls 看文件的 last modified 时间,再结合任务日志判断写入端是否还活着,然后决定是否执行这条命令。
文件处于“未关闭”状态还可能导致一种更隐蔽的现象:文件虽然能看到,但用 hdfs dfs -cat 读取时文件大小比任务日志里记录的要小。这是因为文件没有被正常 close,最后一个 block 没有被提交。这种情况不能靠 cat 判断数据完整性,要用 hdfs fsck /user/data/important.dat 看这个文件的 block 是不是 all blocks committed。
5.2 读文件时报错:checksum mismatch 和 got error 128
读取数据时最常见的错误是 checksum mismatch,以及网络传输过程中的 Got error 128。遇到这种情况,先把错误信息里的 block 找出来,然后执行 fsck 检查这个文件整体的健康状态:
bash复制hdfs fsck /user/data/important.dat -files -blocks -locations
如果 fsck 发现某个 block 损坏,但副本数量足够,HDFS 会尝试从另一个副本来读,客户端一般只是遇到一次短暂的重试。如果所有副本都损坏,你读这个文件必然失败。
修复方式要分情况:
- 如果文件数据是重要的,并且有上游重新生成的渠道,直接删除损坏文件重新生成,比手动去 restore 要快得多。
- 如果文件数据是唯一一份,损坏的部分无法从别处恢复,那只能用物理手段或者备份来恢复。
- 如果损坏的是副本但还有其他健康副本,NameNode 会自行挑选一个健康副本作为源,重新复制一份到新节点。你只需要检查集群是否处于安全模式或复制队列堆积。
这里有一个我踩过的坑: 某个 DataNode 磁盘出现坏道后,HDFS 标记了几个 block corrupt。但因为我长时间没跑 fsck,NameNode 没有及时发现损坏,直到数据读取任务报 checksum mismatch 时才暴露。所以我的建议是:定期(比如每个月)跑一次全集群的 hdfs fsck / -files -blocks -locations,把损坏块提前暴露出来,而不是等问题炸了再排查。
5.3 fsck 输出怎么看:健康状态速查
fsck 的输出信息量很大,很多人看到一堆 OK 就忽略掉了。我一般只看这几个关键指标:
Total blocks:集群总块数。Missing blocks:丢失块,如果在非维护期出现,必须立刻处理。Corrupt blocks:损坏块,说明至少有一个副本校验和不对。Under-replicated blocks:副本数低于 replication factor,需要等后台自动补副本。Misreplicated blocks:副本放置不符合机架策略,比如同一机架上副本过多。
下面是我常用的检查命令和评估思路:
bash复制hdfs fsck / -files -blocks -locations
关键看输出末尾的 summary 部分。healthy 文件数等于总文件数,说明文件层面没问题;如果 missing 或 corrupt 数量不为 0,就要逐个确认是否影响核心数据。有些块虽然 missing 但已经没有文件引用它(其实就是孤儿块),等待 NameNode 自动清理即可,不必惊慌。
修复副本不平衡可以用以下命令:
bash复制hdfs balancer -threshold 10
如果 balancer 长时间无法结束,可能是部分节点没有足够的空间来迁移,这时候要看 DataNode 的磁盘使用率差异,必要时先下线低空间节点,或者调整 dfs.datanode.du.reserved 预留空间。
5.4 集群重启后进入安全模式的常规处理
集群重启后 NameNode 进入 safe mode,在没有异常数据的情况下,通常等 DataNode 上报 block report 到阈值就会自动退出。但生产上有一种常见场景:安全模式一直不退出,查看日志发现大量 block 处于 pending deletion 或者 under-replicated。
这时候不要直接执行:
bash复制hdfs dfsadmin -safemode leave
而是先确认两个信息:一是 dfs.namenode.safemode.threshold-pct 的配置值,二是 fsck 检查出来的总体块状态。如果集群的 block 本来就因为数据节点大规模下线而大量 under-replicated,那么安全模式持续期间就会一直达不到阈值。正确做法是先确认下线节点是否还能恢复,不能恢复就双副本变单副本,等待 NameNode 重新调度复制任务,再观察安全模式状态。
6. 数据一致性运维的几条实在建议
6.1 不要只看“健康检查”绿了就放心
很多公司在 HDFS 上搭了一套监控面板,DataNode 全部 alive 就认为集群正常。但 HDFS 的一致性问题是慢性的、隐性的。DataNode 进程活着不代表它的磁盘没在老化,不代表它上面的副本没有在静默损坏。NameNode 的进程活着也不代表 FsImage 和 EditLog 的合并逻辑没有问题。
所以我的建议是,运维监控指标里至少要有这几个:安全模式状态、under-replicated blocks 数量、missing blocks 数量、fsck 执行频率、NameNode 的 EditLog 同步延迟。我见过很多集群,平时看着一切正常,一跑全量 fsck 才发现有几千个块处于 under-replicated 状态,这种情况就是日常监控缺失导致的。
6.2 定期演练“数据恢复”比搭建高可用更重要
高可用架构解决的是“进程挂掉”的问题,数据一致性解决的是“数据坏掉”的问题。很多人搭建了双 NameNode + JournalNode,就认为万无一失。但一旦出现误删文件、误改权限、数据被覆盖这类“人为故障”,高可用是不能帮你恢复数据的,唯一的希望是快照和备份。
我建议给核心数据目录开启快照,同时定期做一次“模拟误删恢复”演练。做法很简单:找一个测试目录,建一个快照,然后删除目录里的部分文件,再从 .snapshot 路径把文件捞回来。整个过程五分钟就能做完,但真到事故发生时,你对恢复路径的熟悉程度直接决定故障修复时间。
6.3 客户端代码要有一致性意识
最后一条其实是写给数据开发同学的。HDFS 客户端代码写得好不好,直接影响数据一致性。
写文件时要养成一个好的习惯:写完文件后主动调用 fs.close(),不要依赖进程退出自动 close。close 会触发租约释放和最后一个 block 的提交确认。如果写了不关,文件可能一直处于 open 状态,任务报失败,后续还会引发租约超时。
追加写时要注意并发。多个任务并发 append 同一个文件,很容易出现租约互踢,导致部分数据丢失。我见过一个比较典型的例子:两个定时任务同时往同一个 HDFS 路径追加数据,结果文件因为租约冲突处于不可读状态,最终只能删掉重跑。
覆盖写时也要留个心眼。HDFS 本身不支持对已存在文件做原地覆盖写,一般做法是先写临时文件,再 rename 成正式文件。这个 rename 是原子操作,能保证读端要么看到旧文件,要么看到新文件,不会出现读到一半新数据一半旧数据的情况。这个模式是分布式文件系统里非常经典的“原子提交”手段,值得在上游数据落地时普遍采用。
关于 HDFS 数据一致性,机制层面的东西其实不多,关键是理解每条机制是为了解决什么问题,然后在实践中发现问题时能快速对应到相应的机制上去排查。写这篇文章的初衷也就是把自己踩过坑之后才明白的这些东西整理出来,希望能帮你少走一些弯路。
