Web 缓存

Web 缓存的作用与类型

  • 数据库缓存
    • memcached redis
  • 服务端缓存
    • 代理服务器缓存squid
    • CDN缓存(网关缓存 反向代理缓存)
  • 浏览器端缓存
  • Web应用逻辑层缓存(代码逻辑实现)

Web 缓存机制(浏览器端)

根据 Meta 标签HTTP Header头来决定是否缓存(根据 新鲜度(过期机制)校验值(验证机制Etag))

有关缓存的消息报头

规则 消息报头 值/示例 类型 作用
新鲜度 Expires Sun, 16 Oct 2016 05:43:02 GMT 响应 告诉浏览器过期时间前可以使用副本(有可能存在时间不一致问题)
Pragma no-cache 请求/响应 告诉浏览器忽略的缓存副本(HTTP1.1 可用Cache-Control替代)
Cache-Control no-cache 请求/响应 告诉浏览器忽略的缓存副本, 强制每次请求直接发送给源服务器
no-store 响应 强制缓存在任何情况下都不要保留任何副本
max-age=[秒] 请求/响应 指明缓存副本的有效时长, 从请求时间开始到过期时间之间的秒数
public 响应 任何途径的缓存者(本地缓存, 代理服务器), 可以无条件的缓存该资源
private 响应 只针对单个用户或实体(不同用户, 窗口)缓存资源
Last-Modified Sun, 16 Oct 2016 05:43:02 GMT 响应 告诉浏览器当前资源的最后修改时间
If-Modified-Since Sun, 16 Oct 2016 05:43:02 GMT 请求 如果浏览器第一次请求时响应中 Last-Modified 非空, 第二次请求同一资源时, 会把它作为该项的值发给服务器
校验值 ETag 50udw1e3f99dw91:df3 响应 告知浏览器当前资源在服务器的唯一标识符(生成规则由服务器决定)
If-None-Match 50udw1e3f99dw91:df3 请求 如果浏览器第一次请求时响应中Etag非空, 第二次请求同一资源时, 会把它作为该项的值发给服务器
辅助 Vary Accept-Encoding 响应 辅助从多个缓存副本中筛选合适的版本(不同压缩算法产生的副本)

缓存优先级

  • Cache-Control(控制更细致) > Expires
  • Cache-Control/Expires > Last-Modified/ETag 常配合使用
  • ETag > Last-Modified

用户操作行为与缓存

用户操作 Cache-Control/Expires Last-Modified/ETag
地址栏回车 有效 有效
页面链接跳转 有效 有效
新开窗口 有效 有效
前进后退 有效 有效
F5刷新 无效 有效
Ctrl+F5强制刷新 无效 无效

