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

如何在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实现每分钟调度:

  1. 创建Job类:
class ExchangeMarketUpdateJob < ApplicationJob
  queue_as :exchange_updates

  def perform(exchange_name)
    Rake::Task["exchange_markets_update:#{exchange_name}"].execute
  end
end
  1. 配置定时任务:
# config/initializers/sidekiq.rb
Sidekiq::Cron::Job.create(
  name: 'Update all exchanges every minute',
  cron: '* * * * *',
  class: 'ExchangeMarketUpdateJob',
  args: ['gdax', 'gemini', 'bitstamp', ...] # 传入所有Exchange名称
)
  1. 修改Procfile:
web: bundle exec rails server -p $PORT
worker: bundle exec sidekiq -C config/sidekiq.yml

Sidekiq的线程池管理更成熟,内存占用也比多Rake进程低很多,适合长时间运行的任务场景。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:56:08