tmpfs 内存盘实战:把 Session、缓存与 /tmp 搬进内存给低配 VPS 提速,fstab 挂载与五个必踩的坑

为什么个人站长该认识 tmpfs:把高频读写目录搬进内存

很多草根站长的小服务器都是 1核1G 或者 2核2G 的便宜 VPS,磁盘往往是最廉价的 SATA SSD 甚至机械盘。网站跑久了会发现一个怪现象:CPU 和内存都还好,但页面生成、Session 读写、缓存文件一多,响应就肉眼可见地变慢。这时候问题多半出在磁盘 IO 上,而不是 CPU 算力不够。tmpfs 是一个被严重低估的工具——它让你把一部分内存挂载成"磁盘目录",读写速度是 SSD 的几十倍,而且是内核级的、零额外软件。

本文不堆砌概念,而是从一个真实的内容站场景出发:Typecho/WordPress 这类 PHP 站点,每次请求都要读写 Session 文件、编译模板缓存、写日志。这些文件有个共同特点——丢了也没关系,重启后重建即可。它们恰好是 tmpfs 的完美适用对象。

tmpfs 到底是什么,和 ramdisk 有什么本质区别

tmpfs 不是"把内存伪装成一块硬盘"这么简单。传统的 ramdisk(比如 /dev/ram0)会预先分配一整块固定内存,不管你用不用都占着;而 tmpfs 是**用到多少才占多少**,并且底层由内核的页缓存和 swap 共同管理。这意味着:

  • 你挂一个 512M 的 tmpfs,实际只写了 20M,那么只有 20M 内存被占用。
  • 内存紧张时,tmpfs 里不常访问的内容可以被交换到 swap,不会直接触发 OOM Killer。
  • 它天生支持所有 POSIX 文件操作:权限、软硬链接、mmap 全部可用,程序完全无感知。

查看已有的 tmpfs 挂载非常简单,很多系统默认就给 /dev/shm 挂了一个:

df -h -t tmpfs
mount | grep tmpfs

你会看到 /run、/dev/shm、/sys/fs/cgroup 这些系统目录本身就在 tmpfs 上——内核自己都在用,说明它足够稳。

第一步:选对要放进内存的目录

不是所有目录都适合 tmpfs。判断标准就三条:高频读写、体积可控、可重建。对个人站长来说,典型的候选是:

  • PHP Session 目录:/var/lib/php/sessions,每个访客一个文件,读写极其频繁。
  • 应用缓存目录:如 Typecho 的 /usr/cache、WordPress 的 wp-content/cache。
  • Nginx 的临时缓冲:proxy_temp_path、fastcgi_temp_path、client_body_temp_path。反代大流量时这几个目录写得很凶。
  • 临时上传/解压目录:/tmp,编译、打包、插件安装时用。

反过来,数据库文件、网站源码、用户上传的图片绝对不能放 tmpfs——一重启就全没了,那是事故。

第二步:动手挂载,用 fstab 保证开机生效

临时挂载可以先试水,确认没问题再写进 fstab。先看内存余量,别把 tmpfs 撑爆:

free -h

假设你有 2G 内存,日常占用 600M,那给 tmpfs 分 256M 是安全的。先手动挂一个 Session 目录试试:

# 备份原目录内容
mkdir -p /var/lib/php/sessions.tmp
cp -a /var/lib/php/sessions/. /var/lib/php/sessions.tmp/ 2>/dev/null

# 挂载 tmpfs
mount -t tmpfs -o size=256M,mode=1733,nosuid,nodev,noexec,noatime tmpfs /var/lib/php/sessions

df -h /var/lib/php/sessions
mount | grep sessions

参数逐个解释一下,这几个是安全关键:

  • size=256M:硬上限,写满就报 No space left on device,绝不会蚕食系统内存。
  • mode=1733:PHP Session 目录的标准权限,粘滞位 + 所有用户可写但只能删自己的文件。
  • nosuid,nodev,noexec:禁止在这个目录里执行程序,防止有人上传脚本然后运行,这是最容易被忽略的安全加固。
  • noatime:不更新访问时间戳,减少无谓的写入。

确认站点 Session 读写正常后,写进 /etc/fstab 让它开机自动挂载。注意 fstab 格式用 tmpfs 作为设备名即可:

