生产Rails 3应用程序每分钟不能处理200多个请求

时间:2014-06-06 19:29:03

标签: ruby-on-rails ruby-on-rails-3.2

我有一个每天停机几次的Rails生产应用程序。此应用程序除了为其用户提供服务外,还是发送更新的第三方网站的端点。

有时,这些更新会迅速发生,以至于请求备份和应用程序长时间不可用。这是一种合法用法,最终导致拒绝服务。

来自第三方的请求非常简单:

class NotificationsController < ApplicationController

  def notify
    begin
      notification_xml = request.body.read
      notification_hash = Hash.from_xml(item_response_xml)['Envelope']['Body']['NotificationResponse']
      user = User.find(notification_hash['UserID'])
      user.delay.set_notification(notification_hash)
    rescue Exception => bang
      logger.error bang.backtrace
      unless user.blank?
        alert_file_name = "#{user.id}_#{notification_hash['Message']['MessageID']}_#{notification_hash['NotificationEventName']}_#{notification_hash['Timestamp']}.xml"
        File.open(alert_file_name, 'w') {|f| f.write(notification_xml) }
      end
    end
    render nothing: true, status: 200
  end

end

我有两个针对非常大型数据库的应用服务器。但是,当这个第三方网站确实向我们发出通知请求时,每分钟超过200个,每分钟接近1,000个请求,这两个网络服务器完全被束缚了。

您还可以看到上面我正在使用.delay调用,因为我使用的是Sidekiq。我认为这会有所帮助,但它确实存在了一段时间,但应用程序无法处理那么多请求。

除了在单独的应用程序中处理请求之外,我不确定在我的EngineYard安装中是否真的有可能,我可以做些什么来加快处理此请求?

2 个答案:

答案 0 :(得分:2)

如果处理所有这些请求需要太多,请尝试不同的方法。

创建一个新模型(我称之为请求),只有一个字段(我将其命名为消息) - 由此发送给您的xml第三方应用。

重写您的通知操作非常简单快捷:

def notify
  Request.create(message: request.body)
  render nothing: true, status: 200
end

创建一个新动作,让我们说像这样的process_requests:

def process_requests
  Request.order('id ASC')find_in_batches(100) do |group|
    group.each do |request|
      process_request(request)
      request.destroy
    end
  end
end

def process_request(notification_xml)
  begin
    notification_hash = Hash.from_xml(item_response_xml)['Envelope']['Body']['NotificationResponse']
    user = User.find(notification_hash['UserID'])
    user.set_notification(notification_hash)

  rescue Exception => bang
    logger.error bang.backtrace

    unless user.blank?
      alert_file_name = "#{user.id}_#{notification_hash['Message']['MessageID']}_#{notification_hash['NotificationEventName']}_#{notification_hash['Timestamp']}.xml"
      File.open(alert_file_name, 'w') {|f| f.write(notification_xml) }
    end
  end

创建一个cron并以定义的间隔(几分钟)调用process_requests。 我从来没有使用过Sidekiq,所以我更喜欢使用find_in_batches(我只是为了举例而使用了100个结果)。

notify操作不应该运行超过几毫秒(插入速度非常快),因此应该能够在关键时刻处理传入流量。

如果你尝试类似的东西,它可以帮助你的服务器减少关键时刻的负载,请告诉我:D

如果这个有用并且你也在这里插入后台处理,请发布给其他人看看。

答案 1 :(得分:1)

如果您使用New Relic / AppNet /其他方式监控此应用,检查您的报告可能会让您了解一些长期存在的成果。我们这里的应用程序只有一张小图片;它的可能应用程序中其他地方的增强功能也可能有所帮助。

话虽如此,这里有一些可以单独或一起应用的想法:

减少摄入量

现在,在将作业传递给Sidekiq之前,您正在进行大量的XML处理 - 这很昂贵。这是一个瓶颈,并且通过在应用程序流程中运行,它会占用您的应用程序。

如果您的Redis实例有足够的内存,请考虑重构notify,以便将整个XML有效负载传递给Sidekiq。您已经总是向API使用者返回200响应,因此对您现有的外部API没有任何影响。

然后,您的工作实例可以按照自己的进度处理XML有效负载,而不会影响应用程序。

实施API限制

第三方网站正以极大的速度向您发起攻击,即使是大型网站也是如此。这是一个问题。

如果你不能让他们在他们的最后解决它,就像大狗一样玩:在你的头上实施请求限制。您可能某些能够在EngineYard上的Rack级别执行此操作(虽然快速搜索他们的文档并没有立即产生任何结果),但即使在应用程序级别执行此操作也很可能改善一切。

有一个previous Stack Overflow discussion可能会提供一些选项。

代理API

存在一些代理API的服务,允许您轻松实现速率限制,限制和配额等功能,否则可能难以添加。

我最熟悉的是Azure的API Management service。如果这不是一个创收项目,那么成本可能会高得惊人。 (后付49美元/月,虽然预付费会更便宜,如果你有资格使用BizSpark,甚至可以免费。)

将API输出

更高级的API代理表亲,&#34; API即服务&#34;实际上,您可以在自己的VM实例上运行API,并提供代理功能。如果您的数据库不是一个阻塞点,这可以是一种分散负载并帮助防止机器客户端影响人类客户体验的方法。

一万磅的大猩猩是Apigee,虽然还有其他各种已建立和启动的选择。

有一个问题:大多数这些服务是围绕Node.js构建的。如果您的Rails应用程序已经倾向于面向服务的体系结构,并且如果您知道并喜欢JavaScript,那么这对您来说可能不是问题。否则,需要在服务之间建立接口并以第二语言维护服务可能是一个过头的桥梁。