我正在构建一个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分钟的延伸(这意味着不小心杀死了正在运行的工作人员。是灾难性的)。这种方法会使我的处理代码大大复杂化并引入几个新的失败点。但是,如果这是我唯一的选择,我就必须这样做。
感谢任何想法或想法!
答案 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。但也许这对你(或其他人阅读这个问题)仍然有用。