Rails reload dynamic routes on multiple instances/servers

空扰寡人 提交于 2019-12-01 04:12:28

问题


How can we force Rails to reload_routes on multiple servers/instances?

We have a multi-tenant platform in Google App-Engine running on 5+ instances and we want all of our sites to define their own set of routes from the backend. Whenever we have a new site we currently have to restart all servers in order to be able to access the new routes.

We followed this guide but it does only work on a local environment and is not updating routes on all servers in production without restarting the servers.

Our route files look like this:

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

after each update of routes or creation of a new site we are running DynamicRoutes::reload


回答1:


We finally found a solution that works pretty well and is also not affecting performance too much. We use the fact that Threads in production are keeping states across requests. So we decided to create a middleware that checks the latest timestamp of a routes change and in case the timestamp is not the same as the one saved in Thread.current we force a 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

Benefits:

  • You can exclude the reload on certain paths like /assets/ and /admin/
  • Threads server multiple requests and the reload only happens once
  • You can implement this on any model you like

Caveats:

  • New Threads will load routes twice
  • All Threads will reload routes if you clear Rails Cache (you could overcome this with a persistent solution; e.g. saving the timestamp into mysql and then into cache)

But overall we didn't recognise any performance drops.

We have been struggling with this now for years and the above solution is the first that really helped us reloading routes on multiple threads.




回答2:


Assuming you have no shared storage: You could write an action that reloads the route for that particular instance. When you trigger DynamicRoutes::reload, you would make a request to the other instances' reload action.

If you do have shared storage, write a before_action that reloads the routes whenever a specific file has been "touched" and touch that file if you want to have all instances reload the routes.



来源:https://stackoverflow.com/questions/48506247/rails-reload-dynamic-routes-on-multiple-instances-servers

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!