我是怎么从0到1搭建性能门禁系统的

news2025/4/11 8:28:34

背景

页面的性能对于用户的体验起着至关重要的作用,根据Mobify 研究发现,首页加载时间每减少100 毫秒,用户留存率就会增加1.11%。所以做好页面的性能优化,对于网站来说是一个非常重要的步骤。
在解决问题之前需要度量问题,最好用一些可量化的客观的数据指标来衡量当前的问题。这里我们就使用Chrome中的Lighthouse来描述页的性能状况,这里在下面的Lighthouse的部分会介绍
这里又会引申出另一个问题,由于我们的业务场景各不相同,导致我们的页面也是各有各的特点,比如音视频类型的网站里面的流媒体比较多,而淘宝、天猫京东,这些电商网站他的图片就非常多还有可能会做成长页面的效果,而一般的业务系统可能表格和弹框比较多。所以我们应该在上面比较通用的基础之上再结合自己实际的业务特点来制定一套适合自己所在公司的性能模型。

性能检测利器-Lighthouse

上面提到,描述性能的指标和数据的采集主要底层是依赖于Lighthouse的能力。我们可以对一个页面进行性能检测,会自动生成一个关于当前页面的性能报告,如下所示:
在这里插入图片描述

他的性能模型主要采集6个不同的和时间相关的指标。分别为:

  1. 首次内容绘制时间(FCP)
  2. 首次可交互时间(TTI)
  3. 总阻塞时间(TBT)
  4. 累计布局偏移量(CLS)
  5. 最大内容绘制时间(LCP)
  6. 网页速度(SI)

每个指标的含义和计算的方式我都附在最后的参考资料中了,这里就不一一赘述了。
我们看github上官方文档对lighthouse的整体架构设计有这么一张图:
在这里插入图片描述
由上图左下侧我们可以看到,lighthouse是通过Driver模块和Puppeteer(一个 Node 库,它提供了一个高级 API 来通过 DevTools 协议控制 Chromium 或 Chrome)来控制浏览器,我们可以通过Puppeteer来模拟用户对浏览器的各种操作,比如点击元素,滚动页面,键盘操作。一个常见的场景就是我们自己的产品使用lighthouse进行检测的时候,往往需要用户先登录,此时我们就可以使用Puppeteer来帮助我们先模拟用户登录,登录之后,利用Cookie的同源策略再次检测我们的页面就可以了。
关于详细的Lighthouse和Puppteer的组合使用我已近在之前写了三篇详细的技术文章结合lighthouse的部分源码进行了介绍,这一篇更多的是关于技术 + 产品设计的介绍。如果对lighthouse的原理感兴趣的话,欢迎查看之前的文章
lighthouse的介绍和基本使用方法
Lighthouse组合Puppeteer检测页面
lighthouse-自定义Gatherer与Audits

性能模型

在上面我们提到过不同的业务中的页面会有各自的特点,所以可能需要在lighthouse这种通用的数据基础之上再加入一些适合自己公司业务场景的性能模型,这样综合评估得出的性能评估更加契合我们的业务。在我现在的这个公司中我结合了一些其他指标,比如图片,是否上了CDN首屏的http请求的数量还有DOM元素的深度于广度来综合考量当前页面的性能,整个模型如下所示:
在这里插入图片描述
有了这个性能模型,就相当于我们有了一把尺子可以衡量出页面的整体性能如何,并根据检测之后的结果,具体的指标去进行分析优化。

整体架构

整个产品的架构如下:
请添加图片描述
可以看到前端我们主要使用了React生态的内容,特别注意的是,这里还有有一个浏览器插件,是的你没有看错,开发了一个浏览器插件主要是有两个目的。

  1. 用于收集线上已有的页面路由,不用再人肉手工一个个输入了
  2. 可以像lighthouse扩展一样的使用我们的服务检测页面 + 消息推送

