2026-10-31·by 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 一定卡是为了不撒谎——告诉你写完了,数据就一定已经稳稳落在三个副本上。

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 →