【http2/浏览器缓存】网站nginx优化小记

1,733 阅读7分钟

最近随着个人博客的功能不断迭代,网站的首屏加载也变得越来越慢,打开Lighthouse对网站进行分析网站的性能竟然下降到了惊人的34分,这是不能忍的,于是笔者结合Lighthouse给出的优化建议,从nginx角度出发,对博客进行了一系列优化,本篇博客是本次优化的总结;

  1. HTTP1.1升级HTTP2
  2. 浏览器缓存策略

1,升级HTTP2

查看请求头发现目前网站请求还采用HTTP1.1版本,其弊端如下:

  1.    高延迟 — 队头阻塞(Head-Of-Line Blocking)
  2.    无状态特性 — 阻碍交互
  3.    明文传输 — 不安全性
  4.    不支持服务端推送

其中对网站性能造成影响的主要是前两点,

   1,在http1.1服务器是按请求的顺序响应的,如果服务器响应慢,会招致客户端⼀直请求不到数据,也就是队头阻塞;

   2,请求 / 响应头部(Header)未经压缩就发送,⾸部信息越多延迟越⼤。

除此之外,Chrome有个机制,对于同一个域名,默认允许同时建立 6 个TCP持久连接,使用持久连接时,虽然能公用一个TCP管道,但是在一个管道中同一时刻只能处理一个请求,在当前的请求没有结束之前,其他的请求只能处于阻塞状态。 对此http2,分别采用了二进制传输,Header压缩,多路复用来解决上面的问题,升级http2可以大大提升网站响应速度,具体如下: header压缩:

   静态字典:常见的头部名称,如,get/post/cookie等

   动态字典:比如一个req过来,会将字段保存在动态字典中,再一个req,若发现相同的字段,无需传递,只需要使用动态字典中的标识;

流/帧 + 多路复用:

   http2.0提出了流的概念,每个请求对应一个流,每个流都有唯一的ID,用来区分不同的req/rsp;

   基于流的概念,又提出了帧:一个请求的数据被分成多个帧,方便进行数据传输。每个帧都属于某一个流ID;

   多路复用:在一个TCP链接上,可以同时发起无数个请求,并且响应可以同时返回;

请求划分优先级:

   多路复用带来的一个问题是,在共享连接的基础上会存在一些关键请求被阻塞,SPDY允许给每个请求设置优先级,这样重要的请求就会优先得到响应

服务端推送:

   在HTTP1.x中,访问一个页面,浏览器首先获取HTML资源,然后在解析页面时增量地获取其他资源,服务器必须等待浏览器发出请求后才下发页面内资源。而服务器实际上是知道页面内资源有哪些的,如果服务器能够在浏览器显式请求资源之前就将资源推送到浏览器,页面加载速度将会大大提示,这也是本篇的主旨。

   简单来讲,就是当用户的浏览器和服务器在建立链接后,服务器主动将一些资源推送给浏览器并缓存起来,这样当浏览器接下来请求这些资源时就直接从缓存中读取,不会在从服务器上拉了,提升了速率

nginx实际操作如下:

nginx安装with-http_v2_module模块

修改nginx.conf

  # HTTPS server
  server {
    # listen    443 ssl;
    # 启动http2
    listen     443 ssl http2;
    listen     [::]:443 ssl http2;
  }

修改完成后打开控制台查看网站请求协议,看到h2即代表修改成功;

顺带一提,队头阻塞是http1.0,http1.1,http2都没能解决的问题,只能不断优化,直到http3.0才得以解决;

在http1.0中,它的网络响应方式是串行响应的模式,必须要等上一次请求回来才能对下一个请求进行响应。这里如果上一个请求卡死了,那么就会导致整个网站卡死,获取不到数据,还有就是每一次的网络请求都需要做三次握手和四次挥手,没有长连接是一个很大的问题。

在http1.1中,客户端能够进行“并行”发送多个请求,但服务器必须按顺序响应,这使得服务端仍然存在队头阻塞;

在http2.0中,通过多路复用技术解决了http层面上的队头阻塞,但是tcp层面的毫无办法。TCP 的队头阻塞并没有彻底解决。

在http3.0中,彻底弃用了TCP协议,改用可靠的UDP协议(QUIC),彻底解决队头阻塞问题;

2,浏览器缓存策略

查看请求Cache-Control字段,可以看到清一色no-store,这代表这我们没有进行浏览器缓存。对此,我们必须为我们的网站加入缓存策略。

浏览器缓存分为:

1 强缓存(不需要向服务器索要缓存)

  设置 expires:

    就是过期时间,例如,expires: Tue, 18 Apr 2023 06:29:41 GMT 表示缓存将在这个时间后过期。这个到期日期是一个绝对日期。如果修改了本地日期,或者本地日期与服务器日期不一致,那么缓存过期时间就会出错。

  设置 Cache-Control:

    HTTP/1.1增加了一个新的字段,cache-control 可以通过 max-age 字段设置过期时间,cache-control: max-age=7776000 另外 cache-control 还可以设置private/no-cache 等字段。

2 协商缓存(需要询问服务器缓存是否过期,协商缓存需要配合强缓存使用)

  last-modified

    即最后修改时间,当浏览器第一次请求该资源时,服务器会在响应头中添加 last-modified。当浏览器再次请求该资源时,浏览器会在请求头中带上 if-modified-since 字段,该字段的值为上一个服务器返回的最后修改时间,服务器比较两次,如果相同则返回 304,否则返回新的资源并更新 last-modified。

  ETag

    HTTP/1.1 中的一个新字段表示文件的唯一标识符,只要文件内容发生变化,就会重新计算ETag。缓存过程与 last-modified 相同:服务器发送 ETag 字段 -> 浏览器再次请求时发送 If-None-Match -> 如果ETag值不匹配,则文件已更改,返回新的资源并更新ETag,匹配则返回304。

  last-modified 和 ETag 的比较

    ETag 比 last-modified 更准确:如果打开没有修改的文件,last-modified 也会改变,last-modified 的单位时间是秒。如果文件在一秒钟内被修改,它仍然会命中缓存。

如果没有设置缓存策略,浏览器会将响应头中的Date减去 last-modified 值的10%作为缓存时间。

一般一个SPA网页采取的缓存方式为:html文件不设置缓存,Js、CSS等静态资源文件采用强缓存 + 协商缓存,同时静态资源文件在编译时带上hash值以消除代码更新时缓存带来的影响。

具体nginx配置如下:

location / {
  if ($request_filename ~* .*.(?:htm|html)$) {
    add_header Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate";
  }
  if ($request_filename ~* .*.(?:js|css|json|png|jpg|jpeg|gif|gz|svg|mp4|ogg|ogv|webm|htc|xml|woff|woff2)$) {
      # 协商缓存
    expires 1d;
  }
}

修改完成后,查看请求头cache-control值为no-cache,并且响应头带有ETag与last-modified,代表开启协商缓存成功。

至此,nginx改造完成,同时根据Lighthouse给出的建议,对网站进行了其他方面的改造,如(合理的分包、预加载首屏资源等),重新进行Lighthouse分析,终于,网站进入了80分行列,本次网站优化完毕;