在服务层主要是使用了阿里的Egg,我们会把录入的页面定期的巡检,来观察页面在线上的整体表现和性能变化趋势,来分析不同迭代上线之后对页面性能的影响。巡检之后我们一般有每月的月报可以进行定时定向的推送,也可以设置报警阈值,即当前页面性能低于60分进行主动的告警,意味着这个页面可能存在比较大的性能隐患,需要进行额外的关注
主服务一般会在k8s中的某个容器中,还有有一个服务在单独的虚拟机中,安装必备的环境(浏览器,pupputeer)来检测特定的页面,并返回检测的结果数据。

整体流程图

整体的流程图如下所示:
请添加图片描述
这里比较关键的是有些页面需要前置操作才能访问预期的页面。比较典型的场景就是登录,比如有很多页面我们需要在登录之后才能访问,这时候我们就需要使用puppeteer来创建一个浏览器页面,在这个页面中实现登录,登录之后再利用同源策略访问其他页面即可。如果有更复杂的操作,比如我们期望测的是C页面,但是打开C页面之前必须要进行A操作然后再执行B操作之后才能正常打开C页面,那这个时候我们就可以把操作A,B使用Puppeteer来进行操作。

比如下面就是价值好几个亿的代码,先试用Puppeteer来进行一个有Iframe的登录

private async loginWithIframe (browserInfo, params) {
    // 开始登录
    const { page } = browserInfo;
    try {
      const {
        loginUrl,
        loginIframeId,
        accountLocator,
        pwdLocator,
        loginLocator,
        account,
        pwd
      } = params;
      if (
        !loginUrl || !loginIframeId ||
        !accountLocator || !pwdLocator ||
        !loginLocator || !account ||
        !pwd
      ) throw new Error('登录信息缺失');
      // waitUntil对应的参数如下:
      // load - 页面的load事件触发时
      // domcontentloaded - 页面的 DOMContentLoaded 事件触发时
      // networkidle0 - 不再有网络连接时触发(至少500毫秒后)
      // networkidle2 - 只有2个网络连接时触发(至少500毫秒后)
      await page.goto(loginUrl, { waitUntil: 'networkidle0' });
      const elementHandle = await page.$(`#${loginIframeId}`);
      const frame = await elementHandle.contentFrame();
      await frame.waitForSelector(accountLocator);
      await frame.type(accountLocator, account);
      await frame.type(pwdLocator, pwd);
      await frame.click(loginLocator);
    } catch (error) {
      throw new Error(`登录失败: ${error.message}`);
    }
  }

登录之后我们再进行页面的检测

private async evaluatePage (browserInfo, detectUrl) {
    const { browser } = browserInfo;
    try {
      // lh检测页面参数
      const flags: any = {
        port: (new URL(browser.wsEndpoint())).port,
        output: ['html', 'json'],
        logLevel: 'info',
        onlyCategories: ['performance'],
        chromeFlags: ['--headless'],
        locale: 'zh-CN'
      };
      console.log(`开始检测页面detectUrl: ${detectUrl}`);
      const runnerResult = await lighthouse(detectUrl, flags, desktopConfig);
      console.log('Report is done for', runnerResult.lhr.requestedUrl);
      // `.report` is the report as a string
      const reportList = runnerResult.report;
      return reportList;
    } catch (error) {
      throw new Error(`检测页面失败: ${error.message};`);
    }
  }

