秒杀项目系列之六: 查询性能优化技术之页面静态化

  1. 截止到目前系统架构图
    在这里插入图片描述
  2. 访问静态资源文件压测
  • HTTP请求参数
    在这里插入图片描述
  • 压测线程属性
    在这里插入图片描述
  • 压测效果: 平均值50ms、95值: 402ms、吞吐量TPS: 3577/sec
    在这里插入图片描述
  1. 访问静态资源优化方案
    在这里插入图片描述
  • 对名词的一些解释
    • ECS: elastic cloud service 弹性云服务器
    • CDN: content delivery network 内容分发网络: 是指一组在地理上分散的服务器,它们协同工作以提供互联网内容的快速交付.也就是说用户能够更快的访问.
      CDN参考链接
    • OSS: object storage service 对象存储服务
  1. 加入CDN后的网络架构图
    在这里插入图片描述
  • 静态资源请求从CDN中获取,如果CDN中没有则回源到nginx
  • 动态资源请求nginx获取.
  1. 静态请求CDN如何操作
  • DNS用CNAME解析到CDN源站
  • 回源缓存设置
  • 强推失效
  1. cache control响应头(请求头中也有这个参数)
    在这里插入图片描述
  • cache control响应头的取值
    • private: 客户端可以缓存
    • public: 客户端和代理服务器都可以缓存
      客户端向服务端发送http请求并获得响应,中间可能会经过nginx反向代理或正向代理,也有可能会经过CDN(内容分发)网络.当设置为public时,这些中间代理服务器也可以缓存.
    • max-age=xxx: 缓存的内容将在xxx秒后失效
    • no-cache: 强制向服务器再验证一次
      将缓存存储在客户端,但是在使用缓存的时候需要向服务器验证一次缓存是否可用.
    • no-store: 不缓存请求的任何返回内容
  • cache control响应头取值的选取
    在这里插入图片描述
  • no-cache中的向服务器再验证一次: 验证的是缓存的有效性判断
    • ETag: 资源唯一标识
      通过将请求资源内容进行MD5加密或hash处理,生成一个唯一标识ETag,再第一次服务器向客户端返回数据的时候将ETag返回给客户端.再一次向服务器端发送http请求的时候,带上ETag,将ETag值和服务器端本地ETag内容做比较,若一致,返回304(内容从上次请求后没有改变),直接使用缓存的即可.
    • If-None-Match: 客户端发送的匹配ETag标识符
    • Last-Modified: 资源最后被修改的时间
    • If-Modified-Since: 客户端发送的匹配资源最后修改时间的标识符
      在这里插入图片描述
      注: 当缓存过期,想要从缓存中获取数据,ETag并不是必须的,如果没有ETag,可以判断last-modified,向服务器端发送if-modified-since请求,如果返回304,则从本地缓存中获取,如果返回200,则从服务器端获取.
      对于ETag字段,如果客户端有ETag字段,则向服务器端发送if-none-match请求,如果服务器端返回304,则读取本地缓存,如果返回200,则从服务器端获取.
  1. 浏览器三种刷新方式
  • 回车刷新或a链接: 看cache-control对应的max-age是否仍然有效,有效则直接from cache,若cache-control中为no-cache,则进入缓存协商机制.
  • F5刷新或command+R刷新: 去掉cache-control中的max-age或直接设置max-age为0,然后进入缓存协商机制.
  • ctrl+F5或command+shift+R刷新: 去掉cache-control和协商头,强制刷新.即再重新从服务器端获取一次.
  • 缓存协商机制
    比较last-modified和ETag到服务端,若服务器端判断没有变化,则返回304,从缓存中获取数据;否则返回200,从服务器端请求数据.
  1. CDN自定义缓存策略
    CDN是介于客户端浏览器和nginx服务器中间的一个代理层.CDN既充当浏览器客户端的服务器角色,也充当了nginx服务器的客户端角色.
  • 可以自定义目录过期时间
    不管服务器端定义的max-age是多少,CDN可以自定义
  • 可以自定义后缀名过期时间
    对.html、.css等不同文件做不同过期时间处理
  • 可以自定义对应权重
    比如可以自定义后缀名过期时间权重比目录过期时间权重大, 则遵循后缀名过期时间
  • 可以通过界面或api强制CDN对应目录刷新(不一定能保证成功)
    忽略设置的所有规则,强制回源获取最新内容.
    在这里插入图片描述
    总结:
  • 如果源站没有缓存配置则遵从阿里云默认缓存配置
  • 如果原站有缓存配置,则有限遵从控制台缓存配置
  • 如果控制台没有缓存配置,则遵从源站的缓存配置.
  1. 静态资源部署策略
  • 静态资源部署策略一: 静态资源文件部署三种方式
    • css、js、img等元素使用带版本号部署,例如a.js?v=1.0不便利,且维护困难
    • css、js、img等元素使用带摘要部署(比如基于文件内容生成hashcode,文件内容不变则hashcode值不变,文件内容变化则hashcode值变化),例如a.js?v=45edw,存在先部署html还是先部署资源的覆盖问题.
      如果先部署js,则html还是引用的老的js,会出现js资源找不到等问题.如果先部署html,新的js还没有部署,html还是引用的老的js.
    • css、js、img等元素使用摘要做文件名部署,例如45edw.js,新老版本并存且可回滚,资源部署完后再部署html.有新老版本两个js文件,而上面的方式只有一个js文件.(推荐)
  • 静态资源部署策略二: 静态资源文件生命周期设置方式
    • 带摘要的静态资源部署方式保持生命周期内不会变,max-age可以设置的很长,无视失效更新周期
    • html文件设置no-cache或较短max-age,以便于更新(一般也可以)
    • html文件仍然可以设置为较长的max-age,依靠动态的获取版本号,请求发送到后端,比较从后端获取的版本号和当前版本号是否相同.若不相同,异步下载最新的版本号的html后展示渲染在前端.(三者中最好的方式)
  • 静态资源部署策略三
    • 动态请求也可以静态化成json资源推送到cdn上
    • 依靠异步请求获取后端节点对应资源状态做紧急下架处理
      先展示老的静态资源,同时发送一个很小的异步ajax请求,获取版本号,比对版本号是否一样,如果不一样,则向后端发送一个http请求,获取最新的静态资源,覆盖掉老的静态页面上的内容.
    • 通过跑批紧急推送cdn内容以使其下架等操作.
      当内容发生改变,版本号发生变更,可以推动cdn内容使其下架.
  1. 无头浏览器
  • 无头浏览器指的是没有图形用户界面的浏览器. 在类似于流行网络浏览器的环境中提供对网页的自动控制,但是通过命令行界面或使用网络通信来执行.
  • 通常用来:
    • Web应用程序中的测试自动化。
    • 拍摄网页截图
    • 对JavaScript库运行自动化测试
    • 收集网站数据
    • 自动化网页交互
  1. 全页面静态化
  • 定义
    在服务端完成html、css,甚至js的load渲染成纯html文件后直接以静态资源的方式部署到cdn上.
  • phantomjs是什么
    phantomjs实现了一个无头(无界面)的webkit浏览器。虽然没有界面,但dom渲染、js运行、网络访问、canvas/svg绘制等功能都很完备,在页面抓取、页面输出、自动化测试等方面有广泛的应用。
  • phantomjs应用
    • 修改需要全页面静态化的实现,采用initView和hasInit方式防止多次初始化.
    • 编写对应轮询生成内容方式
    • 将全静态化页面生成后推送到cdn
  1. phantomjs全页面静态化代码实现(采用initView和hasInit方式防止多次初始化)
  • 客户端安装phantomjs

    • 下载phantomjs
    • 将压缩包放在自己安装目录下,比如/usr/local,并解压缩
  • 在phantomjs目录下新建js目录,并在js目录下创建getitem.js文件

    var page = require("webpage").create();
    // 导入fs文件系统模块
    var fs = require("fs");
    // function为回调函数
    page.open("http://miaoshaserver:8090/resources/getitem.html?id=1", function(status){
          
          
            console.log("status = " + status);
            var isInit = "0";
            // 每隔1s执行一次
            setInterval(function(){
          
          
                    // 如果isInit值不为1,则一直执行initView()方法,
                    if(isInit != "1"){
          
          
                            page.evaluate(function(){
          
          
                                    initView();
                            });
                            isInit = page.evaluate(function(){
          
          
                                    return hasInit();
                            });
                    }else{
          
          
                            // 覆盖写入到getitem.html
                            fs.write("getitemphantom.html", page.content, "w");
                                    // 结束执行
                                    phantom.exit();
                            }
            }, 1000);
    });
    
  • 修改getitem.html文件

    <body>
    	<div>
    		...
    		<!--    设置一个隐藏域,初始值为0,表示没有缓存-->
        	<input type="hidden" id="isInit" value="0">
        </div>
    </body>
    function hasInit(){
        return $("#isInit").val();
    }
    
    function setHasInit(){
        $("#isInit").val("1");
    }
    
    function initView(){
        var isInit = hasInit();
        // 当isInit值为"1"的时候说明已经向服务器端请求过一次了,return
        if(isInit === "1"){
            return;
        }
        $.ajax({
            type:"GET",
            url:"http://" + g_host + "/item/get",
            data:{
                "id":getParam("id")
            },
            xhrFields:{withCredentials:true},
            success:function (data) {
                if(data.status == "success"){
                    g_itemVO = data.data;
                    reloadDom();
                    // 不断刷新,为了让下单按钮在秒杀开始时亮起
                    setInterval(reloadDom, 1000);
                    // 将isInit值设为为"1",直接返回,无需再向服务器请求
                    setHasInit();
                }else{
                    alert("获取信息失败,原因为"+data.data.errMsg);
                }
            },
            error:function (data) {
                alert("获取信息失败,原因为"+data.responseText);
            }
        });
    }
    // jQuery(document).ready()这个方法在dom载入就绪时对其进行操纵并调用执行它所绑定的函数。
    jQuery(document).ready(function(){
    	...
        initView();
    });
    
  • 将getitem.html部署到nginx服务器端

    scp getitem.html root@nginx服务器ip:/usr/local/openresty/nginx/html/resources/
    
  • 使用phantomjs执行getitem.js文件,会生成一个getitemphantom.html文件,该文件中隐藏域的值已经设置成为1了.

    # /usr/local/phantomjs目录下
    sudo bin/phantomjs js/getitem.js
    

    在这里插入图片描述
    从页面访问上可以看出
    在这里插入图片描述

  • 用户如果用浏览器打开 getitemphantom.html 会发现没有网络请求,这是一个完全静态化的 html 页面,把这个页面放到 CDN 上去,就可以在 CDN 层面完全命中,不会再有请求打到源站服务器上去了;

猜你喜欢

转载自blog.csdn.net/qq_26496077/article/details/113190389