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
相关产品推荐
相关产品推荐

