我遇到一种情况,需要创建数十万个 0 字节锁文件来进行并发控制。
我已经测试过使用以下方法创建它们:
for i in `seq 1 50000`; do touch "/run/lock/${i}.lock"; done
由于这些文件为 0 字节,因此它们不会占用分区中的任何空间。查看df -h
:
Filesystem Size Used Avail Use% Mounted on
tmpfs 50M 344K 49M 1% /run
none 5.0M 0 5.0M 0% /run/lock
none 246M 0 246M 0% /run/shm
none 100M 0 100M 0% /run/user
该行中的数字0%
根本没有变化/run/lock
。
但是,内存大小确实平均每个锁文件增加了大约 1KB。我通过比较free -h
在 中创建 70,000 个锁文件之前和之后的情况发现了这一点/run/lock
。这种内存增加反映在实际内存使用量中(虚拟内存减去缓冲区/缓存)。
后来我发现这 1KB 的增长很可能是由于 inode 造成的。因此我使用以下命令检查了 inode 使用情况df -i
:
Filesystem Inodes IUsed IFree IUse% Mounted on
tmpfs 62729 322 62407 1% /run
none 62729 50001 12728 80% /run/lock
none 62729 1 62728 1% /run/shm
none 62729 2 62727 1% /run/user
如您所见,锁文件增加了/run/lock
分区内的 inode。
我目前在 Ubuntu 上,但/run
安装没有反映在里面/etc/fstab
。运行后mount
显示:
tmpfs on /run type tmpfs (rw,noexec,nosuid,size=10%,mode=0755)
none on /run/lock type tmpfs (rw,noexec,nosuid,nodev,size=5242880)
none on /run/shm type tmpfs (rw,nosuid,nodev)
none on /run/user type tmpfs (rw,noexec,nosuid,nodev,size=104857600,mode=0755)
我对此有几个问题(但第一个是最重要的):
- 如何永久增加 inode 限制
/run/lock
?以便此限制在重启后仍然存在? - 对于我来说,创建自己的目录并在其上挂载 tmpfs 来用于此目的而不是使用 是否更好
/run/lock
? - 每个分区的大小限制是否完全独立?也就是说,存储文件
/run
似乎不会产生影响/run/lock
,反之亦然。 - 1KB 是从 inode 派生出来的吗?我注意到,在创建非空文件时,每个文件的基本块为 4KB。
- 为什么
/run
给出了文件系统类型,tmpfs
但给出了文件系统类型“无”,尤其是因为它们都由 TMPFS 支持?为什么它们不都像列中那样读取?/run/lock
/run/shm
/run/user
tmpfs
Filesystem
- 如果所有目录都受到独立约束,那么 OOM 杀手如何处理存在多个完整 TMPFS 分区的情况,每个分区的大小为 RAM 的 50%,并且还有进程也在争用 RAM。显然,不能使用超过 100% 的 RAM。根据https://www.kernel.org/doc/Documentation/filesystems/tmpfs.txt它提到系统将死锁。这是怎么回事?
答案1
按以下顺序回答您的一些问题:
- 您可以在应用程序启动脚本中使用
mount -o remount,nr_inodes=NUM /run/lock
(如果使用 uid=0 运行)。将相关行添加到/etc/fstab,但尚未测试。 - 分离在这里有一定意义,因为填满所有 inode 不会干扰系统的其余部分。
- 是的,完全独立。
- [...]
- 使用虚拟(基于非块设备的)文件系统,您可以将任何内容作为设备放入 mount 命令中,只有类型才是最重要的。
- [...]
不确定您的应用程序是否会通过打开它来创建空文件(以及持续多长时间),但您也可以考虑增加打开文件的限制(检查限制),以免耗尽。
答案2
你走错了方向。你可以使用文件系统语义来强制一致性。
当你想读取一个文件时,只需打开并读取它。你应该总是此操作使用
open
,从不access
。如果您使用 PHP 库来执行此操作,请检查它是否只调用open
而不是access
文件 - 但fopen
应该可以正常工作。当您想要刷新或创建新文件时,请执行以下操作:-
- 使用临时文件创建机制创建新文件。如果不存在,则创建一个不太可能存在的新文件名(filename.XXXXXX,其中 X 被替换为随机字符)。确保在 O_EXCL 中打开。
- 将相关数据写入文件。
- 将文件重命名为旧文件的名称。
这种操作是安全的,因为重命名被定义为原子操作。打开文件的读者将看到旧文件或新文件 - 但永远不会看到缓存中不存在的文件。
在最坏的情况下,由于对每个文件进行多次并发检查,许多写入者会短暂地互相覆盖。但这比对每个文件使用文件锁便宜得多。
或者,与其为每个文件设置一个锁定文件,不如考虑直接锁定每个单独的缓存对象。不过,我仍然认为这无法扩展。
在这种情况下使用rename
和link
语义可以保证与缓存的一致性,并且比锁定文件管理起来便宜得多。