Skip to content

Instantly share code, notes, and snippets.

@xiangzhuyuan
Last active August 29, 2015 14:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xiangzhuyuan/cb1bfb00c5d56583bb5d to your computer and use it in GitHub Desktop.
Save xiangzhuyuan/cb1bfb00c5d56583bb5d to your computer and use it in GitHub Desktop.

Copy from https://devcenter.heroku.com/articles/http-caching-ruby-rails

#rails 里的 http cache

Rails3就提供了一个简单的 http 缓存配置功能,他能够服务静态页面或者资源文件, 当你的程序通过这个设置获得好处时, 详细的可以在这里 合适的缓存头部设置 ,甚至是动态的页面, 能够给你一个巨大的效果和提高对应响应时间的问题,提高用户体验和资源响应可以提高的你的应用.

本人将贯穿讲述很多的情况,来通过使用 rails 3里的 http 缓存头来提高响应时间通过最小的修改.

#rails3里默认的 http 缓存

在 rails3里默认的设置囊括了基本上全部的 http 缓存头 使用场景. 另外, 它还有附加的对于静态资源的 pipline 来更加高效的提供静态资源.还有 Rack::Cache 和 Rack::ETag 中间件, 他们能够一起提供免打扰的缓存机制.

##asset pipeline

rails3.1中介绍了一个概念就是 asset pipeline, 通过对于 js 和 css 的拼接和压缩, rails 添加了 http 缓存头来方式重新获取相同的资源文件,对于资源的请求有很多个头部定义来说明怎么在本地保存.

max-age-asset

这个 age 头传达了一个从缓存里获取到资源的预计年龄 这个 cache-control 表面当前的资源是一个公开的,(可以存储在一个中间的代理上), 还有一个 max-age 值 这个 etag 是 rack 中间件计算出来的, 基于响应内容的摘要 还有 last-modified, 表明最后的修改时间,基于文件的信息

##Rack::Cache

反向代理缓存,如 Rack::Cache, 它在 web 客户端如浏览器和你的应用程序,还有 缓存可以缓存的资源.

在 rails3中介绍了 Rack::Cache 这个一个原生的代理 cache, 在市场环境里,所有的页面如果有 public 缓存头,将会被 Rack::Cache 缓存起来,他会充当一个中间代理人的角色, 结果就是, 你的 rails 栈会绕过这些被缓存的资源.

默认的 Rack::Cache 会使用内存显示存储,在高分布式 的环境里,如 heroku, 它会用 memcache 来处理这个缓存.

##Rack::ETag

一个 Cache-Control: private 的副作用就是他们将不会内反向代理缓存如 Rack::Cache

Rack::ETag 提供了一个自动追加一个 ETag 头和 Cache-Control:private条件性的请求上,

default header

他能够通过 hash 整个请求的响应来实现,而不需要借助程序里特定的东西.

因为这个方式,显然它还是需要程序完整的处理请求来响应结果,唯一能够节省的就是把内容从服务器端传送到客户端的这个过程.然后给他一个空的响应,告诉客户端这个是304 Not Modified的.通过设置 cahche http 头来最大化性能,但是你还是需要处理好自己的程序.

##基于时间的缓存头

Rails 提供了2种控制器方法来特定基于时间的缓存处理, 通过Expires 头里的 expire_inexpires_now

###expires_in http 头 cache-control 里的那个max-age 值是通过控制器方法expires_in 来实现的. 看下面的代码:


def show
  @company = Company.find(params[:id])
  expires_in 3.minutes, :public => true
  # ...
end

当一个请求请求什么的这个方法时,它会得到下面的这个http 头:

expires_in_logs.png

你会发现有一个 max-age 的值在头里,它可以告诉客户端在一定时间区间被不要再去请求这个内容. 这个可以从粒度来实现缓存,可以用于那些并不是经常更新的内容.

当和 Rack::Cache 这个仪器使用的时候, 这些请求只会实际请求一次服务器. 看下面:

Started GET "/companies/2" for 127.0.0.1 at 2012-09-26 14:07:28 +0100
Processing by CompaniesController#show as HTML
  Parameters: {"id"=>"2"}
  Rendered companies/show.html.erb within layouts/application (9.0ms)
Completed 200 OK in 141ms (Views: 63.8ms | ActiveRecord: 14.4ms)

Started GET "/companies/2" for 127.0.0.1 at 2012-09-26 14:11:10 +0100
Processing by CompaniesController#show as HTML
  Parameters: {"id"=>"2"}
Completed 304 Not Modified in 2ms (ActiveRecord: 0.3ms)

可以看出第一次请求走完了整个请求流,而第二次的时候就会返回一个304的响应.

###expires_now

