nodejs ssr canvas 内存泄漏问题

6 阅读2分钟

gpt生成 dockerfile在下面👇🏻

    • Decay)”机制。一旦它发现某块内存释放了,它会通过系统调用(madvise with MADV_DONTNEED主动、快速地告诉操作系统:“这块地我不用了,你拿回去吧。”
    • 这就是为什么用了 jemalloc 后,你会看到内存曲线在请求结束后迅速回落
  • 原理三:线程隔离优化

    • 它对多线程环境下的锁竞争做了极度优化,不仅省内存,通常还能让 Node.js 的 QPS(吞吐量)提升 10%~20%。

4. LD_PRELOAD 是怎么生效的?

你可能会问: “我没改代码,也没重新编译 Node.js,为什么加个环境变量就好使了?”

这是 Linux 的动态链接机制在起作用:

  1. 标准流程:Node.js 运行时需要申请内存 -> 调用 C 标准库的 malloc() 函数 -> 默认链接到 glibc.so

  2. 注入流程

    • LD_PRELOAD=/.../libjemalloc.so.2 告诉 Linux 加载器:“在加载任何其他库之前,先加载 jemalloc”。
    • jemalloc 里面也实现了 malloc()free() 等标准函数。
    • 当 Node.js 呼叫 malloc() 时,Linux 发现 jemalloc 已经先占位了,于是直接使用了 jemalloc 的实现,完全绕过了 glibc。

总结

  • Glibc (默认) :像个懒惰的囤积狂。你还给它的东西,它堆在仓库里不整理,也不还给老板(OS),导致仓库越来越大。
  • Jemalloc (外挂) :像个强迫症收纳师。它把东西整理得整整齐齐(无碎片),一旦你不用了,它立刻把空间腾出来还给老板(内存回落)。

这就是为什么在服务端 Canvas 渲染这种**“大内存吞吐”**场景下,jemalloc 是必选项。

FROM reg-ai.chehejia.com/ssai/node-20:node20-canvas-v1

# 设置工作目录
WORKDIR /app

# 复制项目
COPY . /app/

# 1. 安装系统依赖 (必须打开注释!)
# pkg-config: 编译必须,否则找不到 cairo
# libjemalloc2: 解决内存不回落
# build-essential & python3: 编译工具
# RUN apt-get update && apt-get install -y --no-install-recommends \
#     libjemalloc2 \
#     && rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y --no-install-recommends \
    fonts-liberation \
    fonts-wqy-microhei \
    fontconfig \
    build-essential \
    python3 \
    pkg-config \
    libcairo2-dev \
    libpango1.0-dev \
    libjpeg-dev \
    libgif-dev \
    librsvg2-dev \
    libjemalloc2 \
    && rm -rf /var/lib/apt/lists/* \
    && fc-cache -fv

# 2. 安装依赖
# 这一步会下载所有包,canvas 此时可能是预编译版本(有隐患)
RUN npm_config_tarball=/tarball/node-v20.15.1-headers.tar.gz CI=true pnpm install

# 3. 关键步骤:强制源码重编译 Canvas
# 解释:
# - npm_config_build_from_source=true: 告诉 node-gyp 不要下载镜像,必须编译
RUN npm_config_build_from_source=true pnpm rebuild canvas
RUN cd node_modules/.pnpm/canvas@3.1.2/node_modules/canvas && npm_config_canvas_binary_host_mirror=https://mirrors.tuna.tsinghua.edu.cn/node-canvas/ pnpm rebuild

# 4. 构建项目
RUN pnpm build

# 设置环境变量
ARG APP_NAME
ENV ENTRY=dist/src/main.js
ENV ENTRY_ESM=dist/src/main.mjs

# 5. 注入 jemalloc (解决内存碎片的终极杀招)
ENV LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2

# 启动命令
# 加上 --expose-gc 方便你在代码里手动回收
CMD ["sh", "-c", "if [ -f $ENTRY_ESM ]; then node --max-old-space-size=4096 --expose-gc $ENTRY_ESM; elif [ -f $ENTRY ]; then node --max-old-space-size=4096 --expose-gc $ENTRY; else echo 'No main file found'; fi"]

EXPOSE 80