Sidekiq图片处理任务内存占用过高问题求助
Sidekiq + libvips 图片处理内存泄漏问题解决方案
同类问题说明
确实有不少开发者遇到过libvips在Sidekiq长期运行场景下的内存累积问题。虽然libvips相比ImageMagick内存效率更高,但如果资源未正确释放、对象引用未及时回收,依然会出现任务完成后内存不释放、持续增长的情况。
核心问题分析
你的代码存在两个关键问题:
- 直接修改原始
ActiveStorage::Blob对象的filename属性,导致原Blob被多次修改并持有不必要的引用,阻碍Ruby GC回收资源。 - 使用
variant.process时依赖隐式资源管理,未显式释放libvips底层的图像资源。
优化方案
1. 修复Blob对象误用问题
不要修改原始Blob的filename,而是基于原始文件生成独立的变体或新Blob:
class Api::V1::Ads::Images::ResizeAndUploadJob < Api::V1::ApplicationJob sidekiq_options queue: 'high' def perform(blob_id) blob = ActiveStorage::Blob.find_by(id: blob_id) return if blob.nil? base_filename = blob.filename.base.split('_ORIGINAL').first content_type = 'image/webp' # 生成并上传1200尺寸的webp create_variant(blob, base_filename, [nil, 1200], content_type) # 生成并上传560尺寸的webp create_variant(blob, base_filename, [nil, 560], content_type) # 生成并上传130尺寸的webp create_variant(blob, base_filename, [nil, 130], content_type) end private def create_variant(original_blob, base_filename, resize_limit, content_type) size_suffix = resize_limit.last variant = original_blob.variant(format: :webp, resize_to_limit: resize_limit) variant.process # 若需要将变体保存为独立Blob(而非仅生成变体文件),可添加以下代码 # ActiveStorage::Blob.create_and_upload!( # io: StringIO.new(variant.service.download(variant.key)), # filename: "#{base_filename}_x#{size_suffix}.webp", # content_type: content_type # ) end end
2. 显式管理libvips资源
直接使用image_processing/vips的低级API,显式销毁图像对象、关闭文件句柄,确保底层内存被释放:
require "image_processing/vips" class Api::V1::Ads::Images::ResizeAndUploadJob < Api::V1::ApplicationJob sidekiq_options queue: 'high' def perform(blob_id) blob = ActiveStorage::Blob.find_by(id: blob_id) return if blob.nil? base_filename = blob.filename.base.split('_ORIGINAL').first content_type = 'image/webp' blob.open do |original_file| # 处理1200尺寸 process_and_upload_image(original_file, base_filename, 1200, content_type) # 处理560尺寸 process_and_upload_image(original_file, base_filename, 560, content_type) # 处理130尺寸 process_and_upload_image(original_file, base_filename, 130, content_type) end end private def process_and_upload_image(original_file, base_filename, height, content_type) # 重新加载原始图像,避免复用同一对象导致资源泄漏 image = ImageProcessing::Vips.source(original_file) processed_image = image.resize_to_limit(nil, height).convert(:webp).call # 上传为新Blob ActiveStorage::Blob.create_and_upload!( io: processed_image, filename: "#{base_filename}_x#{height}.webp", content_type: content_type ) # 显式释放资源 processed_image.close image.destroy! end end
3. 辅助优化措施
- 手动触发GC:在任务末尾添加
GC.start(谨慎使用,避免频繁调用影响性能),帮助Ruby及时回收未释放的对象引用。 - 调整Sidekiq并发数:在Docker启动命令中设置
--concurrency 2(根据服务器配置调整),减少同时处理的图片任务数,降低内存峰值。 - 升级依赖版本:升级
libvips到8.14+稳定版、image_processing到1.12+版本,修复已知的内存泄漏bug。 - Docker内存限制:给Sidekiq容器设置内存阈值(如
--memory 1g),作为兜底方案,避免内存耗尽影响其他服务。
内容的提问来源于stack exchange,提问作者CR7
相关产品推荐
相关产品推荐

