前言

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. Unity3D热更新之LuaFramework篇[09]--资源热更新与代码热更新的具体实现

    前言 在上一篇文章 Unity3D热更新之LuaFramework篇[08]--热更新原理及热更服务器搭建 中,我介绍了热更新的基本原理,并且着手搭建一台服务器. 本篇就做一个实战练习,真正的来实现热 ...

  3. Unity3D热更新之LuaFramework篇[08]--热更新原理及热更服务器搭建

    前言 前面铺垫了这么久,终于要开始写热更新了. Unity游戏热更新包含两个方面,一个是资源的更新,一个是脚本的更新. 资源更新是Unity本来就支持的,在各大平台也都能用.而脚本的热更新在iOS平台 ...

  4. 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新

    本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...

  5. Unity3D热更新之LuaFramework篇[01]--从零开始

    前言 因工作关系,需要对手头的项目进行热更新支持.了解后发现,Lua的几个变种:XLua.ToLua(原uLua)和Slua都可以做Unity热更,而ToLua更是提供了一个简易的热更框架--LuaF ...

  6. Unity3D热更新之LuaFramework篇[10]--总结篇

    背景 19年年初的时候,进到一家新单位,公司正准备将现有的游戏做成支持热更的版本.于是寻找热更方案的任务就落在了我头上. 经过搜索了解,能做Unity热更的方案是有好几种,但是要么不够成熟,要么不支持 ...

  7. 【Quick 3.3】资源脚本加密及热更新(三)热更新模块

    [Quick 3.3]资源脚本加密及热更新(三)热更新模块 注:本文基于Quick-cocos2dx-3.3版本编写 一.介绍 lua相对于c++开发的优点之一是代码可以在运行的时候才加载,基于此我们 ...

  8. Unity资源打包学习笔记(二)、如何实现高效的unity AssetBundle热更新

    转载请标明出处:http://www.cnblogs.com/zblade/ 0x01 目的 在实际的游戏开发中,对于游戏都需要进行打补丁的操作,毕竟,测试是有限的,而bug是无法预估的.那么在手游中 ...

  9. 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新

    上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...

随机推荐

  1. Pass Infrastructure基础架构(下)

    Pass Infrastructure基础架构(下) pass注册  PassRegistration该类在示例中简要显示了各种pass类型的定义 .该机制允许注册pass类,以便可以在文本pass管 ...

  2. 浅谈Gson和fastjson使用中的坑

    相信大家在代码编写中都用过Gson和fastjson吧,用来进行 Java对象和json字符串之间的转换. 本篇文章就主要介绍博主在工作中使用这两款工具时遇到的坑和对应的解决办法. 觉得有用的可以点个 ...

  3. python----日志模块loggin的使用,按日志级别分类写入文件

    1.日志的级别 日志一共分为5个等级,从低到高分别是: 级别 说明 DEBUG 输出详细的运行情况,主要用于调试. INFO 确认一切按预期运行,一般用于输出重要运行情况. WARNING 系统运行时 ...

  4. JUC 并发编程--01,线程,进程,经典卖票案例, juc的写法

    进程: 就是一个程序, 里面包含多个线程, 比如一个QQ程序 线程: 进程中最小的调度单元, 比如 QQ中的自动保存功能 并发: 多个线程操作同一资源, 抢夺一个cpu的执行片段, 快速交替 并行: ...

  5. CArray CList CMap 插入与遍历效率对比

    前言:程序中经常用到不定量数组,选择上可以使用CArray,CList,CMap,而这三者插入及遍历的效率,未测试过,随着数据量越来越大,需要做程序上的优化,于是比较下三种类型的插入盒遍历的效率. 一 ...

  6. 2021Qt打包发布教程

    因为最近写了一个程序,然后想着能给室友玩耍,就研究了一下如何打包,写这篇博客记录一下 1. 首先获得程序的Release版本 就是点击这个Release,然后构建一遍 2. 进入构建的release文 ...

  7. [.NET Core知识点回顾]-自动内存管理

    自动内存管理是公共语言运行时在托管执行过程中提供的服务之一.公共语言运行时的垃圾回收器为应用程序管理内存 的分配和释放.对开发人员而言,在开发托管应用程序时不必编写执行内存管理任务代码. 分配内存 初 ...

  8. Vue(9)购物车练习

    购物车案例 经过一系列的学习,我们这里来练习一个购物车的案例   需求:使用vue写一个表单页面,页面上有购买的数量,点击按钮+或者-,可以增加或减少购物车的数量,数量最少不得少于0,点击移除按钮,会 ...

  9. external-provisioner源码分析(1)-主体处理逻辑分析

    更多ceph-csi其他源码分析,请查看下面这篇博文:kubernetes ceph-csi分析目录导航 概述 接下来将对external-provisioner组件进行源码分析. 在external ...

  10. 2021年Wordpress手把手教你做个独立站——部署篇

    2021年Woocommerce电商主题的安装部署教程 Woocommerce是一个Wordpress的一个流行的电商插件.完成Wordpress的安装即已完成80%.剩下的便是去寻找一款合适的自己喜 ...