【问题标题】:Scale down specific Heroku worker dynos?缩小特定的 Heroku 工人测功机?
【发布时间】:2014-10-02 15:10:11
【问题描述】:

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

谢天谢地,我可以使用 Heroku 的 API(或可选的 Hirefire 等服务)轻松完成此任务。每当有新的处理请求进来时,我可以简单地增加工作人员计数,新工作人员将从队列中获取作业并立即开始处理。

然而,虽然扩大规模是无痛的,但缩小规模是麻烦的开始。 Heroku API 令人沮丧地受到限制。我只能设置正在运行的工人的数量,而不是专门杀死空闲的工人。这意味着,如果我有 20 个工作人员,每个工作人员处理一个图像,并且一个人完成了它的任务,我不能安全地将工作人员数量扩展到 19,因为 Heroku 会杀死一个 任意 工作人员测功机,不管它是否真的在工作中!让所有工人运行直到所有工作完成是不可能的,因为成本将是天文数字。想象一下,在高峰期创建的 100 名工作人员继续无限期地闲置,因为全天都有一些新工作在不断涌现!

我搜索了网络,人们建议的最佳“解决方案”是让您的工作进程优雅地处理终止。好吧,如果您的工作人员只是在发送大量电子邮件,那很好,但我的工作人员正在对图像进行一些非常冗长的分析,正如我上面提到的,大约需要 3 分钟才能完成。

在理想的世界中,我可以在完成任务后杀死一个特定工人测功机。这将使缩小与放大一样容易。

事实上,通过从工人测功机切换到一次性测功机(它在进程终止时终止,即您在它的“根程序”退出后停止为测功机付费),我已经接近了那个理想的世界。但是,Heroku 设置了可以同时运行 5 个一次性 dyno 的硬性限制。这我可以理解,因为我肯定在某种意义上滥用了一次性测功机……但这仍然令人沮丧。

有什么方法可以更好地缩减我的员工人数?我宁愿不必从根本上重新设计我的处理算法......将它分成几块在 30-40 秒内运行而不是 3 分钟的时间(这样就不会意外杀死正在运行的工人灾难性的)。这种方法会极大地复杂化我的处理代码并引入几个新的故障点。但是,如果这是我唯一的选择,我将不得不这样做。

感谢任何想法或想法!

【问题讨论】:

  • afaik 你可以重新启动特定的测功机,例如discussion.heroku.com/t/stop-a-specific-dyno/424。事后立即改变阵型可能会奏效吗?您必须知道已完成运行的工作人员的姓名
  • @unohoo 听起来很有希望!我会试一试并报告。
  • @ColemanS 你有没有想过这里有什么?试图实现基本相同的目标。

标签: heroku scaling hirefire


【解决方案1】:

我知道您提到了优雅终止,但我认为您的意思是优雅终止,例如通过使用 API 设置工作人员数量来杀死工作人员。为什么不将其作为工作逻辑的一部分添加到工作完成后杀死自己?

【讨论】:

  • 当然,你可以让工人杀死自己,这很管用。但是,Heroku 会立即重新启动它。
【解决方案2】:

安排清理任务

总结:排队一个任务以最低优先级运行。完成所有其他任务后,将运行清理任务。

详情

[注意:一旦我写了这个答案,我意识到它并没有解决降低特定工人测功机的需要。但是您应该能够利用此处显示的关键技术:将低(er)优先级 DJ 任务排队以在其他所有内容都已处理后进行清理。]

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

我的应用只需要一名工人;我知道您的要求涉及的内容要多得多,但任何应用程序都可以利用这一技巧:在处理完所有其他延迟的作业任务后,将低优先级任务排队以关闭工作人员测功机。

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

希望这会有所帮助。

【讨论】:

  • 这正是我找到这篇文章时所需要的,谢谢!
【解决方案3】:

这是 Heroku 的支持人员对此的回答:

恐怕目前这是不可能的。当缩小你的 工人,我们将停止数量最多的那个,所以我们不 必须更改这些测功机的公共名称,而您不会得到 编号孔。

我发现this comment 在这种情况下很有趣,尽管它并没有真正解决这个问题。

【讨论】:

    【解决方案4】:

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

    例如如果您的 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 来杀死列表中的第二个测功机。

    这不会完全符合您的要求,因为 Heroku 将立即启动一个新的测功机来替换已关闭的测功机。但也许这对您(或阅读此问题的其他人)仍然有用。

    【讨论】:

      猜你喜欢
      • 2018-06-30
      • 1970-01-01
      • 2011-09-29
      • 2017-02-13
      • 2016-07-26
      • 2020-11-19
      • 2018-10-15
      • 2012-01-15
      • 1970-01-01
      相关资源
      最近更新 更多