你可以强制过期一个资源,通过这个 expires_now 控制器方法,它会在 http 头cache-control给设置成为 no-cache, 这样就不会在浏览器或者任何的中间缓存.

def show
  @person = Person.find(params[:id])

  # Set Cache-Control header no-cache for this one person
  # (just as an example)
  expires_now if params[:id] == '1'
end

看下图,

##有条件的缓存头

有条件的 get 请求,它会要求浏览器初始化一个请求,但是允许服务器响应一个缓存了的响应或者而绕过处理整个基于一个共享的元数据.(etag 或者 Last-Modified 时间戳)

在 Rails 中,可以通过stale? fresh_when方法来处理特定的条件缓存行为.

###stale?

这个方法会在 http 头里设置一个对应的 ETagLast-Modified-Since 头, 然后通过这个决定是否当前的请求没有过时, (是否需要更新).

对于公开的请求,指定 :public => true 来处理反向代理的缓存.


def show
  @company = Company.find(params[:id])
  # ...
  if stale?(etag: @company, last_modified: @company.updated_at)
    respond_to do |format|
      format.html # show.html.erb
      format.json { render json: @company }
    end
  end
end

一般情况下,在使用 stale? 的时候会把当前 activerecord 对象的 updated_at值作为 last_modified字段的值, Rails 还可以直接通过使用这个对象来完成这个赋值操作.

if stale?(@company)
  respond_to do |format|
    # ...
  end
end

之后,会发现这个请求 Companies#show 会处理全部的请求资源.没有什么性能的提升.

之后的请求会跳过视图渲染,返回一个304的响应. 这样就避免了资源的浪费.

这个304的响应不仅仅对于客户端浏览器一侧来说加快了速度,而且对应服务器端来说,他也是能够不再去处理这个请求.

###fresh_when

stale? 这个方法会返回一个布尔值,让你决定是否需要处理这个请求, fresh_when 只是设置 ETagLast-modified-Since,如果这个请求内容没有过期,同样给一个304 not modified的响应,不需要对于控制器添加额外的修改,默认的实现中应该使用它.

def index
  @people = Person.scoped
  fresh_when last_modified: @people.maximum(:updated_at), public: true
end

##延迟加载资源 跳过 http 头来实现缓存的观点,它是能够提过一些渲染视图的请求, 这样, 它能够减少大量的服务器请求, 就拿平常来说,获得 Person.all,来加载全部的 Person.

Started GET "/people" for 127.0.0.1 at 2012-09-26 15:08:15 +0100
Processing by PeopleController#index as HTML
  Person Load (0.2ms)  SELECT "people".* FROM "people"
  Company Load (0.4ms)  SELECT "companies".* FROM "companies" WHERE "companies"."id" = 1 LIMIT 1
  Company Load (0.4ms)  SELECT "companies".* FROM "companies" WHERE "companies"."id" = 2 LIMIT 1
  Rendered people/index.html.erb within layouts/application (2023.8ms)
Completed 200 OK in 2030ms (Views: 2023.7ms | ActiveRecord: 5.2ms)


可是通过在控制器里使用 ActiveRelationscope 这个概念,能够延迟从数据库加载数据指导视图需要它.

def index
  @people = Person.scoped
  fresh_when last_modified: @people.maximum(:updated_at)
end

如果说,当在处理视图的时候,以为 http 缓存头的作用,而不需要去从数据库取,这样就能够获取性能的巨大提升.


Started GET "/people" for 127.0.0.1 at 2012-09-26 15:09:43 +0100
Processing by PeopleController#index as HTML
 (0.4ms)  SELECT MAX("people"."updated_at") AS max_id FROM "people"
Completed 304 Not Modified in 1ms (ActiveRecord: 0.4ms)

##公开的资源 对于公开的资源来说,由于没有敏感的数据,它能够缓存到反向代理等中间服务器上,通过使用 public: truye 这个缓存方法来实现公开的资源.

def show
  @company = Company.find(params[:id])
  expires_in(3.minutes, public: true)
  if stale?(@company, public: true)
    # …
  end
end

##私有的资源 默认的,这个 cache-control对于所有的请求会被设置成为私有的, 但是, 一些缓存的设置会被重写,所以很多时候需要显示设置它,

expires_in(1000.seconds, public: false)

##不缓存的内容

一般就是通过过滤器的方式 before_filter 来实现. 你可以通过继承的方式或者在每一个控制器单独显示声明.

before_filter :set_as_private

def set_as_private
  expires_now
end

默认情况, rails 会提供一个基本实现的 http 缓存对于静态文件来说,但是对于真正要优化 http 缓存的人来说,都要显式的定义你中间的缓存机制,通过 rails 为你提供的很多请求缓存机制.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment