前言

HTTP 缓存时间一直让开发者头疼。时间太短,性能不够好;时间太长,更新不及时。当遇到严重问题需紧急修复时,尽管后端文件可快速替换,但前端文件仍从本地缓存加载,导致更新长时间无法生效。

对于这个问题,很多网站都有相应的解决方案。

传统方案

最常见的方案,就是为静态资源设置很长的缓存时间,然后在文件名中加入版本号或 Hash 值等字符,这样文件更新后 URL 会变化,即可避开之前版本的缓存。

不过这种方案也有缺陷。假设网页中有如下依赖关系的文件:

HTML
└─JS
└─CSS
└─GIF

现在需紧急更新 GIF 文件,于是重新发布 GIF 得到新的 URL。然而 CSS 中引用的仍是之前的 GIF URL,因此 CSS 也要修改和发布,从而得到新的 CSS URL,进而导致加载该 CSS 的 JS 也得更新,加载该 JS 的 HTML 也得更新。

由此可见,快速更新一个文件需更新整个依赖链,白白浪费不少流量。同时产生大量无意义的新版本文件,它们的业务功能并无变化,只是为了修改其中的 URL 而已。

如果能通过 JS 删除某个文件的 HTTP 缓存,这样就不用更换 URL 了,更不必修改其他文件。是否有这样的黑科技?

高级方案

现代浏览器添加了很多新特性,其中有些和缓存相关。

例如 Clear-Site-Data 特性。当 HTTP 存在 Clear-Site-Data: "cache" 响应头时,即可删除该站点的缓存文件。不过它会删除所有文件,而无法指定文件,因此不适合本案例。

例如 Fetch API 中 Request 对象的 cache 属性也涉及缓存操作。阅读文档,其中 no-cache 非常有趣:

no-cache — The browser looks for a matching request in its HTTP cache.

  • If there is a match, fresh or stale, the browser will make a conditional request to the remote server. If the server indicates that the resource has not changed, it will be returned from the cache. Otherwise the resource will be downloaded from the server and the cache will be updated.
  • If there is no match, the browser will make a normal request, and will update the cache with the downloaded resource.

我们先看第一段:如果请求的文件存在缓存,无论是否过期,浏览器都会和后端协商缓存。未变化则使用本地缓存(HTTP 304),有变化则重新下载并 更新缓存

虽然无法删除 HTTP 缓存,但能更新缓存内容,也是不错的。

演示

基于该特性,这里做了个演示:https://www.etherdream.com/cache-purge/

该页面引用了 res.js ,其内容每隔 5s 递增,可通过页面中 Res Ver 显示。

由于 res.js 设置了很长的缓存时间,因此不断刷新页面 Res Ver 也不会变化。(Chrome 刷新时只有页面走协商缓存,内部资源仍从本地缓存加载)

点击 Purge 按钮,再次刷新页面,这时 Res Ver 变化了。之后不断刷新,数字仍保持不变。

通过控制台可见,no-cache 请求不会直接使用本地缓存,并且返回的内容会覆盖本地缓存。从而实现 HTTP 长缓存资源也能热更新

如果 5s 内再次点击 purge,文件不会重新下载,而是协商返回 304 状态,符合文档中的描述。

应用

这个特性如何应用到实际业务中?首先看下兼容性:

目前绝大多数浏览器都支持。如果不考虑低版本浏览器,那该如何使用?

显然需要一个清单列表,记录哪些文件需热更新。同时为了防止重复更新,还需记录每个文件的版本号:

/foo.gif   100
/bar.gif 101
...

热更新后将 URL 和版本号记录到本地存储中。之后再次执行时,如果版本号和本地存储中的相同,就不必再更新了。

至于什么时候执行,最简单的当然是页面打开时执行,但这不是最好的,因为:

  1. 每次打开页面都要加载清单文件,增加请求

  2. 热更新需要一定的时间(包括加载清单),页面初始化时不会等你,它仍使用现有的缓存资源

页面运行时定期执行,可解决第 2 个问题,从而在下次访问页面之前完成更新,但轮询清单会带来更多请求。

如需减少请求,可使用服务端推送技术,当清单变化时主动推送给前端。这样不仅可减少请求,而且前端能在第一时间更新。

缺陷

不过这个方案仍有缺陷。我们的初衷是删除缓存,等业务再次使用时才下载;而本方案却是更新缓存,提前将文件下载到本地,附带预加载的功能。

如果热更新的是常用的公共文件倒还好,但如果是小部分用户才会用到该文件,却让所有用户都提前预加载,这显然不合理,反而更浪费流量。

终极方案

即使不能操作 HTTP 缓存,但如果能拦截 HTTP 请求并返回自定义内容,同样可达到热更新的效果。这正是 Service Worker API 的设计初衷。

开发者只需提供一个清单,将原始 URL 对应到最新 URL:

/foo.gif
https://cdn.mysite.com/1.0.0/foo.gif /bar.gif
https://cdn.mysite.com/1.0.1/bar.gif

Service Worker 根据清单中的地址反向代理。这样,只需更新一个清单文件,即可更新所有 URL 的内容。

得益于 Service Worker 能在浏览器后台持续运行,因此其创建的 WebSocket 可长时间保持连接状态,结合后端的更新推送服务,可大幅减少加载清单的开销。

基于这个功能实现的演示:https://github.com/EtherDream/freecdn/tree/master/examples/quick-update

