【问题标题】:Warming Up Cache Digests Overnight一夜之间预热缓存摘要
【发布时间】:2015-03-27 13:26:00
【问题描述】:

我们有一个相当大的 Rails 3.2 网站,其中包含数千个 URL。我们为俄罗斯娃娃缓存实现了 Cache_Digests gem。它运作良好。我们希望通过在一夜之间预热缓存来进一步优化,以便用户在白天获得更好的体验。我已经看到了这个问题的答案:Rails: Scheduled task to warm up the cache?

是否可以修改为预热大量 URL?

【问题讨论】:

    标签: caching ruby-on-rails-3.2 cron memcached cache-digests


    【解决方案1】:

    触发许多页面的缓存命中,加载时间很长,只需创建一个 rake 任务以迭代地将 Web 请求发送到您网站中的所有记录/网址组合。 (Here is one implementation)

    迭代地Net::HTTP 请求所有站点 URL/记录:

    要只访问每个页面,您可以运行每晚的 Rake 任务,以确保清晨用户仍然拥有一个包含刷新内容的快速页面。

    lib/tasks/visit_every_page.rake

    namespace :visit_every_page do
      include Net
      include Rails.application.routes.url_helpers
    
      task :specializations => :environment do
        puts "Visiting specializations..."
        Specialization.all.sort{ |a,b| a.id <=> b.id }.each do |s|
          begin
            puts "Specialization #{s.id}"
            
            City.all.sort{ |a,b| a.id <=> b.id }.each do |c|
              puts "Specialization City #{c.id}"
              Net::HTTP.get( URI("http://#{APP_CONFIG[:domain]}/specialties/#{s.id}/#{s.token}/refresh_city_cache/#{c.id}.js") )
            end
          
            Division.all.sort{ |a,b| a.id <=> b.id }.each do |d|
              puts "Specialization Division #{d.id}"
              Net::HTTP.get( URI("http://#{APP_CONFIG[:domain]}/specialties/#{s.id}/#{s.token}/refresh_division_cache/#{d.id}.js") )
            end
          end
        end
      end
    
      # The following methods are defined to fake out the ActionController
      # requirements of the Rails cache
      
      def cache_store
        ActionController::Base.cache_store
      end
    
      def self.benchmark( *params )
        yield
      end
    
      def cache_configured?
        true
      end
    end
    

    (If you want to directly include cache expiration/recaching into this task, check out this implementation.)

    通过自定义控制器操作:

    如果您需要绕过用户身份验证限制来访问您的网页,并且/或者您不想(太糟糕地)搞砸您网站的跟踪分析,您可以创建一个custom controller action 来访问缓存摘要 @ 987654324@:

    app/controllers/specializations.rb:

    class SpecializationsController < ApplicationController
    ...
      before_filter :check_token, :only => [:refresh_cache, :refresh_city_cache, :refresh_division_cache]
      skip_authorization_check :only => [:refresh_cache, :refresh_city_cache, :refresh_division_cache]
    
    ...
    
      def refresh_cache
        @specialization = Specialization.find(params[:id])
        @feedback = FeedbackItem.new
        render :show, :layout => 'ajax'
      end
    
      def refresh_city_cache
        @specialization = Specialization.find(params[:id])
        @city = City.find(params[:city_id])
        render 'refresh_city.js'
      end
    
      def refresh_division_cache
        @specialization = Specialization.find(params[:id])
        @division = Division.find(params[:division_id])
        render 'refresh_division.js'
      end
    
    end
    

    我们的自定义控制器操作会渲染其他加载昂贵页面的视图,从而导致这些页面的缓存命中。例如。 refresh_cache 呈现与 controller#show 相同的视图页面和数据,因此对 refresh_cache 的请求将预热相同的缓存摘要controller#show 用于这些记录。

    安全提示:

    出于安全原因,我建议在提供对任何自定义 refresh_cache 控制器请求的访问之前,您传递一个令牌 and check it 以确保 it corresponds with a unique token for that record. 在提供访问之前将 URL 令牌与数据库记录匹配(如上所示)这是微不足道的,因为您的 Rake 任务可以访问每条记录的唯一令牌 - 只需在每个请求中传递记录的令牌即可。

    tl;博士:

    要触发数千个站点 URL/缓存摘要,请创建一个 rake 任务以迭代请求站点中的每个记录/URL 组合。您可以通过创建一个自定义控制器操作来绕过您的应用对此任务的用户身份验证限制,该操作通过令牌对访问进行身份验证。

    【讨论】:

      【解决方案2】:

      我意识到这个问题已经存在大约一年了,但在搜索了一堆不完整且不正确的解决方案之后,我才得出了自己的答案。

      希望这将有助于下一个人...

      根据我自己的实用程序类,可以在这里找到: https://raw.githubusercontent.com/JayTeeSF/cmd_notes/master/automated_action_runner.rb

      您可以简单地运行它(根据它的 .help 方法)并预先缓存您的页面,而无需在此过程中占用您自己的网络服务器。

      class AutomatedActionRunner  
        class StatusObject
          def initialize(is_valid, error_obj)
            @is_valid = !! is_valid
            @error_obj = error_obj
          end
      
          def valid?
            @is_valid
          end
      
          def error
            @error_obj
          end
        end
      
        def self.help
          puts <<-EOH
            Instead tying-up the frontend of your production site with:
              `curl http://your_production_site.com/some_controller/some_action/1234`
              `curl http://your_production_site.com/some_controller/some_action/4567`
            Try:
              `rails r 'AutomatedActionRunner.run(SomeController, "some_action", [{id: "1234"}, {id: "4567"}])'`
          EOH
        end
      
        def self.common_env
          {"rack.input"  => "", "SCRIPT_NAME" => "", "HTTP_HOST" => "localhost:3000" }
        end
        REQUEST_ENV = common_env.freeze
      
        def self.run(controller, controller_action, params_ary=[], user_obj=nil)
          success_objects = []
          error_objects = []
          autorunner = new(controller, controller_action, user_obj)
          Rails.logger.warn %Q|[AutomatedAction Kickoff]: Preheating cache for #{params_ary.size} #{autorunner.controller.name}##{controller_action} pages.|
      
          params_ary.each do |params_hash|
            status = autorunner.run(params_hash)
            if status.valid?
              success_objects << params_hash
            else
              error_objects << status.error
            end
          end
      
          return process_results(success_objects, error_objects, user_obj.try(:id), autorunner.controller.name, controller_action)
        end
      
        def self.process_results(success_objects=[], error_objects=[], user_id, controller_name, controller_action)
          message = %Q|AutomatedAction Summary|
          backtrace = (error_objects.first.try(:backtrace)||[]).join("\n\t").inspect
          num_errors = error_objects.size
          num_successes = success_objects.size
      
          log_message = %Q|[#{message}]: Generated #{num_successes} #{controller_name}##{controller_action}, pages; Failed #{num_errors} times; 1st Fail: #{backtrace}|
          Rails.logger.warn log_message
      
          # all the local-variables above, are because I typically call Sentry or something with extra parameters!
        end
      
        attr_reader :controller
        def initialize(controller, controller_action, user_obj)
          @controller = controller
          @controller = controller.constantize unless controller.respond_to?(:name)
          @controller_instance = @controller.new
          @controller_action = controller_action
          @env_obj = REQUEST_ENV.dup
          @user_obj = user_obj
        end
      
        def run(params_hash)
          Rails.logger.warn %Q|[AutomatedAction]: #{@controller.name}##{@controller_action}(#{params_hash.inspect})|
          extend_with_autorun unless @controller_instance.respond_to?(:autorun)
      
          @controller_instance.autorun(@controller_action, params_hash, @env_obj, @user_obj)
        end
      
      
        private
      
        def extend_with_autorun
          def @controller_instance.autorun(action_name, action_params, action_env, current_user_value=nil)
            self.params = action_params # suppress strong parameters exception
            self.request = ActionDispatch::Request.new(action_env)
            self.response = ActionDispatch::Response.new
            define_singleton_method(:current_user, -> { current_user_value })
      
            send(action_name) # do it
            return StatusObject.new(true, nil)
          rescue Exception => e
            return StatusObject.new(false, e)
          end
        end
      end
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-12-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多