如何在Heroku与Ruby on Rails中用单个Worker合理运行多任务
优化Heroku上Exchange任务的内存占用与并发效率
让我帮你拆解下当前方案的核心问题,再给出针对性的优化思路——你现在的内存压力主要来自重复加载Rails环境,加上任务串行执行导致的资源利用率低下,以下是具体的优化步骤:
1. 复用单个Rails环境,砍掉多进程内存开销
当前你用Foreman启动3个独立的Rake进程,每个进程都要完整加载一次Rails环境,这直接导致内存占用翻了3倍。建议改成单个进程内并行执行所有任务,只加载一次Rails环境:
修改你的Rakefile,用Ruby线程池实现并行执行(可以借助concurrent-ruby库简化线程管理):
Rails.application.load_tasks task :update_all_exchanges => :environment do require 'concurrent' # 列出所有需要执行的Exchange更新任务 exchange_task_names = [ 'exchange_markets_update:gdax', 'exchange_markets_update:gemini', 'exchange_markets_update:bitstamp', 'exchange_markets_update:bitflyer', 'exchange_markets_update:okex', 'exchange_markets_update:cex', 'exchange_markets_update:bitfinex', 'exchange_markets_update:bittrex', 'exchange_markets_update:hitbtc', 'exchange_markets_update:poloniex', 'exchange_markets_update:kraken', 'exchange_markets_update:binance', 'exchange_markets_update:bithumb', 'exchange_markets_update:gateio', 'exchange_markets_update:idex', 'exchange_markets_update:bitbank', 'exchange_markets_update:zb', 'exchange_markets_update:bibox', 'exchange_markets_update:ethfinex' ] loop do # 初始化线程池,控制并发数避免API限流和内存暴涨(先从10开始试) thread_pool = Concurrent::ThreadPoolExecutor.new( min_threads: 5, max_threads: 10 ) exchange_task_names.each do |task_name| thread_pool.post do # 确保每个线程使用独立的数据库连接,避免连接泄漏 ActiveRecord::Base.connection_pool.with_connection do Rake::Task[task_name].execute end end end # 等待所有任务执行完成,再进入下一轮循环 thread_pool.shutdown thread_pool.wait_for_termination # 精准等待到下一分钟的起始点,避免任务堆积 sleep(60.seconds - (Time.now.to_i % 60)) end end
2. 调整数据库连接池配置
因为用了多线程,要确保config/database.yml里的连接池大小至少等于线程池的最大并发数:
production: # 其他数据库配置 pool: 10
3. 简化Procfile,砍掉Foreman多进程
现在不需要再用Foreman启动多个进程了,直接让Worker dyno运行单个Rake任务即可:
web: bundle exec rails server -p $PORT worker: bundle exec rake update_all_exchanges
4. 优化单个Exchange任务的内存泄漏风险
每个Exchange任务本身也可能存在内存浪费,建议做这些检查:
- 确保HTTP请求完成后释放资源(比如用
Faraday替代重客户端,避免连接池泄漏) - 用批量数据库更新替代单条记录更新,减少数据库交互开销
- 清理循环中创建的大对象,避免内存堆积
5. 动态调整并发数,监控内存使用
- 用
heroku logs --tail实时查看Worker的内存占用,逐步调整线程池的max_threads(从5开始慢慢往上加) - 确保内存占用始终控制在450MB以内(给Standard-1X dyno留62MB的余量,避免OOM崩溃)
可选:改用Sidekiq做异步任务调度
如果需要更成熟的任务重试、监控功能,可以把Exchange任务改成Sidekiq Job,配合sidekiq-cron实现每分钟调度:
- 创建Job类:
class ExchangeMarketUpdateJob < ApplicationJob queue_as :exchange_updates def perform(exchange_name) Rake::Task["exchange_markets_update:#{exchange_name}"].execute end end
- 配置定时任务:
# config/initializers/sidekiq.rb Sidekiq::Cron::Job.create( name: 'Update all exchanges every minute', cron: '* * * * *', class: 'ExchangeMarketUpdateJob', args: ['gdax', 'gemini', 'bitstamp', ...] # 传入所有Exchange名称 )
- 修改Procfile:
web: bundle exec rails server -p $PORT worker: bundle exec sidekiq -C config/sidekiq.yml
Sidekiq的线程池管理更成熟,内存占用也比多Rake进程低很多,适合长时间运行的任务场景。
内容的提问来源于stack exchange,提问作者apuente
相关产品推荐
相关产品推荐

