当前位置: 代码迷 >> 综合 >> 传输加载优化(资源压缩、资源缓存、HTTP2)
  详细解决方案

传输加载优化(资源压缩、资源缓存、HTTP2)

热度:12   发布时间:2023-12-25 12:44:45.0

启用压缩 Gzip

Gzip 是用来做网络资源压缩,帮助我们减少资源文件在网络传输大小的技术,可以高达 90%

如下是 MacOs 安装方法,Windows 安装方法及使用可以参考我这篇文章:项目技术架构-Nginx 服务器搭建

  • 安装 homebrew:https://brew.sh/index_zh-cn
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
  • 安装 nginx
brew install nginx
  • 运行 ngix
sudo brew services start nginx
  • 查看配置文件
vim /usr/local/etc/nginx/nginx.conf

修改为如下配置:

  • 访问:localhost:8090 即可(注意每行结尾都需要加 ;,路径需要使用 /
server
{ charset utf-8;                                        # 字符编码listen 8090;                                          # 端口server_name localhost;root E:/dist;                                         # 资源文件路径location / { 											# 对所有路由生效的配置add_header Access-Control-Allow-Origin *;			# 防止跨域(生产环境需改为实际域名)}
}

配置 gzip:

http {// 开启gzipgzip on;// 文件至少1k才进行压缩gzip_min_length 1k;// 压缩级别,有1-9,压缩比例越高,对cpu的消耗也越高,权衡下取6,比较合适的值gzip_comp_level 6;// 压缩文件类型,通常会对文本类文件进行压缩,图片类一般不进行压缩gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/xml text/javascript application/json;// 对gzip已经压缩的静态资源直接利用gzip_static on;// 会在响应头部添加vary的属性,告诉客户端我们是否启用了gzip压缩gzip_vary on;// buffer优化压缩过程gzip_buffers 4 16k;// 压缩使用的http版本gzip_http_version 1.1;
}

启用 Keep Alive

这个技术可以帮助我们对 TCP 链接进行复用,也就是说当我们和一台服务器进行 TCP 建立连接之后,接下来的请求就都不需要重复建立链接。Nginx 默认开启 keep-alive

  • 它是 HTTP 标准中的一部分,多数情况是有益无害的,所以在 HTTP1.1 以后,Keep Alive 默认开启
  • Initial connection 为 TCP 链接的建立,后续资源加载就没有 Initial connection

可以在 Request Headers 中看到 keep-alive 参数

http {keepalive_timeout  65; // 超时时间,65s内没使用TCP链接就会断掉keepalive_requests 100; // 客户端和服务端进行TCP链接后,会开始计数,第101个请求就需要重新建立 TCO链接
}

HTTP 资源缓存

缓存资源

  • 提高重复访问时资加载的速度

Nginx下关于缓存控制字段cache-control的配置说明 - 运维小结

HTTP 缓存方案:

  • Cache-Control/Expires
  • Last-Modified + If-Modified-Since
  • Etag + If-None-Match

Cache-Control/Expires

  • HTTP 1.0 中通过 Pragma 控制页面缓存,通常设置为 no-cache 并加上 expires: 0(立即过期,下次再用时去服务端拿)

  • HTTP 1.1 中启用 Cache-Control 来控制页面是否缓存,常用参数:no-cachepublicno-storemust-revalidate

    配置后两个主要是为了兼容性问题

  • 因为 JS 和 CSS 在 Webpack 里都使用 Hash 命名放,这也可以保证 HTML 更新到最新,拿到的 JS 和 CSS 也是最新的

server
{location / {index index.html index.htm;try_files $uri /index.html;if ($request_filename ~* .*\.(?:htm|html)$) {add_header Cache-Control "no-cache, must-revalidate";add_header "Pragma" "no-cache";add_header "Expires" "0";}if ($request_filename ~* .*\.(?:js|css)$) {expires 7d;}if ($request_filename ~* .*\.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm)$) {expires 7d;}}
}

客户端第一次请求一个 URL,服务器返回状态是 200,同时有一个 Last-Modified 报头的属性标记

Last-Modified:Tue, 24 Feb 2019 08:01:04 GMT

客户端第二次请求此 URL,浏览器会向服务器传送 If-Modified-Since 报头,询问该时间是否被修改过。如果服务器资源没有变化,自动返回 304,内容为空,客户端直接从缓存中取内容即可;如果资源有变化,则向客户端发送最新资源

If-Modified-Since:Tue, 24 Feb 2019 08:01:04 GMT

Etag 同理,第一次请求会服务器会返回 Etag 报头

Etag:“5d8c72a5edda8d6a:3239“

