Rack cache 指导

rack-cache 是一个方便,优雅的 HTTP 缓存。

有点像 varnish,但是方便多了。用过 varnish,语法糟糕而且系统还会 crash。不推荐。作为 Ruby 开发人员,要尽量呆在 Ruby 社区。外面的世界太可怕了。

安装

在 Gemfile 里添加

gem 'rack-cache'

在 config/environments/production.rb 里添加:

config.action_dispatch.rack_cache = {
  metastore: "memcached://localhost:11211/meta",
  entitystore: "file:tmp/cache/rack/body",
  verbose: true,
  allow_reload: true,
  allow_revalidate: true
}

你也可以放在 config/application.rb 里,不过语法有一点区别,原因参见 Github issue.

config.middleware.insert_before(Rack::ConditionalGet, Rack::Cache, {
  metastore: "memcached://localhost:11211/meta",
  entitystore: "file:tmp/cache/rack/body",
  verbose: true,
  allow_reload: true,
  allow_revalidate: true
})

配置

metastore 是元数据,放在 memcached 里很快。

entitystore 我使用的 file system store,够用了。如果用 redis 和 memcached 可能会有意外问题,比如 redis 大量数据启动很慢,memcached 有最大单体容量闲置,默认是只有几兆。

verbose 是日志,打印在 stdout 的,所以 log/developement.log 里没有的。

reload 的意思是用户按住 shift 加点 refresh,这种情况一般不会出现啦。

revalidate 是用户点 refresh。

reload 不顾一切,一定要重新取一遍数据。revalidate 呢,是 rack-cache 一定要去重新算一个 etag (etag 就是页面的 hash 啦),然后和 browser 端比较一下,有可能不传输的。

allow_reload 和 allow_revalidate 的默认值都是 false,所以我们在这里都改为 true。

  allow_reload: true,
  allow_revalidate: true,

好啦。你相当于有一个 cache proxy 啦。

使用

在你的 rails controller 里,加入

expires_in 1.day

这样,这样,不仅浏览器会 cache,rack-cache 这个 middleware proxy 也会 cache!

所以一个用户请求了之后,另外一个用户,可以直接从 rack-cache 里读取到值。

注意,用户点 fresh 会 revalidate,在地址栏按回车,相当于 refresh,也会 revalidate。

Rails 用户要注意,如果你有 protect_from_forgery,默认的 app/views/layout/application.html.erb 上有 <%= csrf_meta_tag %>,这个每次请求页面都是新的,所以 etag 每次都会不同,浏览器会认为页面有更新,还是会重新获取一份。我的方法是把 csrf_meta_tag 拿掉。

如果还要用 protect_from_forgery,可以在需要的页面使用 hidden_field_tag :authenticity_token, form_authenticity_token,比如:

  <%= form_tag "/" do %>
    <%= hidden_field_tag :authenticity_token, form_authenticity_token %>
	  ...
    <%= submit_tag 'Sign In', class: 'button short-button' %>
  <% end %>

会员

除非是静态资源,否则就不要用 HTTP cache 了。

如果游客和注册用户,都通过同一个 URL 访问内容,HTTP cache 就控制不了。其实不光是 URL 上的内容,同一个 URL 如果有不同的 controller 逻辑也不行,因为 HTTP cache 会导致不去请求 controller。

一个常见的解决方法是 subdomain,比如会员使用 my.abc.com。

可以在 controller 里用 root_url(host: "my." + request.domain) 进行跳转。

当然,注意 cross subdomain cookies 是默认关闭的,要去 config/initializers/session_store.rb 里加入 domain: :all。否则你的程序会变成两个独立的服务,flash[:notice] 什么的会出问题。

Application.config.session_store :cookie_store, key: '_app_session', domain: :all