6.2 缓存

    1. 缓存
    2. redis
    3. memcached

    Rails 提供了三种方式的缓存,页面缓存,方法缓存和片段缓存,在 Rails 4 之前的版本里,它包含在 Rails 中,但是从 4.x 开始,三种缓存中的两种转为 gem 形式,只有片段缓存保留在 Rails 默认
    中。

    在开发环境下,缓存是关闭的,如果要测试它,需要更改配置:

    在产品环境下,它默认是 true。

    6.2.2 页面缓存,Page Cache

    Rails 4.x 将页面缓存转为 ,使用的时候需要加入到 gemfile 中。

    我们设置一下缓存路径,在

    1. config.action_controller.page_cache_directory = "#{Rails.root.to_s}/public"

    页面缓存是将整个页面,生成一份静态的 html 页面,这个页面会保存在刚才设置的目录中。Rails 在显示该地址的时候,会优先查找 public 是否有同名的 html 文件优先显示。

    我们把 show 方法加入到页面缓存中:

    1. class ProductsController < ApplicationController
    2. ...
    3. caches_page :show

    当第一次访问时,会创建该缓存文件:

      再次访问时,便直接读取该文件,而不再执行 show 方法了。

      这样做的好处是,可以把一些经常访问的页面作为页面缓存。缺点是,这种页面不能有太多用户的个人信息,因为这个页面对所有人访问都是相同的内容。如果必须考虑个人信息,可以改为 js 形式,或者使用方法缓存(Action Cache)。

      当这个缓存页面内容更改时,可以删掉该文件,再次访问时会自动创建。也可以在 update 内加入过期的命令:

      1. def update
      2. respond_to do |format|
      3. if @product.update(product_params)
      4. expire_page action: 'show', id: @product.id
      5. ...
      6. else
      7. ...
      8. end
      9. end
      10. end

      更新资料后会自动过期该文件。

      1. Expire page /path/to/project/public/products/3.html (1.0ms)

      方法缓存和页面缓存的区别是:它会执行对应的 action 中的代码。页面缓存直接读取缓存文件,不执行 action 中的代码。

      页面缓存的 gem 在这里。

      1. ...
      2. caches_action :index, layout: false

      访问该页面,会创建一个片段缓存(fragment cache)文件:

      1. Write fragment views/localhost:3000/products (5.9ms)

      该片段缓存为当前整个页面,我们增加 layout: false 参数,这样,片段缓存只包含该 action 对应的模板内容,而不包含 layout。我们设计的代码,将用户信息放置在 layout 中,登录后会显示用户名。所以 layout 是不应该放到缓存中的。

      但是,因为我们给 index 方法增加了搜索功能,而该方法已经加入到了缓存中,所以,搜索是还是显示的缓存内容。这里可以做调整,要么将搜索放到专用的非缓存方法中,要么搜索时过时该缓存。

      6.2.4 片段缓存,Fragment Cache

      片段缓存,是 Rails 默认使用的缓存方式,它指的是视图(view)中,缓存局部内容:

      这里把经常访问的分类列表,加入到了缓存中,避免每次页面访问该部分都读取数据库。

      我们可以给 cache 方法增加一些参数:

      1. <% cache(action: 'new', action_suffix: 'all_products') do %>

      它产生的缓存 key 是:

      1. Write fragment views/localhost:3000/products/new?action_suffix=all_products/02c540e3ab26f72d5e9273d5824c204e (60.0ms)

      也可以直接命名缓存 key:

      1. <% cache( "all_products" ) do %>

      它产生的缓存 key 是:

      1. Write fragment views/all_products/cc926a692262d0e538f07d5dd5d54942 (15.1ms)

      或者直接缓存一个实例:

      1. <% cache @product do %>

      它产生的缓存 key 是:

      1. Write fragment views/products/3-20150620164035711340000/b0699b1b8be94ebd1bfcfe74a21571f8 (21.5ms)

      可见,缓存是产生一个 key: value 结构的数据。key 来自于实例的 cache_key 方法:

      1. p.cache_key
      2. => "products/3-20150620164035711340000"
      3. p.updated_at = nil
      4. p.cache_key
      5. => "products/3"

      该方法会读取 updated_at 字段值,这样,每当该实例更改的时候,会自动更新 updated_at 字段,相当于自动更新了缓存。

      我们可以使用

      缓存产生的是 结构的数据,所以我们可以使用支持该解构的数据库来保存缓存。在 config/environments/production.rb 中有 cache_store 的选项:

      1. # Use a different cache store in production.
      2. config.cache_store = :mem_cache_store

      这里有四个选项可以使用::memory_store, :file_store, :mem_cache_store, :null_store。在 手册里还介绍了 JRuby 的 Ehcache。

      6.2.5.1 :memory_store

      缓存和 Ruby 进程使用共同的内存,默认大小为32M,如果超出这个范围,会移除掉旧的记录。我们可以更改这个限制:

      1. config.cache_store = :memory_store, { size: 64.megabytes }

      但是多个 Rails 应用不会共享该缓存。它不适合大型的部署,适合小型的,低访问量的应用。

      6.2.5.2 :file_store

      1. config.cache_store = :file_store, "/path/to/cache/directory"

      缓存利用文件系统来存放缓存文件,虽然可以在多个应用间共享缓存,但是不建议在产品环境下使用。这种方式会不断的增加硬盘使用,直到手动清空所有缓存。

      Rails 默认使用这种方式。

      6.2.5.3 :mem_cache_store

      这种方式使用 Memcached 最为后端缓存服务,它提供了高性能的、集中式的缓存服务,可以在多个应用间共享缓存,这是一种适合中大型商业应用的选择。

      1. config.cache_store = :mem_cache_store, "cache-1.example.com", "cache-2.example.com"

      使用 Memcached 需要安装 ,操作时:

      1. Rails.cache.read('key')
      2. Rails.cache.write('key', value)
      3. Rails.cache.fetch('key') { value }

      6.2.5.4 :null_store

      这是一种适合开发和测试环境的配置,它不会储存任何东西,但是可以正常调试 Rails.cache 中的方法。

      1. config.cache_store = :null_store

      6.2.5.5 自定义缓存服务

      Redis 作为一个高性能的内存型数据库,也可以作为缓存服务。我们先安装 redis 的 gem:

      1. gem 'redis-rails'
      2. gem "hiredis"

      增加配置:

      现在,越来越多的 Rails 项目和 redis 配合使用,比如下一节要介绍的异步服务,还有大量非结构化的数据,也可以储存在 redis 中。比如站内短信息,好友动态,或者好友列表,都可以通过 redis 的命令快速实现,较之关系型数据库拥有更快的读写速度,且更适合储存非结构化数据。

      6.2.6 缓存的读取和写入

      我们可以在 Rails 项目内部,使用 Rails.cache.fetch 来读取缓存,如果不存在,将返回 nil,如果传入 block,会将 block 中的结果写入缓存,并将其返回。比如:

      1. class Product < ActiveRecord::Base
      2. def competing_price
      3. Rails.cache.fetch("#{cache_key}/competing_price", expires_in: 12.hours) do
      4. Competitor::API.find_price(id)
      5. end

      更多 API 信息可以查看 这里