缓存对于提高Web应用程序的性能和可扩展性至关重要–Ruby on Rails中的缓存也不例外。通过存储和重复使用昂贵的计算或数据库查询的结果,缓存大大减少了服务用户请求所需的时间和资源。
在这里,我们回顾了如何在Rails中实现不同类型的缓存,如片段缓存和俄罗斯娃娃缓存。我们还将向你展示如何管理缓存依赖和选择缓存存储,并概述在Rails应用程序中有效使用缓存的最佳实践。
本文假定你熟悉Rails上的Ruby,使用Rails版本6或更高,并能自如地使用Rails视图。代码示例演示了如何在新的或现有的视图模板中使用缓存。
- Ruby on Rails的缓存类型
- Ruby on Rails中的片段缓存
- Ruby on Rails中的俄罗斯娃娃缓存
- Ruby on Rails中的缓存依赖管理
- Ruby on Rails中的缓存存储和后端
- Ruby on Rails缓存的最佳实践
Ruby on Rails的缓存类型
在Ruby on Rails应用程序中,根据缓存内容的级别和粒度,有几种缓存类型可供选择。现代Rails应用程序中使用的主要类型有
- 片段缓存(Fragment caching):缓存网页中不经常变化的部分,如页眉、页脚、侧边栏或静态内容。片段缓存减少了每次请求中渲染的部分或组件的数量。
- 俄罗斯娃娃缓存(Russian doll caching):缓存网页中相互依赖的嵌套片段,如集合和关联。俄罗斯娃娃缓存可防止不必要的数据库查询,并可轻松重用未更改的缓存片段。
另外两种类型的缓存以前是Ruby on Rails的一部分,但现在可以作为单独的宝石使用:
- 页面缓存(Page caching):将整个网页作为静态文件缓存在服务器上,绕过整个页面渲染生命周期。
- 动作缓存(Action caching):缓存整个控制器动作的输出。它与页面缓存类似,但允许您应用过滤器,如身份验证。
页面缓存和动作缓存很少使用,在现代Rails应用程序的大多数用例中不再推荐使用。
Ruby on 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中的俄罗斯娃娃缓存
俄罗斯娃娃缓存是Ruby on Rails中一种强大的缓存策略,它通过相互嵌套缓存来优化应用程序的性能。它使用Rails的片段缓存和缓存依赖来减少冗余工作,提高加载时间。
在一个典型的Rails应用程序中,您经常渲染一个项目集合,每个项目都有多个子组件。更新单个项目时,应避免重新渲染整个集合或任何未受影响的项目。在处理分层或嵌套数据结构时,尤其是当嵌套组件有自己的关联数据且可能独立变化时,请使用Russian Doll缓存。
俄罗斯娃娃缓存的缺点是增加了复杂性。您必须了解您要缓存的项目的嵌套层之间的关系,以确保您缓存了正确的项目。在某些情况下,您需要在Active Record模型中添加关联,以便Rails可以推断缓存数据项之间的关系。
与常规的片段缓存一样,俄罗斯娃娃缓存使用 cache
辅助方法。例如,在视图中缓存一个类别及其子类别和产品,可以这样写:
<% @categories.each do |category| %> <% cache category do %> <h2><%= category.name %></h2> <% category.subcategories.each do |subcategory| %> <% cache subcategory do %> <h3><%= subcategory.name %></h3> <% subcategory.products.each do |product| %> <% cache product do %> <%= render partial: "product", locals: { product: product } %> <% end %> <% end %> <% end %> <% end %> <% end %> <% end %>
cache
助手会将每个嵌套层分别存储在缓存中。下一次请求同一类别时,它将从缓存存储中获取其缓存片段并显示出来,而无需再次渲染。
然而,如果任何子类别或产品的详细信息发生变化(如名称或描述),则其缓存片段将失效,然后将使用更新的数据重新渲染。俄罗斯娃娃缓存确保您不必在单个子类别或产品发生变化时使整个类别失效。
Ruby on Rails中的缓存依赖管理
缓存依赖关系是缓存数据与其底层源之间的关系,对其进行管理可能非常棘手。如果源数据发生变化,任何相关的缓存数据都会过期。
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))
Ruby on Rails中的缓存存储和后端
Rails允许你选择不同的缓存存储或后端来存储缓存数据和内容。Rails的缓存存储是一个抽象层,提供了与不同存储系统交互的通用接口。缓存后端为特定的存储系统实现缓存存储接口。
Rails支持多种类型的缓存存储或后端,详情如下。
内存存储
内存存储使用内存中的哈希作为缓存存储。它快速、简单,但容量和持久性有限。这种缓存存储适用于开发和测试环境或小型、简单的应用程序。
磁盘存储
磁盘存储使用磁盘上的文件作为缓存存储。它是Rails中速度最慢的缓存选项,但容量和持久性较大。磁盘存储适用于必须缓存大量数据但不需要最高性能的应用程序。
Redis
Redis存储使用Redis实例进行缓存存储。Redis是一种内存数据存储,支持多种数据类型。虽然它快速灵活,但需要单独的服务器和配置。它适用于必须缓存经常变化的复杂或动态数据的应用程序。当在云中运行Rails应用程序时,Redis是一个理想的选择,因为包括Kinsta在内的一些托管服务提供商提供Redis作为持久对象缓存。
Memcached
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
。如果调用了多个,缓存存储只会使用最后一个。
每个缓存存储都有其独特的优缺点,这取决于您的应用程序的需求和偏好。选择一个最适合您的使用情况和经验水平的。
Ruby on Rails缓存的最佳实践
在Rails应用程序中使用缓存可以大大提高其性能和可扩展性,尤其是当您实施以下最佳实践时:
- 有选择地缓存:仅缓存频繁访问、生成成本高或更新频率低的数据。避免过度缓存,以防止内存使用过多、数据陈旧和性能下降。
- 过期缓存条目:通过过期失效或不相关的条目来防止数据过期。使用时间戳、过期选项或手动失效。
- 优化缓存性能:选择适合您的应用需求的缓存存储,并微调其参数,如大小、压缩或序列化,以获得最佳性能。
- 监控和测试缓存影响:评估缓存行为(如命中率、未命中率和延迟),并评估其对性能(响应时间、吞吐量、资源使用)的影响。使用New Relic、Rails日志、ActiveSupport通知或Rack mini profiler等工具。
小结
Ruby on Rails缓存通过有效地存储和重复使用经常访问的数据或内容来提高应用程序的性能和可扩展性。通过深入了解缓存技术,您可以更好地为用户提供更快的Rails应用程序。
评论留言