检测完成之后我们会保存报告,并对收集的数据进行分析:


  // 存储lh制品报告
  private async generateReport (reportList) {
    try {
      // const env = process.env.NODE_ENV.trim();
      // const port = (env === 'dev') ? 3008 : 3006;
      this.createFolder();
      const [htmlContent, jsonContent] = reportList;
      const uuid = uuidv4();
      const htmlFileName = `html-${uuid}.html`;
      const jsonFileName = `json-${uuid}.json`;
      const dateTime = dayjs().format('YYYY-MM-DD');
      const reportPath = `report/${dateTime}/`;
      const reportDir = `${rootPath}public/${reportPath}`;
      const htmlFilePath = `${reportDir}${htmlFileName}`;
      const jsonFilePath = `${reportDir}${jsonFileName}`;
      fs.writeFileSync(htmlFilePath, htmlContent);
      fs.writeFileSync(jsonFilePath, jsonContent);
      const htmlVisitPath = reportPath + htmlFileName;
      const jsonVisitPath = reportPath + jsonFileName;
      const htmlUrl = `http://${localMachineIp}:${port}/${htmlVisitPath}`;
      const jsonUrl = `http://${localMachineIp}:${port}/${jsonVisitPath}`;
      return {
        htmlUrl,
        jsonUrl
      };
    } catch (error) {
      throw new Error(`存储制品失败: ${error.message}`);
    }
  }

解析报告的逻辑如下:

// 解析报告得到页面评分
const getPageScoreByReport = (jsonContent, appCode = null) => {
  try {
    const report = JSON.parse(jsonContent);
    const { audits } = report;
    const { field } = indicatorVal;
    const {
      siField,
      ttiField,
      tbtField,
      clsField,
      lcpField,
      fcpField,
      nrField,
      dsField
    } = field;
    const fcpInfo = getFCPScore(audits[fcpField]);
    const ttiInfo = getTTIScore(audits[ttiField]);
    const tbtInfo = getTBTScore(audits[tbtField]);
    const clsInfo = getCLSScore(audits[clsField]);
    const lcpInfo = getLCPScore(audits[lcpField]);
    const siInfo = getSIScore(audits[siField]);

    const imgInfo = getImgScore(audits[nrField]);
    const cdnInfo = getCDNScore(audits[nrField], appCode);
    const httpNumInfo = getHttpNumScore(audits[nrField]);
    const http2Info = getHttpProtocolScore(audits[nrField]);
    const domNumInfo = getDomNumScore(audits[dsField]);
    const domDeepInfo = getDomDeepScore(audits[dsField]);
    const timeScore = siInfo.siScore + ttiInfo.ttiScore + tbtInfo.tbtScore + clsInfo.clsScore + lcpInfo.lcpScore + fcpInfo.fcpScore;
    const pageScore = imgInfo.imageScore + cdnInfo.cdnScore + httpNumInfo.httpNumScore + http2Info.http2Score + domNumInfo.domNumScore + domDeepInfo.domDeepScore;
    const totalScore = timeScore + pageScore;
    return {
      ...siInfo,
      ...ttiInfo,
      ...tbtInfo,
      ...clsInfo,
      ...lcpInfo,
      ...fcpInfo,
      ...imgInfo,
      ...cdnInfo,
      ...httpNumInfo,
      ...http2Info,
      ...domNumInfo,
      ...domDeepInfo,
      totalScore
    };
  } catch (error) {
    throw new Error(`解析报告出错: ${error.message}`);
  }
}

整体页面效果如下:
在这里插入图片描述

在实际使用中,我们会把这个检测能力放入到我们研发流程中。即对上线的每一个页面都会检测其页面的整体性能如何,只有达到事先设定的阈值之后才能正常的从开发环境到测试环境到预发环境最后上线。以此来保障我们上线的每一个页面的整体性能表现。

参考资料

Chrome 性能指标 - FCP
Chrome 性能指标 - TTI
Chrome 性能指标 -TBT
Chrome 性能指标 - CLS
Chrome 性能指标 - LCP
Chrome 性能指标 - SI
lighthouse的介绍和基本使用方法
Lighthouse组合Puppeteer检测页面
lighthouse-自定义Gatherer与Audits

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/918496.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

吴师傅教你怎样开启联想电脑管家的极速模式

如果你的笔记本出现卡顿的情况,可以在联想电脑管家里开启极速模式试一下,会有运行速度上的提升,具体方法如下: 1、双击打开桌面上的联想电脑管家; 2、在打开的“联想电脑管家”界面里,点击右上边的“实用工…

