【问题标题】:Rails Concurrency issue lockupRails 并发问题锁定
【发布时间】:2018-06-24 21:19:26
【问题描述】:

我有一个 Rails 应用程序,其中一个操作永远不会完成然后超时。 查找下图以获得更好的说明。

  1. 我的 rails apps 操作被调用
  2. 该操作将一些数据发布到另一个应用程序
  3. 另一个应用程序需要一些东西来完成计算并调用与第一个 Rails 应用程序不同的操作
  4. 其他应用收到响应并完成计算
  5. 其他应用响应 Rails 应用的 POST 请求
  6. 相应地呈现视图

现在的问题:其他应用永远不会得到主应用的响应。然而,在 Rails 应用程序请求超时后,会发送响应(当然为时已晚),所以我认为它以某种方式被暗示了。

我不明白如何解决这个问题。我使用应该能够处理并行调用的 rails 5 和 Puma。这也不是本地问题,在产品中也会发生同样的情况。

我使用 Heroku 推荐的 puma.rb 配置

workers Integer(ENV['WEB_CONCURRENCY'] || 2)
threads_count = Integer(ENV['RAILS_MAX_THREADS'] || 5)
threads threads_count, threads_count

preload_app!

rackup      DefaultRackup
port        ENV['PORT']     || 3000
environment ENV['RACK_ENV'] || 'development'

on_worker_boot do
  # Worker specific setup for Rails 4.1+
  # See: https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server#on-worker-boot
  ActiveRecord::Base.establish_connection
end

我该怎么做才能解决这个提示?

控制器:

    # New method
    def live_preview_page
      preview_locale = params[:preview_locale]
      date = params[:date] # The date to preview
      page_id = params[:id]
      return if locale.nil? || locale =~ /not/ || date.nil?
      all_templates = Template.all.order('name ASC') # Maybe move to render_live_editor_page
      if date == "all"
        active_modules = @page.page_modules.order(rank: :asc)
      else
        active_modules = @page.page_modules.order(rank: :asc).to_a.valid_for(date: date.to_date)
        puts "Active modules: #{active_modules.count}"
      end
      active_modules_json = active_modules.each do |content_module|
        content_module.body = YAML.load(content_module.body).to_json
      end
      response = helpers.render_preview(active_modules, all_templates, preview_locale)
      renderer = ContentRenderer.new
      actionController = ActionController::Base.new
      rendered_helper = actionController.render_to_string(
        partial: '/pages/preview-helper-snippet', locals: {
        all_templates: all_templates, # For select when creating new modules
        modulesData: active_modules_json, # For rendering the JSON containing the data for the editor
        current_page: @page.id,
        localeLinks: renderer.generateStgPreviewURLs(SettingService.get_named_locales, @page.id),
        locale: preview_locale,
        all_locales: SettingService.locales_for_live_editor,
        all_sites_and_locales: SettingService.get_sites_and_locales
      })
      proxy_service = ProxyService.new
      proxy_service.get_page do |error, page_wrapper|
        # Note: Issue is that Vapor app generates warnings inline template : encountered \r in middle of line, treated as a mere space
        rendered_body_with_helper = response.body.force_encoding("UTF-8") + rendered_helper
        decorated_page = page_wrapper.gsub("__WIDGET__", rendered_body_with_helper)
        render inline: decorated_page
        return
      end

    end

助手

  def render_preview(active_modules, all_templates, preview_locale)
    req = Request.new
    preview_body = {
      modules: active_modules,
      templates: all_templates,
      sites: SettingService.get_sites,
      configuration: {
        locale: preview_locale,
        site: "DE"
      }
    }
    req.send_request(
      url: "#{ENV["RENDER_SERVICE_URL"]}/preview",
      body: preview_body,
      options: {
        type: :post,
        json: true,
        username: ENV["RENDER_SERVICE_BASIC_AUTH_USERNAME"],
        password: ENV["RENDER_SERVICE_BASIC_AUTH_PASSWORD"]
      }
    ) do |response_code, response|
      return response
    end
  end

请求只是一个薄包装

  require "uri"
  require "net/http"

  class Request
    # Yields resonse_code (int), response
    # Parameters besides url: are optional
    def send_request(url:, body: {}, header: {}, options: {})
      uri = URI.parse(url)
      http = Net::HTTP.new(uri.host, uri.port)
      if options.key? :type
        case options[:type]
        when :get
          request = Net::HTTP::Get.new(uri.request_uri, header)
        when :post
          request = Net::HTTP::Post.new(uri.request_uri, header)
        end
      else
        # Default is GET
        request = Net::HTTP::Get.new(uri.request_uri, header)
      end
      if options.key?(:username) && options.key?(:password)
        request.basic_auth options[:username], options[:password]
      end
      unless body.class == String
        body = body.to_json.to_s
      end
      request.body = body unless body.empty?
      puts request.body
      # SSL is default
      if options.key? :ssl
        http.use_ssl = options[:ssl]
      else
        http.use_ssl = Rails.configuration.force_ssl
        #http.verify_mode = OpenSSL::SSL::VERIFY_NONE
      end
      if options.key? :json
        request.add_field("Content-Type", "application/json")
      end
      response = http.request(request)
      yield response.code.to_i, response
    end
  end

【问题讨论】:

  • 问题不太可能出在 Puma 配置中。您能否粘贴负责处理与其他应用程序通信的控制器/服务的代码
  • @KartikeyTanna 编辑了问题
  • 我认为这是我的问题:github.com/puma/puma/issues/1433 但我不知道如何解决它:( 我怎样才能转移到另一个工人或其他什么?
  • 尝试增加工人的数量。将它们设置为 5 或其他值。它可能会减少出错的机会

标签: ruby-on-rails concurrency puma


【解决方案1】:

以下答案(可能)不是您想要的答案 - 但它是您需要的答案:

解决此问题的最佳方法是避免请求/响应逻辑中的循环(rails 应用通过其他应用调用自身)。

并发可能有助于延迟问题的发生,但只要循环存在,问题就会一直发生。

例如,假设您有 100 个来自客户端的 Rails 应用请求。

Rails 将调用另一个应用程序,另一个应用程序的请求将作为请求号 101 排队。

这可以用 100 个线程来解决(例如,10 个工人,每个工人有 10 个线程)...

但是您的应用会如何处理 200 个客户端请求?

这个循环是无止境的,您拥有的客户端越多,您在体验 DoS 之前需要的并发性就越多。

唯一的解决办法是一开始就避免循环。

要么将其拆分为 3 个应用程序,要么(更好)避免微服务之间的依赖关系。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-10-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多