我怀疑您是否可以有效地(阅读:轻松)为任何 Web 进程分配任务管理的唯一所有权,因为您永远不知道哪个 Dyno 将首先关闭以及在生成新的 Dyno 之前多长时间。
即:
另一方面,您的任务似乎已经在持久存储中。
如果任务是使用数据库事务签出的,或者(对于非 SQL 存储)使用单线程数据库(例如带有 List 的 Redis),您可能会让所有 Dynos 执行任务,而不会执行任何任务超过一次。
...
另一个因简单性和可管理性而备受青睐的选择是使用单个工作人员 Dyno 来执行后台任务。这个 Dyno 将运行一个单独的脚本,可能会与 web dynos 共享一些代码(也可能不会)。
如果我为多个网络 Dynos 支付 Heroku,那么只有为这些任务部署一个工作人员 Dyno 才有意义。
EDIT(你可以忽略这个,这只是一些想法)
尽管最好的解决方案可能是使用单个工作人员 Dyno,但这里还有一些想法。
运行时标志
我想到了这个想法,因为您暗示能够缩小到单个 Dyno 会很好......
您可以使用在Procfile 中设置的运行时标志,这将告诉特定 Dyno 处理任务或不处理任务。在运行时,您可以检查 ARGV 数组中的标志。
这里是一个实验性的例子 Procfile 与 Rails 应用程序的这种区别:
web: bundle exec rails server -p $PORT run_tasks
onlyweb: bundle exec rails server -p $PORT
缩放时,确保只缩放运行 Dynos 的非任务:
heroku ps:scale web=1 onlyweb=4
在您的应用程序中,您可以在运行时检查:
if ARGV.include? 'run_tasks'
# initialize tasks for background handling.
end
用于激活任务处理的 API
另一种可能性是使用Kernel.at_exit 和 HTTP API 调用的组合。
这个方案有点像设置“标签”的数字游戏,大概应该包括以下几个步骤:
-
创建一个接受“标签”的 HTTP 路由。收到此类请求后,接收 Dyno 将成为执行 Dyno 的任务(将变为“IT”),初始化他们的任务堆栈并将其 UUID 存储在数据库中(您可以为此使用require 'securerandom'; SecureRandom.uuid)。
在每个任务之间,让“IT”测功机通过检查其 UUID 是否仍然是数据库中的 UUID 来检查它是否仍然是“IT”。
创建一个初始化序列,其中最后一个网络 Dyno 接管任务处理(新玩家总是变成“IT”)。
使用Kernel.at_exit 设置回调,如果退出的 Dyno 是执行 Dyno(“IT”)的任务,它将尝试“标记”新的 Dyno...
这些步骤应确保始终存在执行 Dyno 的任务,即使在动态放大或缩小时也是如此。
但是,现在我想到了,这可能被认为是初始事务概念的变体,因为对 UUID 的审查和从任务堆栈中签出的任务可能都发生在事务中:-/