【问题标题】:How to handle reused object in rails application如何在 Rails 应用程序中处理重用对象
【发布时间】:2014-12-05 20:50:40
【问题描述】:
  • 注意:请注意确定这是否是问题的最佳标题/接受建议以编辑未来值 *

我有一个多租户 Rails 应用程序,它允许客户使用他们自己的自定义 TLD。所以我可以:

www.clientA.com

www.clientB.com

www.clientC.com

等等……

无论好坏,我的数据库 (postgres) 都有一个租户表,其中大约有 60 列,每个租户都有各种设置和配置。有些只是标志,有些是大文本值。

application_controller.rb 中,我有一些逻辑来解析URL,根据域查询租户表并实例化@current_tenant 对象。 @current_tenant 对象在页面的整个生命周期中都可用。这发生在每个。单身的。页。我的应用程序。

虽然我有一些很重的缓存,但这种设计对我来说仍然非常错误,我相信它可以改进。

对此有最佳实践吗?处理 @current_tenant 对象的最佳模式是什么?我担心内存管理。

【问题讨论】:

  • 您如何处理 SSL?我认为您会做与子域类似的事情。看看这个 railscast。 railscasts.com/episodes/221-subdomains-in-rails-3
  • 只要有 ssl,TLD 域就会更改为具有通配符 SSL 的 clientname.myappdomain.com。在那种情况下,我仍在查找并加载 @current_tenant 对象。
  • 我认为答案取决于您使用的服务器类型。例如,Passenger 可能会为每个子域创建单独的进程。如果您使用 Nginx 作为 Puma 或 Unicorn 的代理,您可以将每个请求引导到同一个应用程序实例,这将更容易共享内存访问。我不确定您要缓存什么样的东西(复杂的对象或只是一些字符串来配置您的应用程序等 - 也许一些代码示例会有所帮助),但更通用的方法是使用 Memcached 或Redis 在实例之间共享您的缓存值。
  • 我正在使用 Unicorn(在 Heroku 上)。我想我的问题/担心是我在每个页面请求上加载 @current_tenant 对象,它上面有大约 60 个属性、一些标志、一些大文本值。是否有处理此类问题的最佳做法?

标签: ruby-on-rails postgresql design-patterns activerecord architecture


【解决方案1】:

您的 current_tenant 与 Rails 应用程序中无处不在的 current_user 没有什么不同。

停止微优化。除非您对其进行了基准测试,并且它表明它是一个主要瓶颈(我可以向您保证,它不是)。

任何微小的性能改进(如果有的话)都将被增加的代码复杂性、缓存问题等等所抵消。

做。不是。做。那。 ;)

但有一件事,不要执行分配 @current_tenant 的 before_filter,而是将 current_tenant 方法添加到 application_controller(或其关注点之一),该方法将为请求的其余部分缓存结果:

def current_tenant
  @current_tenant ||= ....
end

【讨论】:

  • 你能澄清你最后的陈述吗?我在application_controller 上有一个before_filter :set_current_tenant。它不在我的应用程序的每个控制器上。
  • 对上述评论有什么想法吗? @vitaly
  • 创建一个 lazy 辅助方法来缓存结果而不是 before_action 来“预先设置”它几乎总是更好。几个原因: - 这样更容易,你甚至不需要考虑哪个动作需要它,哪个不需要。您只需使用它,如果它还没有运行查询,它会“此时”执行它,如果您的操作没有调用它,则查询不会运行。 - 缓存。例如,如果您从缓存片段中需要它,那么如果您使用before_action 执行此操作,即使您有缓存命中,它也会执行查询。
【解决方案2】:

您也可以使用 Rails 缓存缓存 @current_tenant 对象,即:

def current_tenant( host = request.host )
  Rails.cache.fetch(host, expires_in: 5.minutes) do
    Tenant.find_by(tld: host)
  end
end

