2026-10-31·von Sijie Wang

writer-lease

Single-writer lease, renewal thread distinct from DataNode heartbeats, soft limit 60 s, hard limit 1 h, row-lock analogy, and half-async writes with backpressure.

写 lease:每文件一个写者,和两条不同的心跳

动机:单写多读,而数据绕过 NameNode。 HDFS 文件是一个写者、多个读者。数据从客户端直连 DataNode,NameNode 看不见写的过程。没有排他令牌的话,两个写者会交错写同一文件,末块的长度和内容就没法定了:谁的字节算数,块到底多长?

五个步骤。 第一,客户端向 NameNode 申请建文件或追加,NameNode 查无人持有后授 lease,记下持有者和上次续租时间。第二,客户端要块,NameNode 分配块和 DataNode 流水线。第三,数据直连 DataNode,块满了再要下一块。第四,写者持续续租。第五,关闭释放 lease。

关键区分:两条线,不是一条心跳。 DataNode 心跳每 3 秒,只证明存储节点活着,对集群外的写客户端什么都证明不了。客户端因此另有续租线程:租期过半就续租。把这两条线混为一条,是这个机制最常见的误解。

过期分两档。 软限 60 秒(LEASE_SOFTLIMIT_PERIOD = 60 × 1000):过期未续,另一客户端可以抢 lease 并跑 lease recovery,把未完成的末块收尾成一致长度。硬限 1 小时(软限的 60 倍):过期则 HDFS 认定客户端已退出,代它关文件并回收 lease。lease-manager 监控线程约每 2 秒检查一次。手动触发是 hdfs debug recoverLease。而 lease 从不挡读:读者不受谁持有写 lease 的限制。

集群视角:行锁,不是库锁。 lease 是按文件分的,不是按集群分的。100 个应用写 100 个不同文件,就是 100 个 lease 并行,各有自己的流水线。只有写同一文件的才排队。所以 HDFS 的活儿通常拆成每任务一个 part 文件,而不是很多进程往同一个文件里挤。而一个写者卡在 ack 上,也只卡自己那条流水线:不卡别的写者,不卡 NameNode——NameNode 本来就不搬数据。

写是不是阻塞:半异步、带背压。 write() 只把数据拷进包缓冲,满 64KB 一包后由后台 DataStreamer 线程发,应用线程不等单包 ack 就能继续填下一包。但有两个地方会阻塞:缓冲或待 ack 队列满时,写线程停下等——这就是背压,慢副本通过它拖住写者。而 close() 和 flush()(hflush/hsync)一定等到全部 ack 回来才返回,不会让调用方以为写完了其实数据还没落稳。流水线中途断裂时,写会卡一下,等重建流水线、重发未 ack 的包。

复盘:这套机制为什么非这样不可。 先回到最根本的矛盾:数据不经过 NameNode,所以 NameNode 管不了写的过程,只能发令牌。令牌必须排他,否则末块定不下来;令牌必须续租,否则写者死掉后文件永远打不开;续租必须和 DataNode 心跳分开,因为心跳证明的是盘,续租证明的是人。过期分两档也不是随意定的:软限给后来者一个抢跑的机会,让文件尽快恢复可写;硬限给系统一个兜底,让死掉的写者不会永远占着文件。监控线程每 2 秒扫一遍,保证这两档不是纸面规则,而是真的会被执行。行锁的类比到这里也收得拢:锁的粒度是文件,锁的时长是整个写会话,锁挡的只是另一个写者,不挡读者,不挡集群里其他文件的写者。写路径的半异步同样是权衡:平时不卡是为了吞吐,close 和 flush 一定卡是为了不撒谎——告诉你写完了,数据就一定已经稳稳落在三个副本上。

über diesen eintrag

Einer von sijie's Wiki-Einträgen. Die KI auf dieser Site ist im selben Corpus verankert und antwortet in sijie's Stimme mit Zitaten zurück auf Einträge wie diesen — das Beantworten kostet sijie Geld, daher wartet sie hinter einem Code: zugriffscode eingeben →