Help us learn about your current experience with the documentation. Take the survey.

使用 ReactiveCaching

本文档参考 reactive_caching.rb

ReactiveCaching 模块用于在后台获取数据并将其存储在 Rails 缓存中,只要数据被请求就保持其最新状态。如果数据在 reactive_cache_lifetime 时间内没有被请求,它将停止刷新并被移除。

示例

class Foo < ApplicationRecord
  include ReactiveCaching

  after_save :clear_reactive_cache!

  def calculate_reactive_cache(param1, param2)
    # 这里是耗时操作。此方法的返回值会被缓存
  end

  def result
    # 任何参数都可以传递给 `with_reactive_cache`。`calculate_reactive_cache`
    # 将使用相同的参数被调用。
    with_reactive_cache(param1, param2) do |data|
      # ...
    end
  end
end

在此示例中,第一次调用 #result 时,它返回 nil。但是,它会将一个后台工作入队来调用 #calculate_reactive_cache 并设置初始缓存生命周期为 10 分钟。

ReactiveCaching 如何工作

第一次调用 #with_reactive_cache 时,会入队一个后台任务,with_reactive_cache 返回 nil。后台任务调用 #calculate_reactive_cache 并存储其返回值。它还会在 reactive_cache_refresh_interval 后重新入队后台任务再次运行。因此,它保持存储的值是最新的。计算永远不会并发运行。

当值被缓存时调用 #with_reactive_cache 会调用传递给 #with_reactive_cache 的代码块,产生缓存的值。它还会将缓存的生存期延长 reactive_cache_lifetime 的值。

生存期结束后,不再入队后台任务,再次调用 #with_reactive_cache 返回 nil,重新开始整个过程。

为 ReactiveCaching 设置硬限制

为了保持性能,您应该在包含 ReactiveCaching 的类中设置硬缓存限制。请参阅 如何设置它 的示例。

更多信息,请阅读内部问题 Redis (或 ReactiveCache) 软硬限制

何时使用

  • 如果我们需要请求外部 API(例如,对 k8s API 的请求)。不建议在请求外部 API 的过程中阻塞应用服务器的工作线程。
  • 如果一个模型需要执行大量数据库调用或其他耗时的计算。

如何使用

在模型和集成中使用

ReactiveCaching 模块可以在模型和集成(app/models/integrations)中使用。

  1. 在您的模型或集成中包含该模块。

    要在模型中包含该模块:

    include ReactiveCaching

    要在集成中包含该模块:

    include Integrations::ReactivelyCached
  2. 在您的模型或集成中实现 calculate_reactive_cache 方法。

  3. 在您的模型或集成中需要缓存值的地方调用 with_reactive_cache

  4. 相应地设置 reactive_cache_work_type

在控制器中使用