tmpfs  /var/lib/php/sessions  tmpfs  defaults,size=256M,mode=1733,nosuid,nodev,noexec,noatime  0  0

写完后务必先验证再重启,否则可能开不了机:

mount -a
echo $?   # 必须输出 0

第三步:给 Nginx 临时目录搬家(反代站提速最明显)

如果你的小站做了反向代理(比如前面挂 Cloudflare,或者 Nginx 反代后端 PHP/Node),那 Nginx 会大量读写临时缓冲文件。默认它们在 /var/lib/nginx 下。把它迁移到 tmpfs:

# nginx.conf 的 http 块里配置这三行
proxy_temp_path   /dev/shm/nginx_proxy_temp 1 2;
fastcgi_temp_path /dev/shm/nginx_fastcgi_temp 1 2;
client_body_temp_path /dev/shm/nginx_client_temp 1 2;

注意这里直接用 /dev/shm,它本身就是 tmpfs,不用额外挂载,最省事。但记得给这几个子目录建好并赋权:

mkdir -p /dev/shm/nginx_proxy_temp /dev/shm/nginx_fastcgi_temp /dev/shm/nginx_client_temp
chown -R www-data:www-data /dev/shm/nginx_*
nginx -t && systemctl reload nginx

很多人会忽略 /dev/shm 也有大小上限(默认通常是内存的一半)。如果你要跑大文件代理,得显式调整:

# /etc/fstab 中
tmpfs  /dev/shm  tmpfs  defaults,size=512M,noexec,nosuid,nodev  0  0

第四步:验证提速效果,用数据说话

挂完之后不能靠感觉,要压测对比。用 ab 或 wrk 打一个动态页面,对比 tmpfs 前后的每秒请求数:

# 安装压测工具(Debian/Ubuntu)
apt install -y apache2-utils
ab -n 2000 -c 50 https://你的域名/

重点看 Requests per second 和 Time per request 两个数字。同时用 iostat 观察磁盘写量是否下降:

iostat -x 1 10

如果配置生效,你会发现 %util(磁盘繁忙度)明显降低,await(平均等待)也下来了——这就是把 IO 从磁盘搬到内存的直接证据。

把 /tmp 也搬进内存:最容易见效的一步

如果你只想动一个目录就见效,那答案往往是 /tmp。PHP 会话、编译临时文件、图片处理中间产物、插件解压、Composer 依赖安装——全都经过 /tmp。把它搬到 tmpfs 有两条路:一是直接给 /tmp 挂 tmpfs,二是用 systemd 的 tmp.mount 单元做管理。

手动挂载的方式和前面一样:

# 先把 /tmp 里还在用的文件清一清,避免挂载后被遮挡
systemctl stop 依赖 /tmp 的服务   # 视情况
mount -t tmpfs -o size=512M,nosuid,nodev,mode=1777 tmpfs /tmp

# 确认
df -h /tmp
mount | grep ' /tmp '

注意 /tmp 的权限是 1777(粘滞位,人人可写但只能删自己的),写错了会导致很多程序报权限错误。更推荐的做法是用 systemd 管理,创建 /etc/systemd/system/tmp.mount:

[Unit]
Description=Temporary Directory in RAM
DefaultDependencies=no
Conflicts=umount.target
Before=local-fs.target umount.target

[Mount]
What=tmpfs
Where=/tmp
Type=tmpfs
Options=mode=1777,strictatime,nosuid,nodev,size=512M

[Install]
WantedBy=local-fs.target

然后 systemctl daemon-reload && systemctl enable --now tmp.mount,并 systemctl mask tmp.mount... 等等,注意这里是启用不是 mask。启用后 systemd 会在每次开机自动重建 /tmp,比 fstab 更干净。好处是重启即清空,天然的"每日大扫除",还避免了 fstab 写错开不了机的风险。但要提醒:如果某些应用依赖 /tmp 里的文件在重启后仍然存在(很少见但存在),这一步会让它失效。

用 rsync 在重启前回捞数据(防丢保险)

