Rails 在 Rails 中使用 HTTP 缓存

heroyct · 2018年06月03日 · 2156 次阅读
本帖已被设为精华帖!

为了改善页面的响应速度,在rails里面使用了HTTP缓存,做下总结

什么是HTTP 缓存

这里讲的很清楚了,就不多说了
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Caching_FAQ

rails的HTTP 缓存

默认缓存是开启的

随便打开一个页面,在chrome的Network里面查看request和response,可以看到下面的内容

response header

Cache-Control: max-age=0, private, must-revalidate
ETag: W/"25200a77a0ae51ac9511df4ac1e9a0d5"

request header

If-None-Match: W/"64a30229a112c1819d754ef8a7ad8876"

大概意思是,进行(私有)浏览器缓存,每次都会验证请求的状态
比较他们的etag的值,如果是一样的就返回304

手动指定Cache的时间

可以看到默认的max-age是0,我们可以手动指定这个时间

expires_in 1.hour

在chrome里面确认,这个时候要注意的是你如果直接更新页面的话无法确认结果

新打开一个tab, 在URL栏里面点击enter进入才可以确认

image

这种比较适合静态页面或者是一段时间不更新也没有任何问题的页面

如何强制更新

比如上面设置了1个小时的更新,但是你想立刻更新页面的时候
可以在URL后面加上时期之类的来更新,比如?20180810120000

etag是如何生成的

headers['ETag'] = Digest::MD5.hexdigest(body)

可以看出是根据每次返回的response body来生成的,如果你每次返回的body都一样,ETAG也会一样

API

一个API返回json的时候,如果每次返回的值都一样,就会返回304

HTML页面

默认启用了csrf, csrf-token的值每次都会变化,所以基本都是200

自定义ETAG

每次都从response body来生成ETAG,明显不是一个高效的方法
因为还是必须去render html,生成response body,
虽然可以减少传送的内容,但是服务器端的response time没有太大改善

实际应用中,我们很可能需要的是某个或者多个数据未改变,不用去render html,直接返回304
这个时候就可以使用fresh_when

fresh_when

比如有个book mode,我们希望book没有更新对话就返回304,可以在controller里面这样写

def index
  fresh_when last_modified: @book.updated_at.utc, etag: @book
  fresh_when @book
end

这样的话book没有更新的话,就会返回304

多个变量生成ETAG

def index
  fresh_when [@book, current_user.try(:id)]
end

OR

class TopController < ApplicationController
  etag { current_user.try(:id) }
  fresh_when @book
end

book没有更新并且是相同用户的话,才会返回304

从model如何生成etag

headers[Etag] = Digest::MD5.hexdigest(@book.cache_key)

model的cache_key类似这样books/13-20180517145513000000000

<model name>/<id>-<updated_at>

从自定义的类如何生成etag

def etag=(etag)
  key = ActiveSupport::Cache.expand_cache_key(etag)
  @etag = self[ETAG] = %("#{Digest::MD5.hexdigest(key)}")
end

def retrieve_cache_key(key)
  case
  when key.respond_to?(:cache_key) then key.cache_key
  when key.is_a?(Array)            then key.map { |element| retrieve_cache_key(element) }.to_param
  when key.respond_to?(:to_a)      then retrieve_cache_key(key.to_a)
  else                                  key.to_param
  end.to_s
end

可以看出最简单的实现自定义类的etag生成的方法就是实现cache_key方法
比如你有一个类来管理看过的书,看过的书有增加了就生成新的etag

class BookHistory
  def cache_key
    # return read book ids
  end
end

禁止缓存

有时候我们需要禁止缓存,可以这样

def index
  expires_now
end

禁止缓存的情况感觉不多,感觉一般没必要用
我在React(flux + immutable js)的项目中,API如果返回304的话
在safari里面有时候会取不到返回值, 暂时采取了safari的话就禁止缓存

使用fresh_when需要注意的地方

影响页面变动的变量比较少的时候,我会采用fresh_when
但是影响页面变动的变量多的话,用fresh_when会有一些问题

  1. 维护困难,必须搞清楚这个页面的所有变量,把他们都传到fresh_when里面去
    • 比如你可能在页面里面你使用了book.author.name来显示相关书的作者
    • book的author变化了你需要更新页面, 但你可能忘了把author也传给fresh_when
  2. 上线后不容易发现问题,因为你看到没问题,某个用户可能看到的画面就有可能有问题

不知道大家使用时有没有什么好的方法来解决这些问题

总结

  1. 使用HTTP缓存可以很好的提高服务器的响应速度
  2. 如果是在RestAPI中,因为返回的内容基本是某个model的内容,比较推荐使用
  3. 在一般的html页面中,影响页面的变量少的时候可以使用fresh_when,变量多的时候慎重使用

参考文章

共收到 0 条回复
huacnlee 将本帖设为了精华贴 06月04日 09:44
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册