前言

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. 机器学习PAL基本概念

    机器学习PAL基本概念 本文介绍PAI-Studio.PAI-DSW及PAI-EAS的基本概念. PAI-Studio PAI-DSW PAI-EAS

  2. 深度学习Dropout技术分析

    深度学习Dropout技术分析 什么是Dropout? dropout是指在深度学习网络的训练过程中,对于神经网络单元,按照一定的概率将其暂时从网络中丢弃.注意是暂时,对于随机梯度下降来说,由于是随机 ...

  3. CodeGen融合核心关系循环扩展

    CodeGen融合核心关系循环扩展 Expansion Tokens <HARMONYCORE_RELATION_NAME> 插入当前关系的名称.关系名称将自动生成,但可以由Harmony ...

  4. JVM中的堆的新生代、老年代、永久代详解

    JVM中的堆一般分为三大部分:新生代.老年代.永久代,其大致的占比如下:  一.新生代 新生代主要用来存放新生的对象.一般占据堆空间的1/3.在新生代中,保存着大量的刚刚创建的对象,但是大部分的对象都 ...

  5. 二、部署监控服务器-Zabbix Server

    二.部署监控服务器-Zabbix Server 1)源码安装Zabbix Server 多数源码包都是需要依赖包的,zabbix也- 样,源码编译前需要先安装相关依赖包. [root@zabbixse ...

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

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

  7. 【模拟8.09】轰炸行动(bomb) (tarjan缩点和拓扑排序)

    很显然的tarjan嘛......拓扑也很容易想到 我是不会说我因为懒把拓扑改成DFS结果扔了40分然后就是纯板子了 因为我们一条路径的点如果不是一个一个炸,同时炸两个,他们一定会相互到达.... 找 ...

  8. .net core 支付宝,微信支付 一

    源码: https://github.com/aspros-luo/Qwerty.Payment/tree/develop 支付宝支付:参考支付宝sdk及文档,https://docs.open.al ...

  9. yolov5 AssertionError: Image Not Found解决方案

    运行yolov5 train.py报错:AssertionError: Image Not Found ../data/images/xxx.png 运行环境     一开始在笔记本上用显卡跑训练是可 ...

  10. [源码解析] 深度学习分布式训练框架 horovod (12) --- 弹性训练总体架构

    [源码解析] 深度学习分布式训练框架 horovod (12) --- 弹性训练总体架构 目录 [源码解析] 深度学习分布式训练框架 horovod (12) --- 弹性训练总体架构 0x00 摘要 ...