无法缓存的请求

  • HTTP信息头中包含Cache-Control:no-cache,pragma:no-cache,或Cache-Control:max-age=0等告诉浏览器不用缓存的请求
  • 需要根据Cookie,认证信息等决定输入内容的动态请求是不能被缓存的
  • 经过HTTPS安全加密的请求(有人也经过测试发现,ie其实在头部加入Cache-Control:max-age信息,firefox在头部加入Cache-Control:Public之后,能够对HTTPS的资源进行缓存,参考《HTTPS的七个误解》
  • POST请求无法被缓存
  • HTTP响应头中不包含Last-Modified/Etag,也不包含Cache-Control/Expires的请求无法被缓存

使用缓存

  • 同一资源保证URL稳定
  • CSS JS 图片等静态资源增加HTTP缓存头, 并强制入口不被缓存
  • 减少对Cookie的依赖
  • HTTPS加密协议尽量不对可缓存资源使用(因为HTTPS默认不缓存, 必须配置才能缓存)
  • 不涉及敏感信息的资源尽量采用GET方式请求
  • 动态内容缓存
    • 动态脚本定期将内容导出成静态文件, 让Web可直接访问
    • 动态脚本的响应头增加 Cache-Control:max-age,告诉浏览器过期前可以直接使用副本
    • 通过代码给动态脚本的响应头添加Last-Modified/Etag信息,浏览器再次请求的时候,可以通过解析If-Modified-Since/If-None-Match得知浏览器是否存在缓存,由代码逻辑控制是否返回304

服务器配置
Apache Nginx 进行缓存头配置
动态脚本配置
服务端代码Header头增加 Expires/Cache-Control/Etag 等信息, 更精细的控制缓存效果

禁用缓存

方法1 在meta标签标明

<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0"> 

方法2 动态setRequestHeader

cache-control='no-cache,no-store'
pragma='no-cache'
if-modified-since=0;

方法3 请求端设置if-modified-since为已经过期的某个时间,可以是几年前或者几十年前。

方法4 服务端设置Expires为过期某个时间

需要一致性检测则尽量去配合Etag以及last-Modified去进行比较然后返回使用缓存还是新数据

header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");

方法3 url后面加随机数或者时间戳

url += “&random=” + Math.random()

Ajax 缓存

  • Ajax 缓存和 HTTP 缓存效果相同, 只缓存 GET 请求
  • IE 浏览器不会刷新过期日期前的Ajax内容, 只能强制清空缓存
  • Ajax 禁用缓存方式与HTTP禁用缓存方式相同

非幂等请求缓存(POST)

一些idempotent request并不能通过Get来实现的时候,例如,搜索API通常会需要很多的参数,尤其是那些拥有很多属性的产品,而这些属性都必须通过参数来传递。问题来了,如果请求携带的参数超过了GET请求的限制长度怎么办?

HTTP的协议规范允许满足下列条件中其一的以缓存来响应:

  • 缓存的响应与原始服务器的响应是一致的,简而言之就是说代理可以保证缓存的响应和服务器的响应之间的语义等价。
  • 到客户端的响应的新鲜度是可以接受的
  • 到客户端的响应的新鲜度不可接受,但是附加了合适的警告。

解决方式:
将POST的内容(附带一部分头信息),做一个摘要,将摘要附在URL后面,使用这个来作为缓存的key。换句话说,缓存主键被修改为包括URL以及一些请求体,后续的拥有相同的请求体的请求将会命中缓存。在实践的过程中我添加了一些头脑保证缓存的唯一性。

  • 区分idempotent request的post请求和非idempotent request的post请求
    • 在代理中配置URL的匹配模式,代理如果匹配到非idempotent request请求就不缓存。
    • 在头中添加context-aware以区分不同的请求
    • 基于缓存逻辑的一些命名约定,例如API的名称以set、add、delete等开头的就不被缓存。
  • 处理非idempotent request的post请求
    • 如果URL在“DO NOT CACHE”列表中
    • 如果摘要不匹配
    • 过了缓存的有效时间
    • 任何收到需要重新验证的请求的时候

优势

  • 加快了重复请求的效率,减少了代理到原服务器之间的往返
  • 一个用户的请求不仅可以用作缓存该用户的后续请求,其他用户也可共享
  • 节省了代理和原服务器之间的带宽

这个解决方案的变种可以用在正向代理和反向代理,甚至两者都用

HTML5 离线缓存

Manifest

  • 在服务器上添加MIME TYPE支,让服务器能够识别manifest后缀的文件
    AddType text/cache-manifest manifest
  • 创建一个后缀名为.manifest的文件,把需要缓存的文件按格式写在里面,并用注释行标注版本
CACHE MANIFEST
# 直接缓存的文件
CACHE:
Path/to/cache.js
# version:2012-03-20
  • <html> 标签加 manifest 属性,并引用manifest文件
    <html manifest=”path/to/name-of.manifest”>

离线应用访问及更新流程

  • 第一次访问离线应用的入口页HTML(引用了manifest文件),正常发送请求,获取manifest文件并在本地缓存,陆续拉取manifest中的需要缓存的文件
  • 再次访问时,无法在线离线与否,都会直接从缓存中获取入口页HTML和其他缓存的文件进行展示。如果此时在线,浏览器会发送请求到服务器请求manifest文件,并与第一次访问的副本进行比对,如果发现版本不一致,会陆续发送请求重新拉取入口文件HTML和需要缓存的文件并更新本地缓存副本
  • 之后的访问重复第2步的行为

离线机制的缓存用途
从Manifest的机制来看,即使我们不是为了创建离线应用,也同样可以使用这种机制用于缓存文件,可以说是给Web缓存提供多一种可以选择的途径。

存在的问题

Localstorage

Localstorage存储的地方是跟Web缓存分开的,是浏览器重新开辟的一个地方。严格来讲不属于Web缓存
Localstorage的作用
使Web页面能够通过浏览器提供的set/get接口,存储一些自定义的信息到本地硬盘,并且在单次访问或以后的访问过程中随时获取或修改。

Localstorage的API
setItem/getItem/removeItem/clear

Localstorage的缓存用途
Localstorage设计的本意可能是用来存储一些用户操作的个性化设置的文本类型的信息和数据,当我们其实也可能拿来当Web缓存区使用,比如我们可以将Base64格式编码的图片信息,存在localstorage中,再次访问时,直接本地获取后,使用Css3的Data:image的方式直接展现出来。

Localstorage的存在的问题
大小限制,目前浏览器只给每个独立的域名提供5m的存储空间,当存储超过5m,浏览器就会弹出警告框。

Apache 缓存设置

对于 Apache 服务器,可以通过 mod_expires 模块来设定ExpiresCache-ControlHTTP 头。编辑相应目录下的 .htaccess 文件,或直接对 Apache 的配置文件(根据服务器系统版本不同,可能为httpd.confapache2.conf等)作出修改。

分文件类别设定

使用ExpiresByType可以按照文件的 MIME Type 设定某一类文件的过期日期。例如:

<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresByType text/css                "access plus 1 week"
    ExpiresByType application/javascript  "access plus 2 weeks"
    ExpiresByType image/x-icon            "access plus 6 months"
    ExpiresByType image/gif               "access plus 6 months"
    ExpiresByType image/png               "access plus 6 months"
    ExpiresByType image/jpeg              "access plus 6 months"
    ExpiresByType video/x-flv             "access plus 6 months"
    ExpiresByType application/pdf         "access plus 6 months"
</IfModule>

其中access plus 1 week表示将缓存过期设置为访问时间(即当前时间)之后的一周。如果将access替换为modification,则缓存过期会被设定为文件修改时间之后的一周。
可以使用的时间单位包括:

years
months
weeks
days
hours
minutes
seconds

不同的时间也可以进行组合

ExpiresByType text/html "access plus 1 month 15 days 2 hours"
ExpiresByType image/gif "modification plus 5 hours 3 minutes"

根据文件扩展名进行设置

如果希望根据扩展名来指定缓存规则,可以使用FilesMatch配合正则表达式。为了简洁,我这里只规定了ExpiresDefault。它的优先级很低,只会在对应文件没有任何其他规则能够匹配(包括上层目录下的缓存规则)时生效。

<IfModule mod_expires.c>
    <FilesMatch "\.(css|js)$">
        ExpiresActive on 
        ExpiresDefault "access plus 1 week"
    </FilesMatch>
</IfModule>

对某些文件设定

<IfModule mod_expires.c>
    <FilesMatch "^(example\.css|example\.js)$">
        ExpiresActive on 
        ExpiresDefault "access plus 1 week"
    </FilesMatch>
</IfModule>

对某一文件夹下的所有文件设定

对于静态文件,一个比较方便的做法是将它们全部放到一个目录下,并对该目录下的所有文件设定。但是,此处需要注意防止其他规则将ExpiresDefault覆盖掉。

<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresDefault "access plus 10 years"
</IfModule>

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容