TT Lab
开始
学习 学习路径 课程

Apache Hadoop — 在一个 Pod 里搭起并运维 HDFS 与 YARN

空间配额在写入前先预留一个完整数据块

在 TT Lab 中继续学习

一句话总结

HDFS 有两种配额。名称配额限制目录下文件和目录的数量,空间配额限制连副本一起计算的字节数。空间配额看的不是已经写入的量,而是写满一个新块所需的份额,因此会出现 1 字节的文件被 30MB 配额拦下的情况。

为什么需要配额

共享集群中有两类事故。一类是某个团队的作业失控,把磁盘写满;另一类是数百万个小文件蜂拥而至,让 NameNode 撑不住。前者是字节的问题,后者是数量的问题。我们在前面的模块中已经看到,每个文件、每个目录都是 NameNode 内存中的对象。即使磁盘再宽裕,名字太多时,最先倒下的也是 NameNode。

因此 HDFS 配额指南把两种配额分开设置。两者独立运作,但管理方式和实现是并行的。配额设置在目录上,适用于以该目录为根的整棵树。设置和解除只能由管理员完成。

工作原理

先说名称配额。名称配额是对树中文件名和目录名的数量设置的硬性上限。超过之后,创建文件或目录就会失败。指南中有一句值得留意:配额设为 1 时,该目录只能是空的,并且在括号里写明,目录自身也计入配额。所以配额为 10 的目录最多只能放 9 个文件。

还有几个性质。配额会随目录重命名而跟随,如果重命名会导致超出配额,这次重命名就会失败。对已经超出的目录,设置配额本身也会成功。新创建的目录没有配额。超出时客户端收到的异常是 NSQuotaExceededException。

空间配额会提前预留

空间配额是对树中文件所用的字节数设置的上限,块的每一个副本都计入配额。正如指南中的例子,副本系数为 3 的 1GB 数据会占用 3GB 配额。目录本身不占用空间,元数据占用的空间也不计算。修改副本系数,配额占用也会相应增减。

关键是这句话:如果配额不允许写满一个块,块分配就会失败。NameNode 无法提前知道文件实际会写入多少字节。发出块之后,客户端完全可能把这个块写满。所以在发出新块的那一刻,它会检查是否还剩下“块大小 × 副本系数”的空间。

为什么要这么保守?因为块沿着管道流动的过程中,最终大小并没有确定。如果写到一半发现超出配额就停下,就会留下写了一半的块和建了一半的文件。在发出块之前先确认最坏情况,拒绝就总是发生在字节流动之前、块的边界上。但也有代价:小文件同样要求很大的余量,同时写多个文件时,每个打开的块都需要各自的余量。这就是为什么剩余配额看起来很充裕,同时运行的作业一多就会突然被拦住。

向配额为 30MB 的目录写入 1 字节文件的两种情况。块为 128MB、副本数为 1 时,NameNode 在发出块之前要求剩余 128MB,超出 30MB 的配额线而被拒绝。把块缩小到 4MB 后,所需份额为 4MB,因此通过,关闭之后只有实际写入的 1 字节会留作使用量

hdfs-default.xml 中的 dfs.blocksize 默认为 134217728 字节(128MB)。即使副本系数为 1,30MB 配额的目录中也没有 128MB 的余量,所以内容只有 1 字节的文件也拿不到第一个块,会以 DSQuotaExceededException 失败。只要把这个文件的块大小缩小到 4MB,所需余量就变成 4MB,随即通过。文件关闭之后,留在配额中的使用量是实际写入的字节数乘以副本系数。提前预留只发生在写入期间。

hdfs dfsadmin -setSpaceQuota 30m /team/etl
hdfs dfs -put one-byte.txt /team/etl/                         # DSQuotaExceededException
hdfs dfs -D dfs.blocksize=4m -put one-byte.txt /team/etl/     # 통과
hadoop fs -count -q -v /team/etl

缩小块大小也有下限。dfs.namenode.fs-limits.min-block-size 默认为 1MB,不能比它更小。指南中还有极端的情况:空间配额为 0 时允许创建文件,但不能给该文件附加任何块,也就是说只能创建空文件。

如何读取配额

文件系统 Shell的 count -q 会输出八列,顺序是 QUOTA、REMAINING_QUOTA、SPACE_QUOTA、REMAINING_SPACE_QUOTA、DIR_COUNT、FILE_COUNT、CONTENT_SIZE、PATHNAME。没有配额时,配额一栏显示 none,剩余一栏显示 inf。这里的 CONTENT_SIZE 是复制之前的大小,而剩余空间配额是从连副本一起计算的值中扣减的。把两个数字混在一起计算,就会差出副本系数的倍数。加上 -h 会用便于阅读的单位,加上 -v 则会连同表头一起输出。

配额保存在 fsimage 中,重启后依然存在,每次设置或清除都会记入编辑日志。解除用 -clrQuota 和 -clrSpaceQuota。也有按存储介质类型(SSD、DISK 等)分别设置的配额,但只有在使用存储策略的目录上才有意义。

在现场相遇的样子

第一,配额还剩很多,却写不了一个文件。当剩余空间配额小于块大小 × 副本系数时,就会这样。128MB 的块、副本系数为 3,剩余配额一旦低于 384MB,就拿不到新块。设定配额时,要把这份余量算进去。

第二,小文件很多的团队,更紧迫的是名称配额。只设置空间配额的话,一百万个 1KB 的文件照样能写进来。保护 NameNode 的是名称配额。

第三,回收站也会占用配额。正如设计文档所述,每个用户的回收站位于主目录下的 .Trash,所以如果给主目录设置了配额,被删除的文件在回收站中时会一直占用这份配额。这就是 shell 文档说明 -skipTrash 在需要从超出配额的目录中删除文件时很有用的原因。

第四,配额连重命名也会拦。想把大目录移到设有配额的地方而失败,属于正常行为。

第五,已经删除了,数字却没变。根据 shell 文档,除非指定 -x,count 会把该路径下快照中留下的条目也全部计入。被快照抓住的文件,即使从当前树中删除,也不会立刻从统计中消失。快照会在下一个模块中讲解。

实际工作中真正重要的事

下一项实验要做什么

给目录设置名称配额 10,逐个上传小文件,数出第几个被拦下,确认目录自身占了一个名额。接着设置 30MB 的空间配额,重现 1 字节文件因默认块大小的预留而被拒绝的错误,再以 1MiB 的块大小上传同一个文件,看它顺利写入。读懂 count -q 的各列,解读两个目录的剩余配额,并确认:想把没有配额的目录里的文件移入已满的目录时,连重命名也会被拒绝。在报告中用数字写下实际写入的文件数,以及 1 字节文件试图预留的字节数。