pm2的 cluster 模式下子应用无法使用jemalloc内存分配模式

关键点:PM2 的 cluster 模式下,不能只在 ecosystem.config.js 的 env 里写 LD_PRELOAD。
因为 LD_PRELOAD 必须在 Node 进程启动、动态链接器加载共享库之前存在;而 PM2 cluster 模式会先启动 Node worker,再把应用环境变量从 pm2_env 写入 process.env,此时加载 jemalloc 已经晚了。

正确做法是:让 PM2 守护进程本身带着 LD_PRELOAD 启动。这样它创建的所有 cluster worker 都会继承 jemalloc。

立即生效

已有 PM2 应用:

先确认 jemalloc 存在

ls -l /usr/local/lib/libjemalloc.so.2

保存当前 PM2 应用列表

pm2 save

关闭当前没有加载 jemalloc 的 PM2 daemon

pm2 kill

带着 jemalloc 重新启动 PM2 daemon,并恢复所有应用

LD_PRELOAD=/usr/local/lib/libjemalloc.so.2 pm2 resurrect

如果是首次通过配置文件启动:

pm2 kill
LD_PRELOAD=/usr/local/lib/libjemalloc.so.2 \
pm2 start ecosystem.config.js --env production
pm2 save

之后执行:

pm2 reload all
pm2 restart all
pm2 scale api 8

新创建的 cluster worker 都会继续继承 jemalloc。PM2 官方项目也记录过 cluster 模式下应用级 LD_PRELOAD 不生效,需要让 PM2 daemon 在该环境变量下启动。

ecosystem.config.js 示例

module.exports = {
apps: [
{
name: "api",
cwd: "/data/api",
script: "./app.js",
exec_mode: "cluster",
instances: "max",
env: {
NODE_ENV: "production"
}
}
]
};

这里不需要再写:

env: {
LD_PRELOAD: "/usr/local/lib/libjemalloc.so.2"
}

即使写了,也只能让代码中的 process.env.LD_PRELOAD 看起来有值,不能证明动态链接器真正加载了 jemalloc。

验证每个 cluster 进程

不要只检查:

pm2 env 0

应该直接检查进程的内存映射。

如果服务器安装了 jq:

pm2 jlist |
jq -r '.[] | select(.pid > 0) | [.name, .pm_id, .pid] | @tsv' |
while IFS=$'\t' read -r name id pid; do
if grep -q jemalloc "/proc/$pid/maps"; then
echo "$name[$id] pid=$pid:jemalloc 已加载"
else
echo "$name[$id] pid=$pid:jemalloc 未加载"
fi
done

正常结果类似:

api[0] pid=21531:jemalloc 已加载
api[1] pid=21542:jemalloc 已加载
api[2] pid=21553:jemalloc 已加载
api[3] pid=21564:jemalloc 已加载

也可以检查单个进程:

grep jemalloc /proc/21531/maps

应该能看到:

/usr/local/lib/libjemalloc.so.2

检查 PM2 daemon 本身:

PM2_PID=$(pgrep -o -f 'PM2.*God Daemon')
echo "PM2 daemon pid: $PM2_PID"
grep jemalloc "/proc/$PM2_PID/maps"

配置服务器重启后仍然生效

如果使用了:

pm2 startup
pm2 save

还需要把 LD_PRELOAD 写入 PM2 的 systemd 服务,否则服务器重启后,systemd 会启动一个没有 jemalloc 的 PM2 daemon。PM2 的启动钩子会通过保存的进程列表恢复应用。

先查看服务名称:

systemctl list-unit-files | grep pm2

通常类似:

pm2-root.service
pm2-ubuntu.service
pm2-node.service

编辑对应服务:

sudo systemctl edit pm2-ubuntu

填写:

[Service]
Environment="LD_PRELOAD=/usr/local/lib/libjemalloc.so.2"

然后执行:

pm2 save
sudo systemctl daemon-reload
sudo systemctl restart pm2-ubuntu

重新验证:

pm2 jlist |
jq -r '.[].pid' |
while read -r pid; do
if [ "$pid" -gt 0 ] && grep -q jemalloc "/proc/$pid/maps"; then
echo "$pid jemalloc loaded"
else
echo "$pid jemalloc not loaded"
fi
done

最终推荐配置

当前立即应用

pm2 save
pm2 kill
LD_PRELOAD=/usr/local/lib/libjemalloc.so.2 pm2 resurrect

永久应用

sudo systemctl edit pm2-你的用户名

systemd 配置:

[Service]
Environment="LD_PRELOAD=/usr/local/lib/libjemalloc.so.2"

这种方式会让同一个 PM2 daemon 管理的所有 cluster 应用和所有实例都使用 jemalloc。不要使用 /etc/ld.so.preload,除非明确希望让服务器上几乎所有动态链接程序都加载 jemalloc。