第二次请求会向服务器传送 If-None-Match 报头

If-None-Match:“5d8c72a5edda8d6a:3239“

缓存网站参考

更多配置可以看:HTTP Headers

天猫

  • max-age:设置缓存存储的周达周期,单位秒

  • s-maxage:只用于共享缓存,比如:CDN 缓存(s -> share)

    max-age 用于普通缓存,s-maxage 用于代理缓存

  • 它会跟服务器进行重新确认(携带 if-none-match )去确认

知乎

  • public:响应可以被任何对象(发送的客户端、代理服务器)缓存
  • private:响应只能被单个用户缓存,不能作为共享缓存(代理服务器不能缓存)
  • no-store:绝对禁止缓存
  • no-cache:资源不进行缓存,但是设置了这个不代表浏览器不缓存,而是缓存前要向服务器确认资源是否被更改,因为有时为了保险起见还会加上 private 指令或将过期时间设为过去的时间

Google 开发者

  • must-revalidate:缓存必须在使用之前验证旧资源的状态,并且不可使用过期资源
  • 由于通过了 service worker,它并没有真正和服务器进行确认,可以直接去使用

Service Worker

Service Worker API

  • 加速重复访问

  • 离线支持

    用户在没有网络的情况下(offline)也可以让用户访问我们的网页

serviceWorker 也有自己的生命周期,首先要注册安装激活才能使用,打包后的目录里会生成 asset-manifest.json 里面定义了哪些资源要进行缓存、缓存文件的文件名、相关的版本信息会存在 precache-manifest 里,每个文件都有先关版本信息

  • 需要使用两个插件生成 serviceWorker,一个叫 WorkboxWebpackPlugin,另一个叫 ManifestPlugin(生成 asset-manifest.json)它会决定哪些资源进行缓存,通常会把所有静态资源 HTML、CSS、JS 都进行缓存,图片或视频资源一般不会缓存
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin')
const smp = new SpeedMeasurePlugin()
const WorkboxWebpackPlugin = require('workbox-webpack-plugin')
const ManifestPlugin = require('webpack-manifest-plugin')module.exports = smp.wrap({
    plugins: [new WorkboxWebpackPlugin.GenerateSW({
    clientsClaim: true,exclude: [/\.map$/, /asset-manifest\.json$/],importWorkboxFrom: 'cdn',navigateFallback: paths.publicUrlOrPath + 'index.html',navigateFallbackBlacklist: [new RegExp('^/_'),new RegExp('/[^/?]+\\.[^/]+$'),],}),new ManifestPlugin({
    fileName: 'asset-manifest.json',publicPath: paths.publicUrlOrPath,generate: (seed, files, entrypoints) => {
    const manifestFiles = files.reduce((manifest, file) => {
    manifest[file.name] = file.pathreturn manifest}, seed)// 从入口文件开始把所有涉及到的文件全部加到asset-manifest.json里// app是入口文件,通常项目里的入口文件是mainconst entrypointFiles = entrypoints.app.filter(fileName => !fileName.endsWith('.map'))return {
    files: manifestFiles,entrypoints: entrypointFiles,}},}),],
})

在入口文件里注册即可

import * as serviceWorker from './serviceWorker'serviceWorker.register()

Service Worker 原理:

  • 在客户端和服务端建立一个中间层,做了存储

Service Worker 注意:

  • 延长了首屏时间,但页面总加载时间减少

  • 兼容性

  • 只能在 localhost 或者 https 下使用(为了保证安全性)

HTTP2 提升

HTTP 2.0 和 HTTP 1.1 相比有哪些优势呢?

HTTP1.1 问题

  • 高延迟带来页面加载速度的降低
  • 重复传输的体积巨大的 HTTP 头部
  • 不支持服务器推送消息

为了解决性能问题做过的努力:

  • Spriting 合并多张小图为一张大图供浏览器 JS 切割使用
  • Inlining 内联,将图片嵌入到 CSS 或者 HTML 文件中,减少网络请求次数
  • Concatenation 拼接,将多个体积较小的 JavaScript 使用 webpack 等工具打包成 1
    个体积更大的 JavaScript 文件
    但是 1 个文件的改动导致用户重新下载多个文件
  • Sharding 分片,将同一页面的资源分散到不同域名下,提升连接上限