tmpfs 天生易失,但有些场景你希望在重启前把内存里的某些文件落到磁盘。比如一个正在处理的队列状态、一个还没上传完的大文件。可以写一个关机前钩子,用 rsync 把 tmpfs 内容同步到持久目录。先用 systemd 服务绑定:

# /etc/systemd/system/save-tmpfs.service
[Unit]
Description=Persist tmpfs contents before shutdown
DefaultDependencies=no
Before=shutdown.target umount.target

[Service]
Type=oneshot
ExecStart=/usr/bin/rsync -a --delete /var/lib/php/sessions/ /var/backup/sessions/

[Install]
WantedBy=shutdown.target

配合一条 cron 定时同步也行,比如每 5 分钟落一次地。核心思路是:tmpfs 负责"快",磁盘负责"久",两者各司其职,用 rsync 把关键状态桥接起来。注意这种同步不追求一致性,只求"丢了能从上一个点恢复"。

用 vmtouch 验证文件是否真的在内存里

挂完 tmpfs,你可能好奇"这个文件现在到底在内存还是磁盘"。除了 tmpfs 天然在内存外,普通文件也可以被缓存进内存,用 vmtouch 能看清页缓存状态:

apt install -y vmtouch
vmtouch /var/www/html/index.php
vmtouch -v /var/www/html/          # 整个目录

输出里的 Resident Pages 就是常驻内存的页数。vmtouch -t 还能主动把文件"touch"进内存做预热。这对理解了 tmpfs 只是"必定在内存的文件系统"而页缓存是"临时借内存"的区别很有帮助——前者稳定占用,后者可能被回收。

资源监控:让 tmpfs 用量可视化

tmpfs 有一个隐蔽风险——它悄悄吃内存,top 里又看不出是哪个目录吃的。用 df -h -t tmpfs 可以列出所有 tmpfs 的已用和上限。想持续监控,可以写一个小脚本定期记录,配合 node_exporter 的 textfile collector 输出到 Prometheus:

#!/bin/bash
# /usr/local/bin/tmpfs_metrics.sh
OUT=/var/lib/node_exporter/textfile/tmpfs.prom
TMP=$(mktemp)
for mp in $(df -t tmpfs --output=target | tail -n +2); do
  read size used < <(df -B1 --output=size,used "$mp" | tail -1)
  echo "tmpfs_used_bytes{mount=\"$mp\"} $used" >> "$TMP"
  echo "tmpfs_size_bytes{mount=\"$mp\"} $size" >> "$TMP"
done
mv "$TMP" "$OUT"

再加一条 cron 每分钟跑一次,你就能在 Grafana 上看到每个 tmpfs 的使用率曲线,一旦某天接近上限,立刻知道是哪个目录在膨胀。

五个必踩的坑,提前避开

  1. tmpfs 撑爆导致 OOM:虽然 tmpfs 有 size 上限,但如果多个 tmpfs 加起来超过物理内存,系统仍会紧张。全局加起来别超过内存的 30%。
  2. Session 重启丢失导致全员登出:这其实是"预期行为"。如果你的站登录态很重要,可以把 Session 后端换成 Redis,而不是文件。tmpfs 只适合可丢的会话。
  3. noexec 导致某些程序跑不起来:极少数应用会在缓存目录里执行脚本。如果挂了 noexec 后报 Permission denied,就把它去掉,但要权衡安全。
  4. 忘记清理旧文件:tmpfs 不会自动删文件。Session 目录要配好 PHP 的 session.gc_maxlifetime,Nginx 临时目录靠它自己回收,一般没问题。
  5. fstab 写错导致无法开机:最后一项参数保持 0 0,且一定要先 mount -a 验证通过再重启,这是个价值连城的习惯。

总结:小内存 VPS 的性价比之选

tmpfs 的哲学很朴素——把最需要速度的数据放在最快的地方。对预算有限、买不起 NVMe 的个人站长来说,用几十行配置换来的是实打实的 IO 提速,还有 noexec/nosuid 附赠的安全加固。记住那条铁律:只放可重建的数据,绝不碰数据库和源码。先从小流量站点的一个 Session 目录开始试,用压测数据验证收益,再逐步推广到 Nginx 临时目录,稳扎稳打才是运维的正道。

Last modification:October 3rd, 2026 at 01:24 pm

Leave a Comment