缓存对于提高Web应用程序的性能和可扩展性至关重要–Ruby on Rails中的缓存也不例外。通过存储和重复使用昂贵的计算或数据库查询的结果,缓存大大减少了服务用户请求所需的时间和资源。
在这里,我们回顾了如何在Rails中实现不同类型的缓存,如片段缓存和俄罗斯娃娃缓存。我们还将向你展示如何管理缓存依赖和选择缓存存储,并概述在Rails应用程序中有效使用缓存的最佳实践。
本文假定你熟悉Rails上的Ruby,使用Rails版本6或更高,并能自如地使用Rails视图。代码示例演示了如何在新的或现有的视图模板中使用缓存。
在Ruby on Rails应用程序中,根据缓存内容的级别和粒度,有几种缓存类型可供选择。现代Rails应用程序中使用的主要类型有
另外两种类型的缓存以前是Ruby on Rails的一部分,但现在可以作为单独的宝石使用:
页面缓存和动作缓存很少使用,在现代Rails应用程序的大多数用例中不再推荐使用。
片段缓存允许您缓存页面中不经常变化的部分。例如,一个显示产品列表及其相关价格和评价的页面可以缓存不太可能改变的细节。
同时,它可以让Rails在每次页面加载时重新渲染页面的动态部分(如评论或评价)。当视图的底层数据频繁变化时,由于频繁更新缓存的开销,片段缓存就不那么有用了。
作为Rails中最简单的缓存类型,片段缓存应该是你为应用程序添加缓存以提高性能的首选。
要在Rails中使用片段缓存,请在视图中使用缓存辅助方法。例如,编写以下代码来缓存视图中的产品部分:
<% @products.each do |product| %>
<% cache product do %>
<%= render partial: "product", locals: { product: product } %>
<% end %>
<% end %>
cache
助手根据每个元素的类名、 id
和 update_at
时间戳(例如, products/1-20230501000000
)生成一个缓存键。下一次用户请求相同的产品时, cache
助手将从缓存存储中获取缓存片段并显示出来,而无需从数据库中读取产品。
您也可以通过向 cache
助手传递选项来自定义缓存键。例如,要在缓存密钥中包含版本号或时间戳,可以这样写
<% @products.each do |product| %>
<% cache [product, "v1"] do %>
<%= render partial: "product", locals: { product: product } %>
<% end %>
<% end %>
或者,您也可以设置到期时间:
<% @products.each do |product| %>
<% cache product, expires_in: 1.hour do %>
<%= render partial: "product", locals: { product: product } %>
<% end %>
<% end %>
第一个示例将在缓存键(例如, products/1-v1
)上添加 v1
。当您更改部分模板或布局时,这对缓存失效非常有用。第二个示例为缓存条目设置了过期时间(1小时),这有助于过期数据的失效。
俄罗斯娃娃缓存是Ruby on Rails中一种强大的缓存策略,它通过相互嵌套缓存来优化应用程序的性能。它使用Rails的片段缓存和缓存依赖来减少冗余工作,提高加载时间。
在一个典型的Rails应用程序中,您经常渲染一个项目集合,每个项目都有多个子组件。更新单个项目时,应避免重新渲染整个集合或任何未受影响的项目。在处理分层或嵌套数据结构时,尤其是当嵌套组件有自己的关联数据且可能独立变化时,请使用Russian Doll缓存。
俄罗斯娃娃缓存的缺点是增加了复杂性。您必须了解您要缓存的项目的嵌套层之间的关系,以确保您缓存了正确的项目。在某些情况下,您需要在Active Record模型中添加关联,以便Rails可以推断缓存数据项之间的关系。
与常规的片段缓存一样,俄罗斯娃娃缓存使用 cache
辅助方法。例如,在视图中缓存一个类别及其子类别和产品,可以这样写:
<% @categories.each do |category| %>
<% cache category do %>
<%= category.name %>
<% category.subcategories.each do |subcategory| %>
<% cache subcategory do %>
<%= subcategory.name %>
<% subcategory.products.each do |product| %>
<% cache product do %>
<%= render partial: "product", locals: { product: product } %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
cache
助手会将每个嵌套层分别存储在缓存中。下一次请求同一类别时,它将从缓存存储中获取其缓存片段并显示出来,而无需再次渲染。
然而,如果任何子类别或产品的详细信息发生变化(如名称或描述),则其缓存片段将失效,然后将使用更新的数据重新渲染。俄罗斯娃娃缓存确保您不必在单个子类别或产品发生变化时使整个类别失效。
缓存依赖关系是缓存数据与其底层源之间的关系,对其进行管理可能非常棘手。如果源数据发生变化,任何相关的缓存数据都会过期。
Rails可以使用时间戳来自动管理大多数缓存依赖关系。每个Active Record模型都有 created_at
和 updidated_at
属性,指示缓存创建或最后更新记录的时间。为了确保Rails可以自动管理缓存,请定义你的Active Record模型的关系如下:
class Product < ApplicationRecord
belongs_to :category
end
class Category < ApplicationRecord
has_many :products
end
在此示例中:
updated_at
时间戳会自动改变。products/1-20230504000000
),它也会自动使您的缓存片段失效。touch
方法( @product.category.touch
)或在您的模型关联中添加一个触摸选项(belongs_to :category touch: true
)。另一种管理缓存依赖的机制是直接在模型或控制器中使用低级缓存方法–例如 fetch
和 write
。这些方法允许您使用自定义键和选项在缓存中存储任意数据或内容。例如
class Product < ApplicationRecord
def self.average_price
Rails.cache.fetch("products/average_price", expires_in: 1.hour) do
average(:price)
end
end
end
本示例演示了如何使用 fetch
方法和自定义键( products/average_price
)以及过期选项( expires_in: 1.hour
)将计算数据(例如所有产品的平均价格)缓存一小时。
fetch
方法将首先尝试从缓存中读取数据。如果找不到数据或数据已过期,则执行块并将结果存储到缓存中。
要在缓存条目过期前手动使其失效,请使用带有 force
选项的 write
方法:
Rails.cache.write("products/average_price", Product.average(:price), force: true))
Rails允许你选择不同的缓存存储或后端来存储缓存数据和内容。Rails的缓存存储是一个抽象层,提供了与不同存储系统交互的通用接口。缓存后端为特定的存储系统实现缓存存储接口。
Rails支持多种类型的缓存存储或后端,详情如下。
内存存储使用内存中的哈希作为缓存存储。它快速、简单,但容量和持久性有限。这种缓存存储适用于开发和测试环境或小型、简单的应用程序。
磁盘存储使用磁盘上的文件作为缓存存储。它是Rails中速度最慢的缓存选项,但容量和持久性较大。磁盘存储适用于必须缓存大量数据但不需要最高性能的应用程序。
Redis存储使用Redis实例进行缓存存储。Redis是一种内存数据存储,支持多种数据类型。虽然它快速灵活,但需要单独的服务器和配置。它适用于必须缓存经常变化的复杂或动态数据的应用程序。当在云中运行Rails应用程序时,Redis是一个理想的选择,因为包括Kinsta在内的一些托管服务提供商提供Redis作为持久对象缓存。
Memcached存储使用Memcached实例进行缓存存储。Memcached是一种内存键值存储,支持简单的数据类型和功能。它速度快、可扩展,但与Redis一样,需要单独的服务器和配置。该存储适用于需要缓存频繁更新的简单或静态数据的应用程序。
你可以在Rails环境文件(例如config/environments/development.rb)中使用 config.cache_store
选项配置缓存存储。下面是如何使用Rails内置的每种缓存方法:
# Use memory store
config.cache_store = :memory_store
# Use disk store
config.cache_store = :file_store, "tmp/cache"
# Use Redis
config.cache_store = :redis_cache_store, { url: "redis://localhost:6379/0" }
# Use Memcached
config.cache_store = :mem_cache_store, "localhost"
每个环境文件只能调用一次 config.cache_store
。如果调用了多个,缓存存储只会使用最后一个。
每个缓存存储都有其独特的优缺点,这取决于您的应用程序的需求和偏好。选择一个最适合您的使用情况和经验水平的。
在Rails应用程序中使用缓存可以大大提高其性能和可扩展性,尤其是当您实施以下最佳实践时:
Ruby on Rails缓存通过有效地存储和重复使用经常访问的数据或内容来提高应用程序的性能和可扩展性。通过深入了解缓存技术,您可以更好地为用户提供更快的Rails应用程序。