HTTP2 优势

  • 二进制传输

    HTTP 1.1 基于文本传输,效率低且不安全

    HTTP 2 基于二进制编码传输,安全且能进行很好的压缩,提高了传输效率

  • 请求响应多路复用

    HTTP 1.1 实现是基于请求-响应模型,同一个连接中 HTTP 完成一个事务才能处理下一个事务,如果响应迟迟不来,后续请求无法发送,造成了 对头阻塞 问题。如果并发多个请求就需要多个 TCP 连接,开启 keep-alive,虽然可以用多次,但是同一时刻只能有一个 HTTP 请求

    HTTP 2 所有相同域名的请求都通过一个 TCP 连接并发完成,多个 Stream 复用一条 TCP 连接

  • Server push

    HTTP 1.1 不支持服务器主动推送资源给客户端,都是客户端向服务器发起请求后,才能获取到服务器响应的资源

    HTTP 2 服务器可以主动推送资源文件,减少消息传递次数。客户端发起请求,必须使用奇数号 Stream,服务器主动推送,使用偶数号 Stream(会先发送 PUSH_PROMISE 帧,告诉客户端接下来在哪个 Stream 发送资源)

  • 头部压缩(HTTP 协议报文是有 Header + Body 构成)

    HTTP 1.1 可以使用头字段(Content-Encoding)指定 Body 压缩方式(gzip),但是 Header 没有针对它的优化手段

    HTTP 2 使用 HPACK 算法进行压缩,对于常见的头通过 静态表和 Huffman 编码 方式,后续请求头,可以建立 动态表

HTTP/2 核心概念

  • 连接 Connection:1个 TCP 连接,包含一个或者多个 Stream
  • 数据流 Stream:一个双向通讯数据流,包含 1 条或者多条 Message
  • 消息 Message:对应 HTTP/1 中的请求或者响应,包含一条或者多条 Frame
  • 数据帧 Frame:最小单位,以二进制压缩格式存放 HTTP/1 中的内容

在这里插入图片描述

开启 HTTP2

  • HTTPS
  • 适合较高的请求量

server
{ listen 843 ssl;server_name localhost;ssl on;ssl_certificate /path/to/server.crt;ssl_certificate_key /path/to/server.key;ssl_session_cache shared:SSL:1m;ssl_session_timeout 5m;ssl_ciphers HIGH:!aNULL:!MD5;ssl_prefer_server_ciphers on;
}

自签名证书

  • 执行最得到 server.crtserver.key,在工程目录下新建 ssl 文件夹,将其拷贝进去
openssl genrsa -des3 -passout pass:x -out server.pass.key 2048openssl rsa -passin pass:x -in server.pass.key -out server.key openssl req -new -key server.key -out server.csr openssl x509 -req -sha256 -days 3650 -in server.csr -signkey server.key -out server.crt 

访问 https://localhost:843,会出现如下图。因为我们使用的是自签名证书,直接在键盘输入 thisisunsafe,页面就可以绕过证书的验证了

所有的网络资源都变成 http2 的协议了,还有 h3(这里 h3 是对 google 外部资源的请求)

HTTP 1.1 虽然可以用 keep-alive 复用同个 TCP 链接,但是资源还是有顺序的,会形成阻塞

HTTP 2 真正做到了异步或并发的对资源进行传输,同一个时刻可以发起多个资源请求,可以将不同资源信息同时通过网络传回浏览器

Server Push(服务器推送)

正常客户端拿到资源都是向服务器发起请求,服务器再把资源推送给客户端,这个来回是有消耗的(TTFB),如果能让服务器提前把这些东西推送到客户端,就能节约一定的网络开销

server
{location / {index index.html index.htm;http2_push /img/me0.jpg;http2_push /img/me1.jpg;http2_push /img/me2.jpg;}
}

重启 nginx,可以发现图片没有了绿色部分(TTFB),少了请求返还回路的过程

  • Initiator 图片为 Push,这种资源是通过 server push 提前推送到浏览器的

服务端渲染 SSR

彻底理解服务端渲染 - SSR原理 #30

SSR 好处:

  • 加速首屏加载
  • 更好的 SEO

基于 Next.js 实现SSR

npm init -y
npm install next react react-dom

添加 scripts 执行脚本:

// package.json
{
    "scripts": {
    "dev": "next"}
}

index.jsx 中添加如下内容,之后 npm run dev 即可

  • 服务端渲染会把页面上显示的所有内容都放在 html 里
  • next.js 已经帮我们把代码进行基于路由的代码拆分,里面提供了 Link 组件
import React from 'react'
import Header from './Header.jsx'export default () => (<div><Header /><p>Home</p></div>
)

是否使用 SSR

  • 架构-大型,动态页面,面向公众用户(是否关心首屏速度)
  • 搜索引擎排名很重要(前面的页面使用静态页面,后面页面使用 vue react 实现动态加载)