如何快速更新长缓存的 HTTP 资源的更多相关文章

  1. 如何热更新长缓存的 HTTP 资源

    前言 HTTP 缓存时间一直让开发者头疼.时间太短,性能不够好:时间太长,更新不及时.当遇到严重问题需紧急修复时,尽管后端文件可快速替换,但前端文件仍从本地缓存加载,导致更新长时间无法生效. 对于这个 ...

  2. [.NET] 《Effective C#》快速笔记(二)- .NET 资源托管

    <Effective C#>快速笔记(二)- .NET 资源托管 简介 续 <Effective C#>读书笔记(一)- C# 语言习惯. .NET 中,GC 会帮助我们管理内 ...

  3. Redis系列十:缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级

    一.缓存雪崩 缓存雪崩我们可以简单的理解为:由于原有缓存失效,新缓存未到期间(例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而 ...

  4. SpringMVC的缓存对静态资源的影响 304 Not Modified

    我们知道在springmvc的配置中,可以添加缓存,但是缓存到底对静态资源有什么影响? 测试 没有添加缓存 <mvc:resources mapping="/image/**" ...

  5. 《Effective C#》快速笔记(二)- .NET 资源托管

    简介 续 <Effective C#>读书笔记(一)- C# 语言习惯. .NET 中,GC 会帮助我们管理内存,我们并不需要去担心内存泄漏,资源分配和指针初始化等问题.不过,它也并非万能 ...

  6. 增强织梦DedeCMS“更新系统缓存”清理沉余缓存的功能

    我们使用织梦DedeCMS系统有很长一段时间后,不间断的在后台更新系统缓存的时候,有些缓存文件夹及缓存文件没有被清理,导致日积月累的垃圾缓存文件越来越多,可以以百千万计算,现在增强更新系统缓存功能清理 ...

  7. Redis之缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级

    目录 Redis之缓存雪崩.缓存穿透.缓存预热.缓存更新.缓存降级 1.缓存雪崩 2.缓存穿透 3.缓存预热 4.缓存更新 5.缓存降级 Redis之缓存雪崩.缓存穿透.缓存预热.缓存更新.缓存降级 ...

  8. winform快速开发平台->让有限的资源创造无限的价值!

    最近一直在维护一套自己的快速开发平台. 主要应对针对C/S架构下的项目.然而对winform这快,还真没有看到过相对好的快速开发平台, 何为快速,在博客园逛了了好久, 预览了很多通用权限管理系统. 确 ...

  9. 更新页面缓存OutputCache

    为什么要使用OutputCache 我认为OutputCache是最简单的缓存技术了,它针对的是页面级别的,简单的一条指令就可以达到缓存的效果,有效的减轻服务器的压力和减少带宽,对于网站一些不会频繁更 ...

随机推荐

  1. 对端边缘云网络计算模式:透明计算、移动边缘计算、雾计算和Cloudlet

    对端边缘云网络计算模式:透明计算.移动边缘计算.雾计算和Cloudlet 概要 将数据发送到云端进行分析是过去几十年的一个突出趋势,推动了云计算成为主流计算范式.然而,物联网时代设备数量和数据流量的急 ...

  2. YOLO3升级优化版!Poly-YOLO:支持实例分割!

    YOLO3升级优化版!Poly-YOLO:支持实例分割! POLY-YOLO: HIGHER SPEED, MORE PRECISE DETECTION AND INSTANCE SEGMENTATI ...

  3. CUDA C 纹理提取Texture Fetching

    CUDA C 纹理提取Texture Fetching 一.参数曲面的纹理  使用纹理指定参数曲面属性. 二.CUDA C 纹理获取开发 用于计算纹理函数,根据纹理引用的各种属性返回的值的公式(请参见 ...

  4. Java swing JFrame用repaint出现闪烁的问题解决

    这几天用swing写登录页面背景动图的时候发现一直会有闪烁(我的类是继承JFrame),就来搜原因后发现好像是因为repaint会调用update()方法中的清屏操作导致闪烁. 我当时看的是这个文章 ...

  5. 一、Nginx的安装

    1.下载nginx软件 http://nginx.org/download/ 2.安装依赖包 [root@client ~]# useradd -s /sbin/nologin nginx 创建ngi ...

  6. Task06:综合练习

    练习一: 各部门工资最高的员工(难度:中等) 创建Employee 表,包含所有员工信息,每个员工有其对应的 Id, salary 和 department Id. +----+-------+--- ...

  7. 【NX二次开发】UF_OBJ_ask_display_properties获取对象所在层、获取对象颜色、获取对象是否隐藏、获取对象是否高亮,获取对象线宽、字体大小

    UF_OBJ_ask_display_properties 返回一个对象的显示属性(层.颜色.隐藏状态.线宽和字体). UF_OBJ_disp_props_p_t结构体: layer int 对象所在 ...

  8. 【SQLite】教程06-SQLite表操作

    创建表: CREATE TABLE 语句用于在任何给定的数据库创建一个新表.命名表.定义列.定义每一列的数据类型 查看表: 详细查看表: 重命名表: 删除表: 创建表并添加7条记录(第七条记录用了第二 ...

  9. 【SQLite】教程04-SQLite数据类型

    SQLite 存储类 每个存储在 SQLite 数据库中的值都具有以下存储类之一: 存储类 描述 NULL 值是一个 NULL 值. INTEGER 值是一个带符号的整数,根据值的大小存储在 1.2. ...

  10. Spring Boot WebFlux-01——WebFlux 快速入门实践

    第01课:WebFlux 快速入门实践 Spring Boot 2.0 spring.io 官网有句醒目的话是: BUILD ANYTHING WITH SPRING BOOT Spring Boot ...