【问题标题】:How can I get around Heroku's HTTP 30 second limit?如何绕过 Heroku 的 HTTP 30 秒限制?
【发布时间】:2019-10-08 07:40:41
【问题描述】:

我继承了一个使用 Heroku 部署的 Rails 应用程序(我认为)。我在 AWS 的 Cloud9 IDE 上对其进行了编辑,现在只在开发模式下进行所有操作。该应用程序的目的是处理大量调查数据并将其输出到 PDF 报告中。这适用于具有 10 行数据的小型报表,但是当我加载一个查询 5000 多行数据上传的报表以创建一个转换为 PDF 的 HTML 页面时,大约需要 105 秒,比 Heroku 的要长得多为 HTTP 请求分配 30 秒。

Heroku 在他们的网站上这样说,这给了我一些希望:

“Heroku 支持 HTTP 1.1 功能,例如长轮询和流式响应。应用程序有一个初始的 30 秒窗口以将单个字节返回给客户端。但是,此后传输的每个字节(从客户端接收或由您的应用程序发送)重置滚动的 55 秒窗口。如果在 55 秒窗口期间没有发送数据,则连接将终止。 (来源:https://devcenter.heroku.com/articles/request-timeout#long-polling-and-streaming-responses

这对我来说听起来很棒 - 我可以每隔一秒左右循环向客户端发送一个请求,直到我们完成创建大型 PDF 报告。但是,我不知道如何发送或接收一个字节左右来“重置滚动的 55 秒窗口”他们正在谈论。

这是我的控制器发送请求的部分。

            return render pdf: pdf_name + " " + pdf_year.to_s,
                disposition: 'attachment',
                page_height: 1300,
                encoding: 'utf8',
                page_size:   'A4',
                footer: {html: {template: 'recent_grad/footer.html.erb'}, spacing: 0 },
                margin:  {   top:    10,                     # default 10 (mm)
                            bottom: 20,
                            left:   10,
                            right:  10 },
                template: "recent_grad/report.html.erb",
                locals: {start: @start, survey: @survey, years: @years, college: @college, department: @department, program: @program, emphasis: @emphasis, questions: @questions}

我正在提出其他请求以达到这一点,但我相信导致问题的部分在这里呈现模板。我的模板在一个有限循环中查询数据库,当它用完要查询的调查问题时停止。

我的问题是:我如何“向客户端发送或接收字节”来告诉 Heroku“我仍在尝试创建这个庞大的 PDF,所以请重置计时器并给我 55 秒!”是查询的形式吗?因为,如果是这样,我会在我的 report.html.erb 文件中一遍又一遍地查询 MySql 数据库。

此外,它过去可以正常工作并且可以处理小型报告,但现在我在实际页面上的请求完成之前收到错误“504 Gateway Timeout”,但我的 puma 控制台继续像查询数据库一样查询数据库疯子。我认为这是 Heroku 问题,因为 504 错误恰好每 35 秒发生一次(5 秒处理其他部分,30 秒尝试完成模板中的循环,以便正确渲染)。

如果您需要更多信息或代码,请询问!提前致谢

编辑: 下面的两个 cmets 都建议了可能的重复项,但是他们都没有真正的代码的真正答案,他们只是参考我在这里引用的文档。我正在寻找一个代码示例(或者至少是一种让我踏上大门的方法),而不仅仅是指向文档的链接。谢谢!

编辑2:

我尝试了@Sergio 所说的并安装了 SideKiq。我想我真的很接近,但仍然与工人有一些问题。工作人员无权访问 Rails 中的渲染方法所需的 ActionView::Base,因此它不起作用。我可以访问 worker 方法,这意味着我的 sidekiq 和 redis 服务器运行正常,但它在 ActionView 行中被捕获并出现以下错误:

警告:NameError:未初始化的常量 HardWorker::ActionView

这里是工人代码:

require 'sidekiq'

Sidekiq.configure_client do |config|
  # config.redis = { db: 1 }
  config.redis = { url: 'redis://172.31.6.51:6379/0' }
end

Sidekiq.configure_server do |config|
  # config.redis = { db: 1 }
  config.redis = { url: 'redis://172.31.6.51:6379/0' }
end  

class HardWorker
  include Sidekiq::Worker
  def perform(pdf_name, pdf_year)
    av = ActionView::Base.new()
    av.view_paths = ActionController::Base.view_paths
    av.class_eval do
      include Rails.application.routes.url_helpers
      include ApplicationHelper
    end
    puts "inside hardworker"
    puts pdf_name, pdf_year

    av.render pdf: pdf_name + " " + pdf_year.to_s,
                disposition: 'attachment',
                page_height: 1300,
                encoding: 'utf8',
                page_size:   'A4',
                footer: {html: {template: 'recent_grad/footer.html.erb'}, spacing: 0 },
                margin:  {   top:    10,                     # default 10 (mm)
                            bottom: 20,
                            left:   10,
                            right:  10 },
                template: "recent_grad/report.html.erb",
                locals: {start: @start, survey: @survey, years: @years, college: @college, department: @department, program: @program, emphasis: @emphasis, questions: @questions}
  end
end


有什么建议吗?

编辑 3: 我按照@Sergio 所说的去做,并尝试直接从 html.erb 文件制作 PDF 并将其保存到文件中。这是我的代码:

# /app/controllers/recentgrad_controller.rb

pdf = WickedPdf.new.pdf_from_html_file('home/ec2-user/environment/gradSurvey/gradSurvey/app/views/recent_grad/report.html.erb')
            save_path = Rails.root.join('pdfs', pdf_name + pdf_year.to_s + '.pdf')
            File.open(save_path, 'wb') do |file|
              file << pdf
            end

以及错误输出:

RuntimeError (Failed to execute:
["/usr/local/rvm/gems/ruby-2.4.1@gradSurvey/bin/wkhtmltopdf", "file:///home/ec2-user/environment/gradSurvey/gradSurvey/app/views/recent_grad/report.html.erb", "/tmp/wicked_pdf_generated_file20190523-15416-hvb3zg.pdf"]
Error: PDF could not be generated!
 Command Error: Loading pages (1/6)
Error: Failed loading page file:///home/ec2-user/environment/gradSurvey/gradSurvey/app/views/recent_grad/report.html.erb (sometimes it will work just to ignore this error with --load-error-handling ignore)
Exit with code 1 due to network error: ContentNotFoundError
):

我不知道当它说“有时它会通过 --load-error-handling ignore 忽略此错误”时是什么意思。该文件肯定存在,我已经尝试了文件路径的 5 种变体。

【问题讨论】:

  • 参见上面的编辑,我正在寻找如何做到这一点,而不仅仅是在文档中的何处找到它们。似乎其他两个帖子只是将提问者指向我在问题中引用的文档。
  • “我的模板在一个有限循环中查询数据库,当它用完要查询的调查问题时停止。” - 我在这里闻到一箱 N+1
  • 当我从 ApplicationJob 继承一份工作而不是工作人员时,结果证明它起作用了,尽管它们本质上是一样的。这是我的继承:class CreatePdfJob &lt; ApplicationJob。这里真正的赢家是@unixmonkey,他为rails 维护wicked_pdf,他为我设置了一个工作应用程序。您可以在此处查看问题:github.com/mileszs/wicked_pdf/issues/835 可以在此处查看真正改变其工作方式的提交:github.com/unixmonkey/generate_pdf_async_example/commit/... 享受!

标签: ruby-on-rails heroku cloud9-ide puma


【解决方案1】:

我不得不多次这样做。在所有情况下,我最终都编写了一个后台作业来完成所有繁重的 lifting 生成。并且因为它不是网络请求,所以不受 30 秒超时的影响。它是这样的:

  1. 客户端(您的 javascript 代码)请求新报告。
  2. 服务器生成职位描述并将其排入队列以供您的员工领取。
  3. worker 从队列中挑选作业并开始工作(查询数据库等)
  4. 同时,客户端定期询问服务器“我的报告完成了吗?”。服务器响应“还没有,稍后再试”
  5. worker 已完成生成报告。它将文件上传到某个存储(例如 S3),将作业状态设置为“已完成”,并将作业结果设置为已上传报告文件的下载链接。
  6. 服务器,看到该工作已完成,现在可以响应客户端状态更新请求“是的,现在完成了。这是 url。祝你有美好的一天。”
  7. 大家都很开心。并且没有人需要对 heroku 的滚动响应超时进行任何流式处理或播放。

上面的场景使用短轮询。我发现它最容易实现。但是,当然,在资源方面有点浪费。您可以使用长轮询或 websocket 或其他花哨的东西。

【讨论】:

  • 感谢塞尔吉奥的回复。我理解这个概念,但不知道如何实现它。我如何告诉工人该做什么?什么是工人?抱歉我的菜鸟问题,我对 Rails 和一般开发还是很陌生。
  • @Parzival:“我如何告诉工人该做什么?” - 它叫做ActiveJob。您可以(并且应该)为 activejob 使用另一个后端(例如 sidekiq
  • 似乎一切都指向sidekiq,所以我会尝试一下,让你知道发生了什么。感谢您迄今为止的帮助!
  • 我已经很接近了(sidekiq 正在运行,我可以使用该方法),但是在尝试使用 render 方法时仍然出现错误,因为我在工作中。请参阅上面的“编辑 2”。有什么想法吗?
  • 您能否创建文件,将其保存在服务器上,将其发送回用户/访问者,然后在将来的某个时间点将其删除?即设置适当的标题以强制文件下载。
【解决方案2】:

检查my response here,以防万一它适合你。我不想更改用户工作流程,添加一个 bg 作业,然后添加一个位置/通知来获得结果。 我使用带有 Live 模块的 Rails 控制器流支持并设置正确的响应标头。我从某个 Enumerable 对象中获取数据。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-29
    • 2021-11-28
    • 1970-01-01
    • 1970-01-01
    • 2016-09-08
    • 2014-09-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多