目录
2.2 activesupport::cache::store
2.3 activesupport::cache::memorystore
2.4 activesupport::cache::filestore
2.5 activesupport::cache::memcachestore
2.6 activesupport::cache::rediscachestore
2.7 activesupport::cache::nuillstore
本文将介绍如何通过高速缓存机制来加速Rails程序应用。
高速缓存意味着在request-response这个cycle里生成存储数据,然后在反馈相似的请求时复用。
缓存机制是提高应用性能的最高效的机制。通过缓存机制,连接单数据库的单服务器能够支持千级并发。
Rails提供了一系列的开箱即用的caching特性。 本手册将介绍介绍每个特性的范围和目的。
通过阅读本文,你会学到:
- 片段和俄罗斯套娃缓存
- 怎么解决缓存依赖
- 备用缓存存储
- 条件Get支撑
1. 基本缓存
本段将介绍三种类型的缓存技术: page, action和fragment缓存。默认,Rails提供了片段缓存。为了使用page和action的caching机制,需要在Gemfile中添加actionpack-page_caching和actionpack-action_caching。
默认,caching只在production环境中使用。如果想在本地使用缓存机制,就需要在本地config/environments/*.rb文件中配置config.action_controller.perform_caching=true。这个值的改变仅会影响
Action Controller中的caching,而不会影响到实例中的低级别的caching。
1.1 page caching
page caching允许请求生成一个web page的请求。由于这是特别块的,所以其并不适用于每一个情况,例如认证。这时候的访问就像从一个文件系统直接获取文件,则为了安全期间,我们需要设置cache的失效期。
需要注意的是, page caching已经从Rails 4.0中移除。
1.2 action caching
如上段所说,page caching不能用于filter之前的actions。例如,需要认证的pages,这就是为什么会有action caching。
需要注意的是,action caching也已经从Rails 4.0中移除。
1.3 fragment caching
动态的web应用通常会基于许多组件(存储机制不同)来生成pages。当page中不同部分需要cached,并且需要不同的生命周期时,则需要使用fragment caching。
fragment caching允许view的段落可以被包装成一个cache block然后当下一个请求来的时候从cache store中取出来。
例如,可以用以下代码来cache页面中的每一个product:
<% @products.each do |product| %>
<% cache product do %>
<%= render product %>
<% end %>
<% end %>
当你的应用收到关于这个界面的第一个请求时,Rails会基于唯一键写一个新的cache,例如
views/products/index:bea67108094918eeba42cd4a6e786901/products/1
这个中间的hash字符是基于我们caching的view片段的内容计算出来的。如果这个view片段发生改变,hash字符都会改变,老的cache则会失效,且会从cache stores里删除。
如果想基于某个条件cache某个片段,则可以使用‘cache_if’或者'cache_unless'。
<% cache_if admin?, product do %>
<%= render product %>
<% end %>
除了单个cache,也可以集合方式cache,如下:
<%= render partial: 'products/product', collection: @products, cached: true %>
1.4 俄罗斯套娃caching
cache也是可以嵌套的,这叫做俄罗斯套娃caching。
俄罗斯套娃caching的优势在于:如果单一产品更新了,其他内嵌的片段可以被重复使用用于生成外部的片段。
当一个cached文件的updated_at变化了,则这个cache就失效了,但是。其内部镶嵌的片段都却不会过期。如下片段:
<% cache product do %>
<%= render product.games %>
<% end %>
当game的任一属性改变了,则updated_at会设置成当前时间,然后game的cache会过期。然而,相关联的product会不会因此而过期,从而产生陈旧数据,这是不正确的。
则为了解决这个问题, 我们需要用touch方法将这两个主题连接起来。
class Product < ApplicationRecord
has_many :games
end
class Game < ApplicationRecord
belongs_to :product, touch: true
end
这样的话,任何game的变化都会引起相关联的product的cache的更新。
1.5 共享部分caching
共享部分cachings也是有可能实现的。例如,共享部分caching允许模版在html和javascripts文件件共享部分文件。
1.6 管理依赖
为了正确的废止cache,我们需要正确定义caching依赖。Rails默认能处理一般例子,然而,有时,当你处理定制化的helpers时,则需要显示定义他们。
隐式依赖定义:
render @project.documents.where(published: true)
显示依赖定义:
<%# Template Collection: notification %>
<% my_helper_that_calls_cache(some_arg, notification) do %>
<%= notification.name %>
<% end %>
1.7 低级别caching
有时,我们不需要去cocah view的片段,而仅仅需cache一个值或者query的结果。幸运的是,rails的caching机制支持保存任何信息。
最高效的实现底层caching则需要用rails.cache.fetch方法。这个方法支持读写cache。当参数是一个值时,则key被获取,caches里的值被返回。如果参数是一个块,则没有cache时,该代码块会被执行,然后返回值会被写进cache。
如下例子:
class Product < ApplicationRecord
def competing_price
Rails.cache.fetch("#{cache_key_with_version}/competing_price", expires_in: 12.hours) do
Competitor::API.find_price(id)
end
end
end
1.8 sql caching
query caching缓存每一个query返回的查找结果。如果rails遇到相同的query,则会使用cached的值,而不会再次查询。
例如:
class ProductsController < ApplicationController
def index
# Run a find query
@products = Product.all
...
# Run the same query again
@products = Product.all
end
end
这二次去数据库执行相同的查询,但是并不是去真正的访问数据库。第一次查询返回的结果存储在query cache里面(内存里),第二次会直接从内存中获取到。
然而,查询caches在一个action开始的时候创建,在action结束的时候销毁,所以仅仅存在在一个action的生命周期里。如果你希望这个查询结果保存的更长久,则可以使用low-level caching。
2 cache stores
除了sql和page caching,rails为不同的存储数据存储了不同的存储。
2.1 configuration
你可以通过设置config.cache_store配置选项来配置应用程序默认的cache store。如下:
config.cache_store = :memory_store, { size: 64.megabytes }
除了这种方法,你还可以在configuration块外调用actioncontroller::base.cache_store去定义cache store.
还可以通过rails cache去获取cache。
2.2 activesupport::cache::store
这个类提供了rails中cache交互基础。这个是抽象类,我们不能直接使用。我们在具体化类时需要和存储引擎绑定。
主要方法包括:read, write, delete, exist?和 fetch。
2.2.1 connection pool options
默认的,MemCacheStore和RedisCacheStore使用一个单一的连接进程。如果想增加可用连接的数字,则使能connection pooling。
第一步,在Gemfile文件中添加connection_pool
gem 'connection_pool'
然后,在配置cache store时陪你pool_size和pool_timeout,如下:
config.cache_store = :mem_cache_store, "cache.example.com", { pool_size: 5, pool_timeout: 5 }
2.2.2 定义化cache stores
我们可以通过继承activesupport::cache::store来创建自己的cache store。如下实例化类:
config.cache_store = MyCacheStore.new
2.3 activesupport::cache::memorystore
这个cache stor保存在同一进程中的实例在内存中。我们可以通过以下配置类初始化cache store。默认大小为32m,当所占的内存超过该值时,则使用最少的一些值会被清除。
config.cache_store = :memory_store, { size: 64.megabytes }
如果rails应用多程序运行譬如用了phusion passenger,则rails server process实例间无法共享cache 数据。这种cache store对大型应用的部署并不适用。而对于small, low traffic的网站,却能很好的适用。
由于当我们使用:memory_store,进程间无法共享进程,所以我们无法通过rails console去手动的读写或者失效cache。
2.4 activesupport::cache::filestore
这种cache store用文件系统去存储实体。如下:
config.cache_store = :file_store, "/path/to/cache/directory"
如果我们没有如上设定位置,则cache默认会存储在“#{root}/tmp/cache”目录下
2.5 activesupport::cache::memcachestore
这个cache store用danga's memcached 服务器为服务提供了中心化的缓存。rails默认用了dundled dalli gem。这是目前对production网站最流行的cache store。他可以用来提供单一的,共享的高性能高冗余的cache集群。
当初始化cache时,你需要指定集群中memcached servers的地址,如下:
config.cache_store = :mem_cache_store, "cache-1.example.com", "cache-2.example.com"
如果不指定地址,则默认为本地(如下),对于小网站,这样是可以的,但是对于大型应用是不可以的。、
config.cache_store = :mem_cache_store # Will fallback to $MEMCACHE_SERVERS, then 127.0.0.1:11211
2.6 activesupport::cache::rediscachestore
redis cache store的优势是当达到最大存储空间会自动清理。我们需要在gemfile中添加redis gem。如下:
gem 'redis'
我们可以使能更快的hiredis链接库。我们需要在gemfile中添加hiredis gem。如下:
gem 'hiredis'
redis cache store会自动需要和使用hiredis。而不需要其他更多额外的配置。
2.7 activesupport::cache::nuillstore
这个cache store的实现意味着其仅在development和test环境中使用,且不存储任何东西。这在开发时是很有用的,你可以通过rails.cache直接看到代码的变化。
config.cache_store = :null_store
3. caching in development
我们说了通常cache只在production环境中有效,当我们想在开发环境中测试我们的cache机制时,rails提供了dev:cache去方便的打开和关闭caching。如下:
$ bin/rails dev:cache
Development mode is now being cached.
$ bin/rails dev:cache
Development mode is no longer being cached.