调用使用 ReactiveCaching 的模型或服务方法的控制器端点不应等待后台工作完成。

  • 调用使用 ReactiveCaching 的模型或服务方法的 API,在缓存计算过程中(当 #with_reactive_cache 返回 nil 时)应返回 202 accepted
  • 它还应该使用 Gitlab::PollingInterval.set_header 设置轮询间隔头
  • API 的使用者应该轮询该 API。
  • 您也可以考虑实现 ETag 缓存 来减少轮询造成的服务器负载。

在模型或服务中实现的方法

这些是应该在包含 ReactiveCaching 的模型/服务中实现的方法。

#calculate_reactive_cache(必需)

  • 必须实现此方法。其返回值会被缓存。
  • ReactiveCaching 需要填充缓存时调用它。
  • 传递给 with_reactive_cache 的任何参数也会传递给 calculate_reactive_cache

#reactive_cache_updated(可选)

  • 如果需要,可以实现此方法。
  • 每当缓存更新时,ReactiveCaching 模块都会调用此方法。如果缓存正在刷新且新缓存值与旧缓存值相同,则不会调用此方法。只有在将新值存储在缓存中时才会调用它。
  • 它可用于在缓存更新时执行操作。

由模型或服务调用的方法

这些是 ReactiveCaching 提供的方法,应该在模型/服务中调用。

#with_reactive_cache(必需)

  • 必须在需要 calculate_reactive_cache 结果的地方调用 with_reactive_cache

  • 可以给 with_reactive_cache 传递一个代码块。with_reactive_cache 也可以接受任意数量的参数。传递给 with_reactive_cache 的任何参数都会传递给 calculate_reactive_cache。传递给 with_reactive_cache 的参数会附加到缓存键名称中。

  • 如果在结果已被缓存时调用 with_reactive_cache,则会调用代码块,产生缓存的值,代码块的返回值由 with_reactive_cache 返回。它还将缓存的超时重置为 reactive_cache_lifetime 的值。

  • 如果结果尚未被缓存,with_reactive_cache 返回 nil。它还会入队一个后台任务,该任务调用 calculate_reactive_cache 并缓存结果。

  • 后台任务完成后结果被缓存,下一次调用 with_reactive_cache 会获取到缓存的值。

  • 在下面的示例中,data 是缓存的值,它被传递给 with_reactive_cache 的代码块。

    class Foo < ApplicationRecord
      include ReactiveCaching
    
      def calculate_reactive_cache(param1, param2)
        # 这里是耗时操作。此方法的返回值会被缓存
      end
    
      def result
        with_reactive_cache(param1, param2) do |data|
          # ...
        end
      end
    end

#clear_reactive_cache!(可选)

  • 当需要使缓存过期/清除时可以调用此方法。例如,可以在模型的 after_save 回调中调用它,以便在模型修改后清除缓存。
  • 应该使用与传递给 with_reactive_cache 相同的参数调用此方法,因为参数是缓存键的一部分。

#without_reactive_cache(可选)

  • 这是一个方便的方法,可用于调试目的。
  • 此方法在当前进程中调用 calculate_reactive_cache,而不是在后台工作线程中。

可配置选项

有一些可以调整的 class_attribute 选项。

self.reactive_cache_key

  • 此属性的值是 dataalive 缓存键名称的前缀。传递给 with_reactive_cache 的参数形成缓存键名称的其余部分。

  • 默认情况下,此键使用模型的名称和记录的 ID。

    self.reactive_cache_key = -> (record) { [model_name.singular, record.id] }
  • data 缓存键是 "ExampleModel:1:arg1:arg2"alive 缓存键是 "ExampleModel:1:arg1:arg2:alive",其中 ExampleModel 是模型名称,1 是记录 ID,arg1arg2 是传递给 with_reactive_cache 的参数。

  • 如果您在集成(app/models/integrations/)中包含此模块,则必须通过在集成中添加以下内容来覆盖默认值:

    self.reactive_cache_key = ->(integration) { [integration.class.model_name.singular, integration.project_id] }

    如果您的 reactive_cache_key 与上述完全相同,您可以使用现有的 Integrations::ReactivelyCached 模块。

self.reactive_cache_lease_timeout

  • ReactiveCaching 使用 Gitlab::ExclusiveLease 来确保缓存计算永远不会被多个工作线程并发运行。
  • 此属性是 Gitlab::ExclusiveLease 的超时时间。
  • 默认为 2 分钟,但如果需要不同的超时时间,可以覆盖它。
self.reactive_cache_lease_timeout = 2.minutes

self.reactive_cache_refresh_interval

  • 这是缓存刷新的间隔。
  • 默认为 1 分钟。
self.reactive_cache_refresh_interval = 1.minute

self.reactive_cache_lifetime

  • 这是如果没有请求,缓存被清除的持续时间。
  • 默认为 10 分钟。如果 10 分钟内没有对此缓存值的请求,缓存将过期。
  • 如果在缓存过期前请求了缓存值,缓存的超时将重置为 reactive_cache_lifetime
self.reactive_cache_lifetime = 10.minutes

self.reactive_cache_hard_limit

  • 这是 ReactiveCaching 允许缓存的最大数据大小。
  • 默认为 1 兆字节。超过此值的数据不会被缓存,并在 Sentry 上静默抛出 ReactiveCaching::ExceededReactiveCacheLimit
self.reactive_cache_hard_limit = 5.megabytes

self.reactive_cache_work_type

  • 这是 calculate_reactive_cache 方法执行的工作类型。基于此属性,它能够选择正确的工作线程来处理缓存任务。如果工作执行任何外部请求(例如,Kubernetes、Sentry),请务必将其设置为 :external_dependency;否则将其设置为 :no_dependency

self.reactive_cache_worker_finder

  • 这是后台工作线程用来查找或生成可以调用 calculate_reactive_cache 的对象的方法。

  • 默认情况下,它使用模型的主键来查找对象:

    self.reactive_cache_worker_finder = ->(id, *_args) do
      find_by(primary_key => id)
    end
  • 可以通过定义自定义的 reactive_cache_worker_finder 来覆盖默认行为。

    class Foo < ApplicationRecord
      include ReactiveCaching
    
      self.reactive_cache_worker_finder = ->(_id, *args) { from_cache(*args) }
    
      def self.from_cache(var1, var2)
        # 此方法将被后台工作线程调用,参数为 "bar1" 和 "bar2"。
        new(var1, var2)
      end
    
      def initialize(var1, var2)
        # ...
      end
    
      def calculate_reactive_cache(var1, var2)
        # 这里是耗时操作。此方法的返回值会被缓存
      end
    
      def result
        with_reactive_cache("bar1", "bar2") do |data|
          # ...
        end
      end
    end
    • 在此示例中,主键 ID 与传递给 with_reactive_cache 的参数一起传递给 reactive_cache_worker_finder
    • 自定义的 reactive_cache_worker_finder 使用传递给 with_reactive_cache 的参数调用 .from_cache

更改缓存键

由于 reactive caching 的工作原理,更改 calculate_reactive_cache 方法的参数就像更改 Sidekiq 工作线程的参数一样。因此需要遵循 相同的规则

例如,如果向 calculate_reactive_cache 方法添加了一个新参数:

  1. calculate_reactive_cache 方法添加一个带有默认值的参数(发布 M)。
  2. 将新参数添加到所有 with_reactive_cache 方法的调用中(发布 M+1)。
  3. 移除默认值(发布 M+2)。