【问题标题】:Rails reload dynamic routes on multiple instances/serversRails 在多个实例/服务器上重新加载动态路由
【发布时间】:2018-07-08 10:37:48
【问题描述】:

我们如何强制 Rails 在多个服务器/实例上重新加载路由?

我们在 Google App-Engine 中有一个多租户平台,在 5 个以上的实例上运行,我们希望我们的所有网站都从后端定义自己的一组路由。每当我们有一个新站点时,我们当前必须重新启动所有服务器才能访问新路由。

我们遵循了这个guide,但它只适用于本地环境,并且不会在生产中的所有服务器上更新路由而不重新启动服务器。

我们的路由文件如下所示:

routes.rb

Frontend::Application.routes.draw do
  root 'home#index'
  ...
  DynamicRoutes.load
end

lib/dynamic_routes.rb

def self.load 
  Frontend::Application.routes.draw do
    Site.all.each do |site|
      site.routes.each do |custom_route|
       route_name = custom_route[0]
       route = custom_route[1]

       # write the route with the host constraint
       self.constraints(:host => site.hostname) do
         case route_name
         when :contact_form
           mapper.match "#{route}", to: 'contact_forms#new' as: "contact_#{site.id}"
         end
         ...
       end
     end
    end
  end
end

def self.reload
  Frontend::Application.reload_routes!     
end

每次更新路线或创建新站点后,我们都会运行DynamicRoutes::reload

【问题讨论】:

  • 您是否尝试在Site 模型的after_save 回调中添加DynamicRoutes::reload
  • 是的,我已经试过了。多次保存站点和路线也不能确保重新加载的路线
  • @Mik 使用Frontend::Application.routes_reloader.reload! 重新加载路由的方法
  • @VishalJAIN Frontend::Application.reload_routes! 是该方法的别名

标签: ruby-on-rails ruby ruby-on-rails-3 ruby-on-rails-4 routing


【解决方案1】:

我们终于找到了一个运行良好且不会过多影响性能的解决方案。我们使用生产中的线程跨请求保持状态这一事实。所以我们决定创建一个中间件来检查路由更改的最新时间戳,如果时间戳与Thread.current 中保存的时间戳不同,我们将强制使用Frontend::Application.reload_routes!

config/production.rb

Frontend::Application.configure do
  ...
  config.middleware.use RoutesReloader
  ...
end

app/middleware/routes_reloader.rb

class RoutesReloader
  SKIPPED_PATHS = ['/assets/', '/admin/']

  def initialize(app)
    @app = app
  end

  def call(env)
    if reload_required?(env)
      timestamp = Rails.cache.read(:routes_changed_timestamp)

      if Thread.current[:routes_changed_timestamp] != timestamp
        Frontend::Application.reload_routes!

        Thread.current[:routes_changed_timestamp] = timestamp
      end
    end

    @app.call(env)
  end

  private

  def reload_required?(env)
    SKIPPED_PATHS.none? { |word| env['PATH_INFO'].include?(word) }
  end
end

app/model/routes.rb

class Routes < ActiveRecord::Base

  after_save :save_timestamp

  private

  def save_timestamp
    ts = Time.zone.now.to_i
    Rails.cache.write(:routes_changed_timestamp, ts, expires_in: 30.minutes)
  end
end

好处:

  • 您可以在 /assets/ 和 /admin/ 等某些路径上排除重新加载
  • 线程服务器多个请求,重新加载只发生一次
  • 您可以在任何您喜欢的模型上实现此功能

注意事项:

  • 新线程会加载两次路由
  • 如果您清除 Rails 缓存,所有线程将重新加载路由(您可以通过持久性解决方案克服这个问题;例如,将时间戳保存到 mysql 中,然后保存到缓存中)

但总的来说,我们没有发现任何性能下降。

多年来,我们一直在努力解决这个问题,而上述解决方案是第一个真正帮助我们在多个线程上重新加载路由的解决方案。

【讨论】:

    【解决方案2】:

    假设您没有共享存储:您可以编写一个操作来重新加载该特定实例的路由。当您触发 DynamicRoutes::reload 时,您将向其他实例的重新加载操作发出请求。

    如果您确实有共享存储,请编写一个 before_action 以在“触摸”特定文件时重新加载路由,如果您想让所有实例重新加载路由,请触摸该文件。

    【讨论】:

      猜你喜欢
      • 2017-08-18
      • 2020-12-04
      • 1970-01-01
      • 2014-09-05
      • 1970-01-01
      • 1970-01-01
      • 2013-01-03
      • 2012-08-24
      • 1970-01-01
      相关资源
      最近更新 更多