mathematica 提取Solve(NSolve)函数变量

直接上例子,非常直观 求解的方程是: 0.7 sin ⁡ ( x ) 0.7 sin ⁡ ( 2 x ) 0.6047 0.7 \sin (x)0.7 \sin (2 x)0.6047 0.7sin(x)0.7sin(2x)0.6047 提取Solve(NSolve)函数变量,列表提取第一个元素 列表提取第3个元素 提取第三个元素的…

timer定时器,使用timer定时器完成LED123点亮

使用timer定时器完成LED123&#xff0c;一秒亮&#xff0c;一秒灭 #include "head.h" #include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/io.h> #include <l…

存在逻辑删除的表字段上建立唯一索引的巧办法 (逻辑删除与唯一索引)

存在逻辑删除的表字段上建立唯一索引的巧办法 首先&#xff0c;我们肯定是清楚地知道唯一键值逻辑删除位如果联合创建唯一索引的话&#xff0c;只能最多存在两条数据&#xff0c;无法满足不断删除新增的需求&#xff0c;所以需要一个巧妙的办法去实现有逻辑删除标志位的唯一索…

Anaconda安装pytorch-cuda

1 查看电脑对应cuda版本 【win-R】> 【cmd】> 确定 执行nvidia-smi&#xff0c;可以从图中看出&#xff0c;该电脑的CUDA Version为&#xff1a;11.6 2 官网查找对应的版本的安装语句 PyTorch官网&#xff1a;https://pytorch.org/ 2.1 可以直观的选择对应的版本 如…

LabVIEW开发聋哑人智能辅助手套

LabVIEW开发聋哑人智能辅助手套 今天的残疾人面临着许多挑战&#xff0c;最大的问题是沟通效率低下。由于这些人通常使用通信来请求基本必需品&#xff0c;因此在他们的日常生活中起着重要作用。能力不同的人通常会找到解决这个问题的方法。残疾人很难表达自己的需求&#xff…

