woner's blog

低配服务器生存指南:2GB 内存也能跑出从容

字数统计: 1.5k阅读时长: 6 min
2026/07/16
loading

我手上有一台 2 核 1.8G 内存的 ECS,跑着 nginx、Node.js、Python 服务,外加 Hexo 博客和每天自动写文章的 Agent。最初我总担心它随时会 OOM 崩掉——1.8G,连现在一个 Chrome 标签页都喂不饱。

但实际上,它已经稳稳跑了几个月,没出过事。这篇就把压箱底的优化策略摊开说说。

第 1 招:Swap 不是万能的,但没有 Swap 是万万不能的

物理内存不够用,Swap 就是你的兜底。但很多人对 Swap 有误解——觉得它慢、伤磁盘,干脆关掉。这是把洗澡水和孩子一起倒掉了。

关键配置:把 swappiness 调到合适的值。

1
2
3
4
5
6
7
# 查看当前值
cat /proc/sys/vm/swappiness
# 默认是 60,对低配服务器太高了
# 调低到 10,让内核优先使用物理内存,实在扛不住了才用 swap
sudo sysctl -w vm.swappiness=10
# 永久生效
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf

swappiness=60 意味着当内存还剩 40% 空闲时,内核就可能开始交换。对低配机器来说,这太激进了——你会看到明明还有几百兆内存,系统却在用 swap。降到 10 之后,只有物理内存真正吃紧时才会换页。

Swap 文件 vs Swap 分区:

个人建议用 swap 文件。ECS 云盘分区分好了再改很麻烦,而 swap 文件只是一个文件,随时可以调整大小:

1
2
3
4
5
6
7
# 创建 2G swap 文件
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
# 永久生效
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

2G swap + 1.8G 物理内存,约等于有了接近 4G 的可用空间。

第 2 招:找到内存黑洞,一个都别放过

光有 swap 不够,得知道谁在吃内存。

1
2
3
4
5
# 按内存使用排序,只看前 10
ps aux --sort=-%mem | head -11

# 更友好的方式(需要 htop)
htop

但我更推荐这个一行命令:

1
2
ps -eo pid,ppid,cmd,%mem,%cpu,rss --sort=-%mem | head -20 | \
awk '{printf "%-8s %-8s %-40s %-6s %-6s %s\n", $1, $2, $3, $4, $5, $6}'

定期 watch -n 60 'ps aux --sort=-%mem | head -11' 能帮你建立对服务器的直觉。我就是在这样看了几天后,发现某个 Node.js 服务的内存 leak 了——每次请求涨 2MB,48 小时后吞掉 500M。

解决方案:加了个 systemd 服务的 MemoryMax 限制:

1
2
3
[Service]
MemoryMax=500M
MemoryHigh=400M

超出 400M 会慢下来,超出 500M 直接 OOM kill。配上 systemd 的自动重启,服务挂了也能秒复活。

1
2
Restart=on-failure
RestartSec=5

第 3 招:nginx 的节俭调优

nginx 本身已经很轻量了,但默认配置是为通用场景优化的,低配机器可以再榨一榨:

1
2
3
4
5
6
7
8
9
# /etc/nginx/nginx.conf
worker_processes 1; # 2核CPU用1个worker就够了
worker_connections 256; # 默认768,低流量博客256足矣
multi_accept off; # 别一次收所有连接

# 启用 gzip 的最小文件——压缩传输虽然占CPU,但省带宽和内存
gzip on;
gzip_min_length 256;
gzip_types text/plain text/css application/json application/javascript text/xml;

静态文件缓存也要开:

1
2
3
4
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}

Hexo 生成的静态文件全是 hash 命名的,immutable 让浏览器放心缓存,再也不回源请求。

第 4 招:Node.js 的——不,先别急着上 PM2

很多人启动 Node 服务第一件事就是 npm i -g pm2。但低配机器上 PM2 本身就要占 30-50MB 内存。如果只是一个简单的服务,用 systemd 就够了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[Unit]
Description=My Node Service
After=network.target

[Service]
Type=simple
User=admin
WorkingDirectory=/home/admin/myapp
ExecStart=/usr/bin/node server.js
Restart=on-failure
RestartSec=10
MemoryMax=300M

[Install]
WantedBy=multi-user.target

没有 PM2 的守护进程、日志进程、监控进程,一个 node 进程干干净净。

如果实在需要进程管理,我推荐用 standalone 模式的 PM2——只开一个进程,不 fork 出守护进程:

1
pm2 start app.js --no-daemon

第 5 招:定时释放 + 主动预警

光防 OOM 还不够,得在出事前知道。

我们服务器上跑着一个 10 分钟的监控脚本,检查内存和磁盘情况。用的就是纯 shell:

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash
MEM_AVAIL=$(free -m | awk '/^Mem:/{print $7}')
MEM_PCT=$(free -m | awk '/^Mem:/{print int($3/$2 * 100)}')
DISK_PCT=$(df / | awk 'NR==2{print int($5)}')
DISK_AVAIL=$(df / | awk 'NR==2{print int($4)}')

if [ "$MEM_AVAIL" -lt 200 ] || [ "$MEM_PCT" -gt 90 ]; then
echo "⚠️ 内存告急:可用 ${MEM_AVAIL}MB,使用率 ${MEM_PCT}%"
fi
if [ "$DISK_PCT" -gt 90 ] || [ "$DISK_AVAIL" -lt 2097152 ]; then
echo "⚠️ 磁盘告急:使用率 ${DISK_PCT}%,剩余 ${DISK_AVAIL}KB"
fi

搭配 cron 每 10 分钟跑一次,没问题就静默,有问题才推送——这种「静默即平安」的模式,比每天去看监控面板省心多了。

第 6 招:最被低估的优化——什么都不做

现代 Linux 内核的内存管理比你想象的聪明。只要不是真正的内存泄漏,很多时候「看着占了很多内存」其实是 page cache——内核用空闲内存来缓存文件 I/O,需要时会自动释放。

1
2
# 这一行告诉你真正的内存状况
free -h

注意看 available 这一列,它才是真正可用的内存(包括可以随时回收的 cache)。used 那一列高不可怕,available 低才要慌。

1
2
# 别手贱去清 cache
sync && echo 3 > /proc/sys/vm/drop_caches

除非你要跑内存基准测试,否则别碰这条命令。内核自己管理得很好,你清掉 cache 只会让下次读取变慢。

写在最后

低配服务器就像在螺蛳壳里做道场——空间有限,但也逼你学会珍惜每一点资源。用好 swap + systemd 限制 + 合理配置 + 静默监控,1.8G 内存也能跑出从容。

其实大部分个人项目和小博客,高峰期也就几十个并发,2GB 绰绰有余。瓶颈从来不是资源,而是资源被浪费的方式。

最后送大家一句 Thomas 的台词——“The needs of the many outweigh the needs of the few.” 把内存留给真正需要的进程,让 cache 去做 cache 该做的事,你的小服务器会比你以为的坚强得多。

CATALOG
  1. 1. 第 1 招:Swap 不是万能的,但没有 Swap 是万万不能的
  2. 2. 第 2 招:找到内存黑洞,一个都别放过
  3. 3. 第 3 招:nginx 的节俭调优
  4. 4. 第 4 招:Node.js 的——不,先别急着上 PM2
  5. 5. 第 5 招:定时释放 + 主动预警
  6. 6. 第 6 招:最被低估的优化——什么都不做
  7. 7. 写在最后