如何快速更新长缓存的 HTTP 资源
前言
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 和版本号记录到本地存储中。之后再次执行时,如果版本号和本地存储中的相同,就不必再更新了。
至于什么时候执行,最简单的当然是页面打开时执行,但这不是最好的,因为:
每次打开页面都要加载清单文件,增加请求
热更新需要一定的时间(包括加载清单),页面初始化时不会等你,它仍使用现有的缓存资源
页面运行时定期执行,可解决第 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 资源的更多相关文章
- 如何热更新长缓存的 HTTP 资源
前言 HTTP 缓存时间一直让开发者头疼.时间太短,性能不够好:时间太长,更新不及时.当遇到严重问题需紧急修复时,尽管后端文件可快速替换,但前端文件仍从本地缓存加载,导致更新长时间无法生效. 对于这个 ...
- [.NET] 《Effective C#》快速笔记(二)- .NET 资源托管
<Effective C#>快速笔记(二)- .NET 资源托管 简介 续 <Effective C#>读书笔记(一)- C# 语言习惯. .NET 中,GC 会帮助我们管理内 ...
- Redis系列十:缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级
一.缓存雪崩 缓存雪崩我们可以简单的理解为:由于原有缓存失效,新缓存未到期间(例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而 ...
- SpringMVC的缓存对静态资源的影响 304 Not Modified
我们知道在springmvc的配置中,可以添加缓存,但是缓存到底对静态资源有什么影响? 测试 没有添加缓存 <mvc:resources mapping="/image/**" ...
- 《Effective C#》快速笔记(二)- .NET 资源托管
简介 续 <Effective C#>读书笔记(一)- C# 语言习惯. .NET 中,GC 会帮助我们管理内存,我们并不需要去担心内存泄漏,资源分配和指针初始化等问题.不过,它也并非万能 ...
- 增强织梦DedeCMS“更新系统缓存”清理沉余缓存的功能
我们使用织梦DedeCMS系统有很长一段时间后,不间断的在后台更新系统缓存的时候,有些缓存文件夹及缓存文件没有被清理,导致日积月累的垃圾缓存文件越来越多,可以以百千万计算,现在增强更新系统缓存功能清理 ...
- Redis之缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级
目录 Redis之缓存雪崩.缓存穿透.缓存预热.缓存更新.缓存降级 1.缓存雪崩 2.缓存穿透 3.缓存预热 4.缓存更新 5.缓存降级 Redis之缓存雪崩.缓存穿透.缓存预热.缓存更新.缓存降级 ...
- winform快速开发平台->让有限的资源创造无限的价值!
最近一直在维护一套自己的快速开发平台. 主要应对针对C/S架构下的项目.然而对winform这快,还真没有看到过相对好的快速开发平台, 何为快速,在博客园逛了了好久, 预览了很多通用权限管理系统. 确 ...
- 更新页面缓存OutputCache
为什么要使用OutputCache 我认为OutputCache是最简单的缓存技术了,它针对的是页面级别的,简单的一条指令就可以达到缓存的效果,有效的减轻服务器的压力和减少带宽,对于网站一些不会频繁更 ...
随机推荐
- 视频处理器为电池供电的设计提供4K视频编码
视频处理器为电池供电的设计提供4K视频编码 Video processor enables 4K video coding for battery-powered designs OmniVision ...
- 一文带你了解.Net自旋锁
本文主要讲解.Net基于Thread实现自旋锁的三种方式 基于Thread.SpinWait实现自旋锁 实现原理:基于Test--And--Set原子操作实现 使用一个数据表示当前锁是否已经被获取 0 ...
- P5960 【模板】差分约束算法
题目描述 给出一组包含 $m$ 个不等式,有 $n$ 个未知数的形如: 的不等式组,求任意一组满足这个不等式组的解. 输入格式 第一行为两个正整数 $n,m$,代表未知数的数量和不等式的数量. 接下来 ...
- python赋值,深拷贝和浅拷贝的区别
1.赋值 list1=[[1,2],'fei',66] list2=list1 list1 [[1, 2], 'fei', 66] list2 [[1, 2], 'fei', 66] list2.ap ...
- 自动发布.NET Core Web应用
1 原因和目的 相信很多开发者都需要将自己的编写的应用进行编译并部署到服务器上,这个过程在个人或小型团队的项目中都是一个简单的事情.但是对于并行化开发而言,就需要通过工具来辅助这个过程.于是,我参考了 ...
- 如何使用 Python 统计分析 access 日志?
一.前言 性能场景中的业务模型建立是性能测试工作中非常重要的一部分.而在我们真实的项目中,业务模型跟线上的业务模型不一样的情况实在是太多了.原因可能多种多样,这些原因大大降低了性能测试的价值. 今天的 ...
- SpringCloud Alibaba实战(6:nacos-server服务搭建)
源码地址:https://gitee.com/fighter3/eshop-project.git 持续更新中-- 大家好,我是三分恶. 这一节我们来学习SpringCloud Alibaba体系中一 ...
- mturoute 最大传输单元路由检测Host
mturoute检测mtu字符 下载地址:https://www.elifulkerson.com/projects/mturoute.php mturoute.exe ...
- 从HTTP到HTTPS
从HTTP到HTTPS HTTP存在的缺陷 通信使用明文(不加密),内容可能会被窃听 不验证通信方的身份,因此有可能遭遇伪装 无法证明报文的完整性,所以有可能已遭篡改 防窃听 通信加密 HTTP 协议 ...
- 在C++中,你真的会用new吗?
摘要:"new"是C++的一个关键字,同时也是操作符.关于new的话题非常多,因为它确实比较复杂,也非常神秘. 本文分享自华为云社区<如何编写高效.优雅.可信代码系列(2)- ...