缩小特定的Heroku工作人员dynos?

时间:2014-08-09 04:57:37

标签: heroku scaling hirefire

我正在构建一个Web应用程序,该应用程序提供用户上传大图像并对其进行处理的核心功能。处理大约需要3分钟才能完成,我认为Heroku将是一个理想的平台,能够以高度可扩展的方式按需运行这些处理作业。处理任务本身在计算上相当昂贵,需要运行高端PX dyno。我希望最大化并行化,并最小化(有效消除)作业在队列中等待的时间。换句话说,我想为N个工作提供N PX dynos。

值得庆幸的是,我可以使用Heroku的API(或者像Hirefire这样的服务)轻松完成这项工作。每当有新的处理请求进入时,我只需增加工作者数量,新工作人员就会从队列中获取作业并立即开始处理。

然而,虽然扩大规模是徒劳的,但缩小规模是麻烦开始的地方。 Heroku API令人沮丧地受到限制。我只能设置正在运行的工作人员的数字,而不是专门杀死空闲工作人员。这意味着如果我每个处理一个图像的工作人员都有20个,并且一个完成了它的任务,我就无法安全地将工作人员数量扩展到19,因为Heroku会杀死一个任意工作人员dyno,无论它是否&# 39; s实际上是在工作中!让所有工人继续工作直到所有工作完成都是不可能的,因为成本是天文数字。想象一下,在一个尖峰期间创造的100名工人继续无限期地闲置,因为一天中有一些新工作涓涓细流!

我已经搜索过网络,以及最好的"解决方案"人们建议让你的工作流程优雅地处理终止。如果您的工作人员正在进行大量发送电子邮件,那就完全没问题,但是我的工作人员正在对图像进行一些非常精细的分析,正如我上面提到的,大约需要3分钟才能完成。

在理想的世界中,我可以在完成任务后杀死特定的工作人员dyno。这样可以缩小尺寸,就像放大一样简单。

事实上,我已经接近这个理想的世界,从工作人员的dynos切换到一次性dynos(终止于进程终止,即你停止支付dyno之后的#d&#34"#34 ; root程序"退出)。然而,Heroku设置了5个可以同时运行的一次性dynos的硬限制。我可以理解,因为我当然在某种意义上滥用一次性dynos ......但是它仍然非常令人沮丧。

有什么办法可以让我的工人更好地缩小规模吗?我宁愿不必从根本上重新设计我的处理算法......将它分成几个块,这些块在30-40秒内运行,而不是一个3分钟的延伸(这意味着不小心杀死了正在运行的工作人员。是灾难性的)。这种方法会使我的处理代码大大复杂化并引入几个新的失败点。但是,如果这是我唯一的选择,我就必须这样做。

感谢任何想法或想法!

4 个答案:

答案 0 :(得分:3)

这是Heroku的支持回答的问题:

  

我担心目前不可能这样做。缩小你的时间   工人,我们将阻止数量最多的人,所以我们不要   必须改变那些dynos的公共名称,而你却得不到   编号孔。

我发现this comment在这种情况下很有意思,虽然它并没有真正解决这个问题。

答案 1 :(得分:2)

安排清理任务

摘要:将任务排队以最低优先级运行。完成所有其他任务后,清理任务将会运行。

详细信息

[注意:一旦我写下这个答案,我意识到它并没有解决关闭特定工作人员dyno的需要。但是你应该能够利用这里显示的关键技术:排除低优先级DJ任务,以便在处理完所有其他事项后进行清理。]

我很幸运使用Heroku的[platform-api][1]宝石按需启动延迟工作工作人员,并在他们完成时将其拆除。为简化起见,我创建了一个heroku_control.rb文件,如下所示。

我的应用只需要一名工人;我认识到您的要求更加复杂,但任何应用程序都可以利用这一个技巧:在处理完所有其他延迟作业任务后,将低优先级任务排队以关闭工作人员dyno。

require 'platform-api'

# Simple class to interact with Heroku's platform API, allowing
# you to start and stop worker dynos under program control.
class HerokuControl

  API_TOKEN = "<redacted>"
  APP_NAME = "<redacted>"

  def self.heroku
    @heroku ||= PlatformAPI.connect_oauth(API_TOKEN)
  end

  # Spin up one worker dyno
  def self.worker_up(act = Rails.env.production?)
    self.worker_set_quantity(1) if act
  end

  # Spin down all worker dynos
  def self.worker_down(act = Rails.env.production?)
    self.worker_set_quantity(0) if act
  end

  def self.worker_set_quantity(quantity)
    heroku.formation.update(APP_NAME, 'worker', {"quantity" => quantity.to_s})
  end

end

在我的应用中,我做了类似的事情:

LOWEST_PRIORITY = 100

def start_long_process
  queue_lengthy_process
  queue_cleanup_task        # clean up when everything else is processed
  HerokuControl::worker_up  # assure there is a worker dyno running
end

def queue_lengthy_process
  # do long job here...
end
handle_asynchronously :queue_lengthy_process, :priority => 1

# This gets processed when Delayed::Job has nothing else
# left in its queue.
def queue_cleanup_task
  HerokuControl::worker_down # shut down all worker dynos
end
handle_asynchronously :queue_cleanup_task, :priority => LOWEST_PRIORITY

希望这有帮助。

答案 2 :(得分:0)

我知道你提到了优雅的终止,但我认为你的意思是优雅的终止,就像通过使用API​​设置工人数来杀死一个工人一样。为什么不在作业完成时添加作为工作逻辑的一部分来自杀呢?

答案 3 :(得分:0)

现在可以使用heroku ps:stop命令关闭特定的dyno。

e.g。如果您的heroku ps输出包含:

web.1: up 2017/09/01 13:03:50 -0700 (~ 11m ago)
web.2: up 2017/09/01 13:03:48 -0700 (~ 11m ago)
web.3: up 2017/09/01 13:04:15 -0700 (~ 11m ago)

你可以运行heroku ps:stop web.2来杀死列表中的第二个dyno。

这不会完全符合你的要求,因为Heroku会立即启动一个新的dyno来取代那个被关闭的dyno。但也许这对你(或其他人阅读这个问题)仍然有用。