MyBatis的场景应用(动态SQL、模糊查询及映射结果)附(Mybatis中#和$的区别)

一.Mybatis简介 MyBatis&#xff08;之前被称为iBatis&#xff09;是一种开源的持久化框架&#xff0c;它将面向关系数据库的持久层操作封装起来&#xff0c;使得开发人员可以通过简单的配置来实现对数据库的操作。MyBatis提供了灵活且强大的SQL映射功能&#xff0c;能够将数据…

关于融合项目点云pointpillars检测不显示三维检测框问题的解决

这个问题主要还是launch文件中出现了一些偏差。 launch文件的第26行 这里原先是0.6&#xff0c;在检测kitti的时候是0.6&#xff0c;由于kitti是64线激光雷达&#xff0c;我个人用的是16线激光雷达&#xff0c;所以把0.6降到了0.2.出现了三维检测框&#xff0c;问题解决

Linux 安装mysql(ARM架构)

添加mysql用户组和mysql用户 安装依赖libaio yum install -y libaio* 下载Mysql wget https://obs.cn-north-4.myhuaweicloud.com/obs-mirror-ftp4/database/mysql-5.7.27-aarch64.tar.gz安装mysql 解压Mysql tar xvf mysql-5.7.27-aarch64.tar.gz -C /usr/local/ 重命名 …

SQL注入之延时注入

文章目录 延时注入是什么&#xff1f;延时注入获取数据库版本号 延时注入是什么&#xff1f; 延时注入就是利用sleep()函数通过if语句判断所写的语句真假&#xff0c;如果为真返回我们想要的东西&#xff08;例如&#xff1a;数据库的长度&#xff0c;数据库的名字等&#xff0…

C++类练习

作业&#xff1a; 整理思维导图设计一个Per类&#xff0c;类中包含私有成员&#xff1a;姓名、年龄、指针成员身高、体重&#xff0c;再设计一个Stu类&#xff0c;类中包含私有成员&#xff1a;成绩、Per类对象 p1&#xff0c;设计这两个类的构造函数、析构函数和拷贝构造函数…

b树/b+树、时间轮、跳表、LSM-Tree

b树、b树&#xff1a;关系型数据库核心存储结构 1、为什么磁盘数据存储结构用B树、而不用红黑树 磁盘每次读取不是读一个节点、是返回一页数据。 红黑树每次遍历一个节点排除一半数据。 B树通常映射相邻的磁盘页数据。4K mysql索引一个节点隐射16k故而映射4倍&#xff0c;故…

static相关知识点详解

文章目录 一. 修饰成员变量二. 修饰成员方法三. 修饰代码块四. 修饰类 一. 修饰成员变量 static 修饰的成员变量&#xff0c;称为静态成员变量&#xff0c;该变量不属于某个具体的对象&#xff0c;是所有对象所共享的。 public class Student {private String name;private sta…

【多天线传输技术】迫零检测算法、串行干扰相消算法、排序串行干扰相消算法

clc; clear; close all; len_s100000; % 信号长度 snr0:2:20;%信噪比 len_snrlength(snr); s2_1zeros(1,4); ber_zfzeros(1,len_snr); ber_zf_siczeros(1,len_snr); ber_zf_chsiczeros(1,len_snr); for ii1:len_snrerror_zf20;for i1:len_ssnrandi([0,1],4,1); %产生随机信号mo…

35岁的前阿里员工:薪资从46K降到40K进传统企业,太香了,8.30上班,5点下班!

互联网大厂&#xff0c;对每一位程序员而言都是一个向往的地方。高薪、高压、高目标&#xff0c;每个人都为之奋斗不止。然而&#xff0c;在光鲜亮丽的外表之下&#xff0c;却隐藏着无数的焦虑与疲惫。 35岁&#xff0c;对于一个程序员来说&#xff0c;似乎是一个被现实无情提…

性能调优篇 一、概述

目录 五、测试指标 五、测试指标 1、响应时间 提交请求和返回该请求之间使用的时间、一般关注平均响应时间 在垃圾回收环境自中&#xff1a; 暂停时间&#xff1a;执行垃圾收集时&#xff0c;程序的工作线程被暂停的时间 -XX:MaxGCPauseMillis xxx2、吞吐量 单位时间内完…

Linux 生产者和消费者问题

一、相关概念&#xff1a; 1.耦合&#xff1a;耦合是指两个或两个以上的体系或两种运动形式间通过相互作用而彼此影响以至联合起来的现象。在软件工程中&#xff0c;对象之间的耦合度就是对象之间的依赖性。对象之间的耦合越高&#xff0c;维护成本越高&#xff0c;因此对象的…

Spring boot(一)

Spring Boot是一个构建在Spring框架顶部的项目。它提供了一种简便&#xff0c;快捷的方式来设置&#xff0c;配置和运行基于Web的简单应用程序。 它是一个Spring模块&#xff0c;提供了 RAD(快速应用程序开发)功能。它用于创建独立的基于Spring的应用程序&#xff0c;因为它需…

SAP‘s ECC6 EoL(End of Life) 支持服务声明 2027?

前言 一、EoL公告信息&#xff0c;2027&#xff1f; 二、继续使用ECC6.0的选项 1.引入第三方支持 2.S/4 HANA 3.SAP Business ByDesign 4.SAP Business One 总结 最新的公告是&#xff1a;2027年&#xff0c;SAP ECC 6.0将停止得到支持&#xff0c;并退出主流SAP支持&am…

组装电脑及问题排查,成功点亮

组件购买 模块描述渠道价格CPUi5 13600KF 盒装拼多多&#xffe5;1918散热器风冷利民PA120京东&#xffe5;197主板华硕ROG 吹雪 B70G京东&#xffe5;1479内存条金士顿DDR5 16G * 2京东&#xffe5;699固态硬盘致态 Tipro 7000 2t京东&#xffe5;940显卡七彩虹4060京东&…