• 如何设计静态资源缓存方案


    为什么要有缓存

    当我们在访问一个页面时,浏览器会先加载HTML文件,然后边解析边加载关联的静态资源,所有资源加载解析完毕后,最终被渲染到屏幕上。

    对浏览器来说,用户每次打开页面,都要去请求服务器获取页面资源。如果页面没有更新,每次都去请求服务器是没有必要的。所以,需要有一个缓存功能:把下载的资源先存起来,下次访问页面时直接读取本地数据,从而提高了页面访问速度。

    对于服务器来说,需要对每个请求进行解析url,读取文件和返回数据。而服务器的处理能力是有上限的,所以如果能通过缓存减少不必要的请求,将会降低服务器的负载,让它可以做更多有意义的事情

    综上所述,为了提高网页的访问速度,降低服务器的负载,我们需要一个缓存方案,而应用最广的就是http缓存。

    http缓存是怎么设计的

    1. 过期时间

    首先,比较容易想到的方法就是:服务器在返回资源时指定过期时间浏览器拿到资源后存起来,后续请求如果资源未过期就返回缓存数据,不用再请求服务器了

    这是http1.0的方案,即通过Expires头字段指定过期时间。

    Expires: Thu, 05 May 2022 08:13:07 GMT
    
    • 1

    这里有一个问题:Expires是一个绝对时间,所以要求浏览器和服务器两者的系统时间必需保持同步

    首先,Expires使用的是GMT时间,解决了时区问题。但是,如果浏览器和服务器的系统时间不一致,将会导致浏览器无法准确地判断过期时间。

    为了解决这个问题,http1.1使用的是max-age,这是一个相对时间

    cache-control: max-age=60
    
    • 1

    max-age=60表示资源有效时间为60s,由浏览器自己计算过期时间,自然也就不存在系统时间同步问题了。

    当expires和max-age同时存在时,会优先使用max-age。

    2. 协商缓存

    当浏览器缓存数据过期了,而服务器资源不一定有更新,这时候再去下载一次资源也是没有必要的。此时要和服务器验证资源是否已更新,如果未更新就继续使用缓存,否则再去下载新的资源。这就是http1.1中的协商缓存。

    当浏览器缓存数据过期了,要和服务器确认资源是否更新,那么如何判断资源已更新呢?

    常见的有以下两种方案:

    • 文件内容的hash值。
    • 文件最后更新时间。

    分别对应http1.1中的协商缓存字段etag/ If-None-Match和last-modified/ If-Modified-Since。

    服务器返回资源时会带上字段etag或last-modified。当浏览器缓存过期了,会带上If-None-Match或If-Modified-Since字段请求服务器验证资源是否更新。服务器判断资源有更新,就返回200和新资源;否则返回304

    有了协商缓存,就能在浏览器缓存过期了,而服务器资源实际未更新的情况下避免重复下载。

    当etag和last-modified同时存在时,浏览器会优先选择etag。相对而言,etag精度更高:etag只在文件内容变化时更新,而last-modified更新时不一定代表文件内容有更新。

    过期时间和协商缓存是http缓存的核心内容。除此之外,cache-control还提供了其它指令,可以满足多种应用场景。

    应用场景

    PS:当我们刷新页面或第二次在地址栏按回车键,都会直接请求服务器。为了验证本地缓存未过期的情况,需要打开新的tab访问页面。

    1. 缓存静态资源

    Cache-Control:max-age=31536000
    
    • 1

    应用场景:一般用来缓存更新频率低的资源。
    像js/css/img可以设置一年,当文件需要及时更新时,只需要更改html中引用的文件名。(所以html一般需要保持较高的新鲜度)

    示例:

    第一次请求:返回200,max-age=31536000 和 Last-Modified/ETag。

    # request
    GET /script.js HTTP/1.1
    
    # response
    HTTP/1.1 200 OK
    Content-Type: application/javascript
    Last-Modified: Fri, 03 Jun 2022 08:24:43 GMT
    ETag: "6299c54b-24"
    Cache-Control: max-age=31536000
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    第二次请求:返回200,显示数据来自缓存。

    # request
    GET /script.js HTTP/1.1	
    
    # response
    HTTP/1.1 200 OK
    Content-Type: application/javascript
    Last-Modified: Fri, 03 Jun 2022 08:24:43 GMT
    ETag: "6299c54b-24"
    Cache-Control: max-age=31536000
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8yI9E4D9-1659171954471)(C:\Users\Ys\AppData\Roaming\Typora\typora-user-images\image-20220604110638294.png)]

    2. 强制协商缓存

    浏览器在使用缓存之前必需跟原始服务器协商验证。

    # 方法一
    Cache-Control: no-cache
    # 方法二
    Cache-Control: max-age=0, must-revalidate
    
    • 1
    • 2
    • 3
    • 4

    适用场景: 对数据实时性要求高,又能用缓存提高访问速度。

    一般的html使用协商缓存保证实时性,对于并发量大的页面也可以设置较短的缓存有效时间。

    为什么html页面实时性要求高呢?

    如果html没有及时更新,将会导致页面引用的js/css等资源(缓存时间较长)无法及时更新。另外,如果涉及接口变更,前后端必须同步更新的情况,那么访问旧的html将会导致接口报错。

    示例:

    第一次请求:返回200和Last-Modified/ETag。

    # request
    GET /index.html HTTP/1.1
    Cache-Control: max-age=0
    
    # response
    HTTP/1.1 200 OK
    Content-Type: text/html
    Last-Modified: Fri, 03 Jun 2022 07:32:28 GMT
    ETag: "6299b90c-14f"
    Cache-Control: no-cache
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    第二次请求:请求带上Last-Modified/ETag,服务器验证资源未更新,返回304。

    # request
    GET /index.html HTTP/1.1
    If-None-Match: "6299b90c-14f"
    If-Modified-Since: Fri, 03 Jun 2022 07:32:28 GMT
    
    # response
    HTTP/1.1 304 Not Modified
    Last-Modified: Fri, 03 Jun 2022 07:32:28 GMT
    ETag: "6299b90c-14f"
    Cache-Control: no-cache
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3. 禁止缓存

    禁止浏览器缓存资源(响应)和不使用缓存(请求)。

    Cache-Control: no-store
    
    • 1

    适用场景:包含敏感信息等不希望缓存的场景。

    示例:

    第一次请求:返回200和no-store。

    # request
    GET /index.html HTTP/1.1
    Cache-Control: max-age=0
    
    # response
    HTTP/1.1 200 OK
    Content-Type: text/html
    Last-Modified: Fri, 03 Jun 2022 08:25:15 GMT
    ETag: "6299c56b-178"
    Cache-Control: no-store
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    第二次请求:请求服务器,返回200和新资源。

    4. 代理服务器缓存控制

    Cache-Control:public,max-age=600,s-maxage=60
    
    • 1

    public表示浏览器和代理服务器都可以缓存资源;private只有浏览器可以缓存资源;s-maxage表示代理服务器缓存的有效时间。

    http缓存除了存在浏览器(私有缓存),还可以存在代理服务器(公共缓存)。例如常见的CDN,即降低了网络延迟,还能降低服务器的负载

    对于安全性要求高的资源,建议设置为Cache-Control:private,即禁用公共缓存。因为数据一旦被代理服务器缓存下来,就多了一份被攻击的风险。

    各大网站缓存方案

    B站

    • html:no-cache
    • JS文件:max-age=31536000(1年),文件命名带版本号或指纹信息,方便及时更新。
    • CSS文件:max-age=31536000,文件命名带版本号或指纹信息,方便及时更新。
    • 图片:max-age=31536000,文件命名带版本号或指纹信息,方便及时更新。
    • XHR请求: no-cache

    微信

    • html:public, max-age=500

    • JS文件:max-age=31536000(1年),文件命名带版本号或指纹信息,方便及时更新。

    • CSS文件:max-age=31536000,文件命名带版本号或指纹信息,方便及时更新。

    • 图片:max-age=31536000,文件命名带版本号或指纹信息,方便及时更新。

    • XHR请求:no-cache,must-revalidate

    淘宝

    • html:默认
    • js文件:max-age=2592000(一个月),s-maxage=86400;文件命名带版本号或指纹信息,方便及时更新。
    • CSS文件:max-age=2592000,s-maxage=3600;文件命名带版本号或指纹信息,方便及时更新。
    • 图片:max-age=15552000;文件命名带版本号或指纹信息,方便及时更新。

    新浪

    • html: max-age=60
    • js文件:max-age=31536000;max-age=14400;max-age=3600
    • CSS文件:public, max-age=14400
    • 图片:max-age=31536000(一年)

    实现方式

    1. HTML缓存

    通过 meta标签 的http-equiv和content来设置报文头:Cache-Control和Expires。

    <meta http-equiv="Expires" content="Mon, 20 Jul 2013 23:00:00 GMT" />
    <meta http-equiv="Cache-Control" content="max-age=7200" />
    
    • 1
    • 2

    用meta标签的http-equiv属性来设置强缓存。用法简单,不需要服务器支持。适合更新频率低的页面。

    2. nginx缓存

    当nginx作为静态资源服务器时,通过配置http头保证浏览器缓存行为一致。

    # html使用协商缓存
    location ~.*\.html$
    {
        add_header Cache-Control no-cache;
        # 作用跟Cache-Control no-cache一致;兼容HTTP/1.0
        add_header Pragma no-cache;
    }
    
    # 对于更新频率低的,缓存有效时间可设置长一点。
    location ~.*\.(js|css|png|jpg)$
    {
        expires  365d;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    3. webpack文件名hash

    entry:{
        main: path.join(__dirname,'./main.js'),
        vendor: ['react', 'antd']
    },
    output:{
        path:path.join(__dirname,'./dist'),
        publicPath: '/dist/',
        filname: 'bundle.[contenthash].js'
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    通过webpack打包,自动给文件名加上hash值。其中,contenthash表示hash值由文件内容计算得到,内容不同产生的contenthash值也不一样。

    总结

    为了提高网页的访问速度,降低服务器的负载,我们需要一个缓存方案,而应用最广的就是http缓存。

    http缓存的核心是过期时间和协商缓存:服务器在返回资源时指定过期时间,浏览器拿到资源后存起来,后续请求如果资源未过期就返回缓存数据,不用再请求服务器了;当浏览器缓存过期后,再和服务器协商确认资源是否更新,如果更新则返回新资源,否则返回304。

    基于http缓存,我们可以实现对静态资源的缓存控制,从而提升网站性能。通常会把js和css等更新频率低的资源设置较长的过期时间,把html等实时性要求高的资源设置为不缓存或者强制协商缓存。

    最后,列举了各大网站的缓存方案可供借鉴,介绍了通过html和nginx等方式来实现缓存配置。

    参考资料

    MDN

    HTTP 的缓存为什么这么设计?

    HTTP缓存协议实战

    前端缓存最佳实践

    HTTP 缓存别再乱用了!推荐一个缓存设置的最佳姿势!

  • 相关阅读:
    Expected linebreaks to be ‘LF‘ but found ‘CRLF‘. 使用 ESlint 插件自动格式化配置 解决报错
    第7篇 vue的模块化与babel的转换
    110 个主流 Java 组件和框架整理,常用的都有,建议收藏!!
    开源日报 0822 | 语音识别与推理
    Redis 3.2.3 crashed by signal: 11 服务宕机问题排查
    径向基函数拟合
    树莓派4:安装 .NET 6 SDK 并测试连接 MSSQL SERVER(无 mono)
    利用VB宏设置将多个excel表合并为一个
    springboot+vue+elementUI基于SpringBoot的冬奥会科普平台#毕业设计
    2021CCPC 哈尔滨(B D E I J)
  • 原文地址:https://blog.csdn.net/weixin_39805244/article/details/126073670