ruby-on-rails 从缓存提供过时数据

y1aodyip  于 2022-11-19  发布在  Ruby
关注(0)|答案(1)|浏览(116)

我正在一个Rails应用程序中工作,我想在那里提供陈旧的数据(其中的变化很小,没有那么大的关系,但我想在后台升级该高速缓存)。
一种方法是这样的:

if Rails.cache.exist?('my_view')
 Rails.cache.read('my_view')
 MaybeUpdateInBackground.perform_async
end

# MaybeUpdateInBackground.rb 
def perform
 Rails.cache.write('my_view', render_to_string('my_view')) 
end

在RoR中是否有更好的模式?

g0czyy6m

g0czyy6m1#

这样就完成了任务:
要分解它,请执行以下操作:

  • Dummy利用了一个“陈旧缓存”--模块可以添加到任何类中。
  • 当你调用expensive_but_unimportant_operation_cached时,它会检查Rails.cache--如果值在那里,它会返回它--如果那里的值过时,它会触发“刷新”。
  • 刷新是重新运行函数的Sidekiq作业。
class Dummy
  extend T::Sig
  include WithStaleCache

  sig { returns(Integer) }
  def expensive_but_unimportant_operation_cached
    cache_with_stale_cache(
      'expensive_but_unimportant_operation',
      expires_in: 1.week,
      stale_in: 1.day,
    )
  end

  sig { returns(Integer) }
  def self.expensive_but_unimportant_operation
    sleep 10
    10
  end
end

module WithStaleCache
  extend T::Sig

  sig do
    params(
        method: String,
        serialized_arguments: T.nilable(T::Array[String]),
        stale_in: ActiveSupport::Duration,
        expires_in: ActiveSupport::Duration,
      )
      .returns(T.untyped)
  end
  def cache_with_stale_cache(
    method,
    serialized_arguments = nil,
    stale_in:,
    expires_in:
  )
    SolidAssert.assert(
      T.unsafe(self).class.respond_to?(method),
      "#{T.unsafe(self).class} must implement class method: #{method}.",
    )

    cache_key =
      build_key(T.unsafe(self).class.to_s, method, serialized_arguments)
    cached_value = Rails.cache.read(cache_key)
    value = cached_value&.dig('value')

    if cached_value.nil?
      value =
        CacheFillWorker.new.perform(
          cache_key,
          serialized_arguments,
          { 'expires_in' => expires_in, 'stale_in' => stale_in },
        )
    elsif cached_value['stale_at'].before?(Time.current)
      CacheFillWorker.perform_async(
        cache_key,
        serialized_arguments,
        { 'expires_in' => expires_in, 'stale_in' => stale_in },
      )
    end

    value
  end

  sig do
    params(
        klass: String,
        method_name: String,
        args: T.nilable(T::Array[String]),
      )
      .returns(String)
  end
  def build_key(klass, method_name, args)
    "stale_cache/v1/#{args}/#{klass}/#{method_name}"
  end

  sig { params(cache_key: String).returns([String, String]) }
  def self.class_and_method_from_cache_key(cache_key)
    T.cast(cache_key.split('/').last(2), [String, String])
  end

  sig do
    params(cache_key: String, serialized_arguments: T.nilable(T::Array[String]))
      .returns(T.untyped)
  end
  def self.perform(cache_key, serialized_arguments)
    deserialized_args =
      if serialized_arguments.nil?
        nil
      else
        serialized_arguments.map { |arg| JSON.parse(arg) }
      end
    class_name, method = class_and_method_from_cache_key(cache_key)
    klass = class_name.constantize

    if deserialized_args.nil?
      klass.send(method)
    else
      klass.send(method, *deserialized_args)
    end
  end
end

# typed: true
class CacheFillWorker
  extend T::Sig
  include Sidekiq::Worker

  # Arguments are serialized.
  sig do
    params(
        cache_key: String,
        serialized_arguments: T.nilable(T::Array[String]),
        cache_options: StringKeyHash,
      )
      .returns(T.untyped)
  end
  def perform(cache_key, serialized_arguments, cache_options = {})
    cached_value = Rails.cache.read(cache_key)

    if cached_value.nil?
      next_value = WithStaleCache.perform(cache_key, serialized_arguments)
    elsif cached_value['stale_at'].before?(Time.current)
      next_value = WithStaleCache.perform(cache_key, serialized_arguments)
    else
      # No-op since the value is not stale.
      return cached_value['value']
    end

    stale_in = cache_options.delete('stale_in').to_i
    Rails.cache.write(
      cache_key,
      {
        'value' => next_value,
        'stale_at' => stale_in.seconds.from_now,
        'cached_at' => Time.zone.now,
      },
      cache_options,
    )

    next_value
  end
end

相关问题