【问题标题】:Consume external API and performance使用外部 API 和性能
【发布时间】:2020-12-28 12:09:07
【问题描述】:

我正在开发一个 Ruby on Rails 项目,该项目通过一些外部 API 使用数据。

此 API 允许我获取汽车列表并将它们显示在我的单个网页上。

我创建了一个模型,其中包含与此 API 相关的所有方法。 控制器使用模型中的 list_cars 方法将数据转发到视图。

这是专用于 API 调用的模型:

class CarsApi
  @base_uri = 'https://api.greatcars.com/v1/'

  def self.list_cars
    cars = Array.new
    response = HTTParty.get(@base_uri + 'cars',
                            headers: {
                              'Authorization' => 'Token token=' + ENV['GREATCARS_API_TOKEN'],
                              'X-Api-Version' => ENV["GREATCARS_API_VERSION"]
                            })

    response["data"].each_with_index do |(key, value), index|
      id = response["data"][index]["id"]
      make = response["data"][index]["attributes"]["make"]
      store = get_store(id)
      location = get_location(id)
      model = response["data"][index]["attributes"]["model"]

      if response["data"][index]["attributes"]["status"] == "on sale"
        cars << Job.new(id, make, store, location, model)
      end
    end

    cars
  end

  def self.get_store(job_id)
    store = ''
    response_related_store = HTTParty.get(@base_uri + 'cars/' + job_id + "/relationships/store",
                                               headers: {
                                                 'Authorization' => 'Token token=' + ENV['GREATCARS_API_TOKEN'],
                                                 'X-Api-Version' => ENV["GREATCARS_API_VERSION"]
                                               })

    if response_related_store["data"]
      store_id = response_related_store["data"]["id"]
      response_store = HTTParty.get(@base_uri + 'stores/' + store_id,
                                         headers: {
                                           'Authorization' => 'Token token=' + ENV['GREATCARS_API_TOKEN'],
                                           'X-Api-Version' => ENV["GREATCARS_API_VERSION"]
                                         })
      store = response_store["data"]["attributes"]["name"]
    end

    store
  end

  def self.get_location(job_id)
    address, city, country, zip, lat, long = ''
    response_related_location = HTTParty.get(@base_uri + 'cars/' + job_id + "/relationships/location",
                                               headers: {
                                                 'Authorization' => 'Token token=' + ENV['GREATCARS_API_TOKEN'],
                                                 'X-Api-Version' => ENV["GREATCARS_API_VERSION"]
                                               })
    if response_related_location["data"]
      location_id = response_related_location["data"]["id"]
      response_location = HTTParty.get(@base_uri + 'locations/' + location_id,
                                         headers: {
                                           'Authorization' => 'Token token=' + ENV['GREATCARS_API_TOKEN'],
                                           'X-Api-Version' => ENV["GREATCARS_API_VERSION"]
                                         })
      if response_location["data"]["attributes"]["address"]
        address = response_location["data"]["attributes"]["address"]
      end
      if response_location["data"]["attributes"]["city"]
        city = response_location["data"]["attributes"]["city"]
      end
      if response_location["data"]["attributes"]["country"]
        country = response_location["data"]["attributes"]["country"]
      end
      if response_location["data"]["attributes"]["zip"]
        zip = response_location["data"]["attributes"]["zip"]
      end
      if response_location["data"]["attributes"]["lat"]
        lat = response_location["data"]["attributes"]["lat"]
      end
      if response_location["data"]["attributes"]["long"]
        long = response_location["data"]["attributes"]["long"]
      end
    end

    Location.new(address, city, country, zip, lat, long)
  end
end

加载我的主页需要 1 分 10 秒! 我想知道是否有更好的方法来做到这一点并提高性能。

【问题讨论】:

  • 你每次发出请求时都直接调用这个list_cars吗?由于您已经创建了一个模型来保存 api 响应,我建议您编写一个后台任务来填充汽车模型。你的 api 可以从你的数据库中获取数据
  • 它不是一个 ActiveRecord 模型,而是一个普通的旧 Ruby 对象,所以是的,似乎每个请求都会调用它。

标签: ruby-on-rails ruby


【解决方案1】:

加载我的主页需要... 1 分 10 秒!我想知道是否有更好的方法来做到这一点并提高性能。

如果您想提高某段代码的性能,您应该始终做的第一件事就是添加一些工具。看起来你已经有了一些指标,加载整个页面需要多长时间,但你现在需要弄清楚什么实际上需要很长时间。有许多很棒的宝石和服务可以帮助您。

服务:

宝石

N+1 请求

一个缓慢的假设可能是您为每辆车执行了四个额外的请求来获取商店和位置。这意味着如果您在主页上显示 10 个工作,您将需要执行 50 个 API 请求。即使每个请求只需要约 1 秒,也几乎是一分钟。

一个简单的想法是将附加资源缓存在查找表中。我不确定有多少工作会共享同一个商店和位置,但不确定它实际上安全多少。

这可能是一个简单的查找表,例如:

class Store
  cattr_reader :table do
   {}
  end

  class << self
    def self.find(id)
      self.class.table[id] ||= fetch_store(id)
    end

    private
    
    def fetch_store(id)
      # copy code here to fetch store from API
    end
  end
end

这样,如果多个工作具有相同的位置/商店,您只需执行一个请求。

延迟加载

这取决于页面的设计,但您可以延迟加载位置和商店等其他信息。

许多页面要做的一件事是显示占位符或dominant colour,然后使用 Java Script 延迟加载更多内容。

另一个想法可能是在滚动或悬停时加载存储和位置,但这在一定程度上取决于您的页面设计。

分页/限制

也许您还从 API 请求了太多项目。查看 API 是否有一些选项来限制您请求的项目数量,例如https://api.greatcars.com/v1/cars/limit=10&amp;page=1

但如前所述,即使这仅限于 10 个项目,您最终也会收到总共 50 个请求。因此,在您解决第一个问题之前,这不会产生太大影响。

一般缓存

一般来说,我认为总是为页面收到的每个请求发送 API 请求并不是一个好主意。您可以引入一些缓存,例如仅每 x 分钟/小时/天执行一次 API 请求。

这可以像存储在类变量中或使用 memcached / Redis / 数据库一样简单。

【讨论】:

  • 不应缓存请求(这意味着向服务器发出的第一个请求将花费与当前相同的时间),而应将数据保存在后台进程中。
  • 这仍然是某种缓存,但绝对有可能。但是,存储在数据库中会使缓存失效变得更加困难。
  • which would mean that the first request to the server would take as much time as it is currently taking 不一定能改进 n+1 查询。我不希望每辆车都在不同的商店,应该有一些重叠并大大减少所需的请求数量。
  • 持久性并不一定意味着关系数据库内部的持久性。我假设鉴于他的应用程序的性质,即使请求数量减少,他也需要 n+1 个查询以在某种搜索中显示整个数据。缓存第一个请求就足够了吗?例如,如果第一个请求需要 1 分钟,是否可以接受?这完全取决于对用户的响应应该有多快。我假设 OP 需要服务器尽快回答。在这种方法中,我会选择在后台“缓存”(不管在哪里)作为一种悲观的措施。
  • 首先感谢克里斯蒂安的建议!我发现的一件重要的事情是 API 实际上支持允许在响应中包含额外数据的参数。这立即将加载所有信息的时间从 1 分 10 秒减少到 2 秒......但即使 2 秒仍然太慢,所以我按照你的建议将数据缓存在数据库中。数据不是那么重要,因此每隔一小时更新一次。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-14
相关资源
最近更新 更多