Rails 4 - 线程错误

时间:2016-03-10 14:45:42

标签: ruby-on-rails

我正在尝试执行一些计算来填充数据库中的一些历史数据。

数据库是SQL Server。服务器是tomcat(使用JRuby)。

我在指向uat环境的rails控制台中运行脚本文件。

我正在尝试使用线程来加速执行。我们的想法是每个线程都会获取一个对象并为其运行计算,并将计算出的值保存回数据库。

问题:我一直收到此错误:

ActiveRecord::ConnectionTimeoutError (could not obtain a database connection within 5.000 seconds (waited 5.000 seconds))

代码:

require 'thread'

    threads = []
    items_to_calculate = Item.where("id < 11").to_a #testing only 10 items for now

    for item in items_to_calculate
      threads << Thread.new(item) { |myitem|

        my_calculator = ItemsCalculator.new(myitem)
        to_save = my_calculator.calculate_details
        to_save.each do |dt|
          dt.save!
        end
      }
    end

    threads.each { |aThread|  aThread.join }

2 个答案:

答案 0 :(得分:1)

如果您离开测试阶段,您可能会同时启动大量线程。

这些线程中的每一个都需要数据库连接。 Rails将为每个线程创建一个新的(可能同时创建大量的数据库连接),或者它不会,在这种情况下你会遇到麻烦,因为有几个线程试图使用相同的连接并行。第一种情况可以解释错误消息,因为数据库服务器中可能存在开放数据库连接的硬限制。

通常不建议创建这样的线程。通常,您最好创建少量(受控/受限)的工作线程并使用队列在它们之间分配工作。

在您的情况下,您可以使用一组工作线程来执行计算,并使用第二组工作线程来写入数据库。我对您的代码细节知之甚少,无法为您做出更好的决定。如果计算成本很高且DB工作不成功,那么您可能只有一个工作人员以串行方式写入DB。如果您的数据库是野兽并且针对并行写入进行了高度优化,并且您需要编写大量数据,那么您可能需要(少量)数据库工作者。

答案 1 :(得分:1)

您可能会产生比ActiveRecord更多的线程,DB连接池有连接。 Ekkehard's answer是一个很好的一般描述;所以这里有一个简单的例子,说明如何使用Ruby的线程安全Queue来限制你的员工。

require 'thread'

queue = Queue.new
items.each { |i| queue << i } # Fill the queue

Array.new(5) do # Only 5 concurrent workers
  Thread.new do
    until queue.empty?
      item = queue.pop
      ActiveRecord::Base.connection_pool.with_connection do
        # Work
      end
    end
  end
end.each(&:join)

我之所以选择5,因为它是ConnectionPool's default,但您当然可以将其调整到仍然有效的最大值,或者使用结果填充另一个队列以便稍后保存并运行任意数量的线程计算。

with_connection方法抓取连接,运行块,然后确保释放连接。这是必要的,因为ActiveRecord中的错误,连接并不总是被释放。查看this blog post了解一些详细信息。