谈谈实操中的缓存问题及优化方案

118 阅读5分钟

缓存的问题令人头疼,下面讲讲我在开发中遇到过的问题:

我们公司开发了一个单页面应用,属于是敏捷开发,基本上一个星期发一个版本。然后这里就慢慢暴露了一些问题。

还没有了解过HTTP缓存的朋友,可以先点击链接了解一下。

改进前

在我还没进公司之前,nginx是这样配置的。由于隐私问题,这里我自己部署了一个服务以还原真实场景。

现在部署一个单页面程序,nginx的配置如下:

server {
    listen 8030;
    server_name 192.168.50.50;
    location / {
        root /usr/share/nginx/html;
        add_header Cache-Control 'max-age=86400';
        try_files $uri $uri/ /index.html;
    }
 }

可以看到这个配置非常简单粗暴,设置了拉取路径和缓存时间,缓存时间是86400秒也就是1天。

随着用户越来越多以及加上了用户反馈功能,逐渐出现有这样的场景:

用户:为什么这个功能不见了啊?
客服:刷新一下就好了。
用户:好的,没有问题。
几分钟过后
用户:怎么又不行了啊?
客服:再刷新一下。
用户:怎么这么麻烦。。

到这,客服就反馈给了技术人员,技术总监就把这个活交给了我,写下这篇文章以避免大家有同样的问题。

现在通过一个案例告诉大家这个问题出现的原因。

案例一

还是同样的nginx配置,测试一下到底有什么问题。

server {
    listen 8030;
    server_name 192.168.50.50;
    location / {
        root /usr/share/nginx/html;
        add_header Cache-Control 'max-age=86400';
        try_files $uri $uri/ /index.html;
    }
 }

我准备了三个页面

  • 首页:/
  • 套餐页:/package_order
  • 查询页:/converse_search?country=US

Index.html内容如下:这里只需要关注version:3。

 <div>version:3</div>
 <div id="app"></div>

依次进行如下操作:

  1. 新开窗口1,访问首页看到的内容是version:3

  2. 新开窗口2,访问查询页看到的内容是version:3

  3. 发布新版本version:4

    <div>version:4</div>
    <div id="app"></div>
    
  4. 新窗口3,访问首页看到的内容是version:3

  5. 新窗口4,访问套餐页看到的内容是version:4

  6. 回到窗口1,当前是首页,刷新页面,访问到version4

  7. 新窗口5,访问查询页访问到version3

  8. 新窗口6, 更改查询参数访问查询页-即/converse_search?country=CA访问到version4

这就是出现了用户所说的问题,即每次版本都不统一。

问题解析:

  1. 窗口1,第一次访问首页,拉取最新代码,显示version3

  2. 窗口2,第一次访问查询页,拉取最新代码,显示version3

  3. 发布新版本version4

  4. 窗口3,第二次访问首页,走强缓存,不拉取代码,显示version3

  5. 窗口4,第一次访问套餐页,拉取最新代码,显示version4

  6. 窗口1,刷新页面,浏览器请求头携带Cache-Control:max-age=0,(如果控制台里勾选了停用缓存的话,请求头携带的是Cache-Control:no-cache)强制缓存失效,走协商缓存,协商缓存判定有新版本,状态码200,返回最新代码,显示version4。

    (再次刷新页面的话,会发现协商缓存命中即判定没有新版本,状态码304)

  7. 窗口5,第二次访问查询页,走强缓存,不拉取代码,显示version3

  8. 窗口6,第三次访问查询页,但此时查询参数不一样,相当于一个新地址,拉取最新代码,显示version4

透过现象看本质:

这里无非3种情况:

  1. 在当前页刷新(或者在当前页重新输入相同的网址),强制缓存会失效,走协商缓存,根据缓存结果返回200或者304。

  2. 在新窗口访问页面,如果是第一次访问,直接获取最新代码。

  3. 在新窗口访问页面,不是第一次访问,走强缓存,如果缓存到期,走协商缓存。

而且通过案例可以得知,浏览器的缓存策略是根据url的全路径来确定的,这里的全路径指的是协议、域名、端口、路径和查询参数,但不包括锚点查询参数不一样也会视为不同的资源。

改进后

在了解问题本质后,解决问题就快很多,这里附带前端网络资源请求优化的策略。

server {
    listen 8030;
    server_name 192.168.50.50;
    location / {
        root /usr/share/nginx/html;
        add_header Cache-Control 'no-cache';
        try_files $uri $uri/ /index.html;
    }
    location ~* \.(gif|jpg|jpeg|png|svg|css|js|ico|eot|otf|fon|font|ttf|ttc|woff|woff2)$ {
        root /usr/share/nginx/html;
        expires 1y;
    } 
 }

这里设置html的缓存为no-cache,即每次都需要向服务器发起请求验证资源是否有更新。这样的话无论是命中上面所诉的哪种情况都能拿到最新的代码。

设置其他静态资源为1年的缓存,这样只要版本不变,静态资源都不会请求服务器,从而优化网络传输并减轻服务器压力。

所以,根据这个依据,可以引出打包工具的优化策略

  1. 分包
  2. 设置contenthash

解释优化策略:

  1. 设置完contenthash后,只要文件内容没变,打包出来的bundle的哈希值就不变,那么将会命中缓存。
  2. 分包。分成多个包后,可以并行请求多个资源,提高效率。拆成多个包后,因为设置了contenthash,那么改动某些内容,影响的只是其中某个或某几个bundle,从而提高命中缓存的几率。如果不分包,每次都只打包成一个index.[contenthash].js,那么改动一点内容就会有新的哈希而导致缓存失效重新请求,而这个请求的大小还很大,很显然是不合理的。

至此,缓存的问题就解决了,顺便还优化了一下打包和网络请求。

如果觉得有帮助,帮忙点个赞,谢谢~