【讨论】:

    【解决方案3】:

    (注意:我已经 3 年没有接触过 Rails,所以请用适当的盐来回答这个问题。)

    对此有最佳实践吗?处理@current_tenant 对象的最佳模式是什么?

    我见过实现这种东西的最佳方式是使用 PHP,而不是 Ruby on Rails。更具体地说,在 Symfony 框架中。

    简而言之,Symfony 应用的布局是这样的:

    /app    <-- app-specific Kernel, Console, config, cache, logs, etc.
    /src    <-- app-specific source files
    /vendor <-- vendored source files
    /web    <-- public web folder for the app
    

    要从同一个代码库运行多个应用程序,您基本上可以这样做:

    /app
    /app2
    /...
    /src
    /vendor
    /web
    /web2
    /...
    

    ...然后将每个域指向不同的 /web 文件夹。

    据我了解,可以将 Rail 项目的目录结构重新组织为或多或少等效的结构。请特别查看此相关答案:

    https://stackoverflow.com/a/10480207/417194

    理论上,您可以从那里为每个租户启动一个应用实例,每个实例都有各自的资源、硬编码的默认值等,同时继续使用共享代码库。

    下一个最佳选择是,imo,您当前正在做什么。使用 memcached 或等效项快速将特定域名映射到租户,并在必要时回退到数据库查询。我想你已经在这样做了,因为你“有一些大量的缓存”。

    顺便说一句,您可能会发现这个相关问题以及其中相关的问题很有趣:

    Multiple applications using a single code base in ruby

    (而且 FWIW,我最终没有坚持使用 PHP 来开发多租户应用程序。上次我使用 Ruby 或 Rails 时没有灵丹妙药。)

    关于内存使用,我认为不必太担心:严格来说,您是在每次请求中查找并创建一次租户对象。即使这样做非常慢,粗略地监控您的应用程序实际花费的时间会发现,与每次请求运行无数次的看似无害的代码片段相比,它可以忽略不计。

    【讨论】:

      【解决方案4】:

      我设计的应用程序基本上完全按照您的描述进行。由于您正在加载单个对象(即使在每个页面请求上),只需确保查询仅返回一行(当前租户)并且不会进行大量的连接。应用了 LIMIT 的单行查询不会让您的网站崩溃,即使每秒请求数百次也是如此。无论如何,如果您获得这种类型的流量,无论如何您都必须扩展您的服务器。

      可以帮助您做的一件事是确保您的搜索列已在数据库中编入索引。如果您通过 url 查找当前租户,请索引 url 列。

      这是我所做的一个例子。我对变量进行了全球化,因此这些信息在所有控制器/模型/视图中都可用。

      在我的应用程序控制器中:

      before_filter :allocate_site
      
      
      private
      
      def allocate_site
      
        url = request.host_with_port
        url.slice! "www."
      
        # load the current site by URL
        $current_site = Site.find_by({:url => url, :deleted => false})
      
        # if the current site doesn't exist, we are going to create a placeholder
        # for the URL hitting the server. 
        if $current_site.nil?
          $current_site = Site.new
          $current_site.name = 'New Site'
          $current_site.url = url
      
          $current_site.save
      
        end
      end
      

      【讨论】:

      • 谢谢!将其设为 $current_site 与 @current_site 有什么好处吗?如果单行相当宽(60 列,一些文本类型),我应该担心内存管理吗?
      • $current_site 使它成为一个全局变量。这个变量可以在控制器、模型和视图中访问,而无需调用它。如果您使用@current_site,这是一个实例变量。它仅适用于从 ApplicationController 继承的控制器(因此也适用于视图)。您需要将变量传递给模型。至于性能,我认为您只需要运行一些测试即可。我的站点对象包含大约 30 个属性,我没有注意到任何速度问题(没有一个比 rails 已经做的更糟糕了哈哈)。
      • 除此之外,如果您担心不断的数据库拉取,agios 的响应会告诉您如何缓存它。唯一的区别是,我会根据对象 updated_at 属性使缓存过期,这样只有在记录更改时才需要重建缓存。
      • 我应该补充一下,我选择了 $ 而不是 @,只是因为我有一些需要运行的进程总是在 $current_site 范围内。我可以很容易地将 @current_site 传递给模型,而不是使用全局 $current_site。您需要确定最适合您和您的业务需求的方法。
      【解决方案5】:

      如果您不介意为您的应用程序添加额外的 gem,我建议您使用 apartment。这样做正是为了满足您的目的:处理具有多个租户的 Rails 应用程序。

      这将为您的问题带来两个好处:

      1. 处理@current_tenant(或任何您想命名的名称)是通过中间件完成的,因此您无需在ApplicationController 中设置它。查看READMESwitch on domain 部分,了解它是如何完成的。 (注意:apartment 使用Apartment.current_tenant 来指代您的@current_tenant

      2. 最好的部分:在大多数情况下,您将不再需要 @current_tenant,因为 apartment 将在适当的 postgresql 架构上限定所有请求。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-08-11
        • 2018-04-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多