使用 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)中使用。
-
在您的模型或集成中包含该模块。
要在模型中包含该模块:
include ReactiveCaching要在集成中包含该模块:
include Integrations::ReactivelyCached -
在您的模型或集成中实现
calculate_reactive_cache方法。 -
在您的模型或集成中需要缓存值的地方调用
with_reactive_cache。 -
相应地设置
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
-
此属性的值是
data和alive缓存键名称的前缀。传递给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,arg1和arg2是传递给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.minutesself.reactive_cache_refresh_interval
- 这是缓存刷新的间隔。
- 默认为 1 分钟。
self.reactive_cache_refresh_interval = 1.minuteself.reactive_cache_lifetime
- 这是如果没有请求,缓存被清除的持续时间。
- 默认为 10 分钟。如果 10 分钟内没有对此缓存值的请求,缓存将过期。
- 如果在缓存过期前请求了缓存值,缓存的超时将重置为
reactive_cache_lifetime。
self.reactive_cache_lifetime = 10.minutesself.reactive_cache_hard_limit
- 这是
ReactiveCaching允许缓存的最大数据大小。 - 默认为 1 兆字节。超过此值的数据不会被缓存,并在 Sentry 上静默抛出
ReactiveCaching::ExceededReactiveCacheLimit。
self.reactive_cache_hard_limit = 5.megabytesself.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。
- 在此示例中,主键 ID 与传递给
更改缓存键
由于 reactive caching 的工作原理,更改 calculate_reactive_cache 方法的参数就像更改 Sidekiq 工作线程的参数一样。因此需要遵循 相同的规则。
例如,如果向 calculate_reactive_cache 方法添加了一个新参数:
- 向
calculate_reactive_cache方法添加一个带有默认值的参数(发布 M)。 - 将新参数添加到所有
with_reactive_cache方法的调用中(发布 M+1)。 - 移除默认值(发布 M+2)。