You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rails 5中Wicked_PDF生成多图片PDF致服务器内存耗尽问题

解决Rails 5中wicked_pdf生成多图片PDF导致服务器宕机的问题

首先,你遇到的这个问题大概率是wkhtmltopdf(wicked_pdf依赖的渲染引擎)在加载大量本地图片时内存占用过高,最终导致服务器进程因内存耗尽被杀死——哪怕单张图片尺寸小,数量多了内存累积起来也很可观。结合你的场景,我给你几个针对性的解决方案:

1. 优化wkhtmltopdf渲染参数,减少内存消耗

wkhtmltopdf有很多参数可以控制资源占用,你可以在render时添加这些参数来限制内存使用、禁用不必要的功能:

respond_to do |format|
  format.html { render :inspection_report, layout: "reports_layout" }
  format.pdf do
    render pdf: "inspection_report",
           template: "pdf/inspection_report.html.erb",
           layout: "reports_layout",
           wkhtmltopdf: {
             disable_javascript: true, # 如果页面不需要JS,禁用它能大幅减少内存
             disable_plugins: true, # 禁用浏览器插件,避免额外资源占用
             memory_limit: "512m", # 给wkhtmltopdf设置内存上限,根据服务器配置调整
             enable_local_file_access: true # 确保本地图片/CSS能被正确访问(避免权限问题)
           }
  end
end

2. 将图片转为Base64嵌入HTML,减少IO开销

wkhtmltopdf加载本地图片时会反复进行磁盘IO,而且每个图片都会占用一个文件句柄,数量多了容易引发资源泄漏。把小图片转成Base64直接嵌入HTML,让wkhtmltopdf直接读取内存中的内容:

先在你的helper里加一个转换方法:

# app/helpers/pdf_helper.rb
module PdfHelper
  def image_to_base64(image_path)
    full_path = Rails.root.join('public', image_path) # 假设图片存在public目录下
    return image_path unless File.exist?(full_path)

    file_ext = File.extname(full_path).delete('.')
    "data:image/#{file_ext};base64,#{Base64.encode64(File.read(full_path))}"
  end
end

然后在视图里替换原有的图片引用:

# pdf/inspection_report.html.erb
<img src="<%= image_to_base64('uploads/inspection/image1.jpg') %>" alt="Inspection Photo">

3. 改用异步生成PDF,避免阻塞服务器进程

如果报表图片数量极多,同步生成PDF会长时间占用服务器进程,甚至耗尽内存。用后台任务(比如Sidekiq)异步生成,让用户不用等待,也不会拖垮服务器:

第一步:创建Sidekiq任务

# app/workers/inspection_pdf_worker.rb
class InspectionPdfWorker
  include Sidekiq::Worker

  def perform(inspection_id)
    inspection = Inspection.find(inspection_id)
    # 渲染HTML模板
    html_content = ApplicationController.new.render_to_string(
      template: "pdf/inspection_report.html.erb",
      layout: "reports_layout",
      locals: { inspection: inspection }
    )
    # 生成PDF
    pdf_content = WickedPdf.new.pdf_from_string(
      html_content,
      wkhtmltopdf: { disable_javascript: true, memory_limit: "512m" }
    )
    # 保存PDF到本地或云存储(比如S3)
    save_path = Rails.root.join('public', "reports/inspection_#{inspection_id}.pdf")
    FileUtils.mkdir_p(File.dirname(save_path))
    File.open(save_path, 'wb') { |f| f.write(pdf_content) }
    # 发邮件通知用户下载
    InspectionMailer.report_ready(inspection, save_path).deliver_now
  end
end

第二步:修改控制器逻辑,触发异步任务

respond_to do |format|
  format.html { render :inspection_report, layout: "reports_layout" }
  format.pdf do
    InspectionPdfWorker.perform_async(@inspection.id)
    redirect_to inspection_path(@inspection), notice: "PDF报表正在生成,完成后会发送邮件通知您!"
  end
end

4. 检查CSS加载方式与服务器资源配置

  • 确保布局里用wicked_pdf_stylesheet_link_tag加载CSS,而不是普通的stylesheet_link_tag,这样wicked_pdf能正确解析本地CSS:
    # app/views/layouts/reports_layout.html.erb
    <%= wicked_pdf_stylesheet_link_tag "reports.css" %>
    
  • 调整服务器进程配置:如果用Puma,减少worker和线程数,避免多个PDF渲染任务同时占用内存;如果用Passenger,调整PassengerMaxPoolSize等参数,控制进程数量。

内容的提问来源于stack exchange,提问作者Charith H

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 06:59:04