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

背景

页面的性能对于用户的体验起着至关重要的作用,根据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 stringconst 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.rhkb.cn/news/102906.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

期权分仓开户资金是否安全?具体保障措施有哪些?

网上关于期权分仓系统的真假一直都没有定论,两方人的争论也让很多没有接触过期权分仓系统的人摸不着头脑,那么期权分仓靠谱吗?资金在里面安全吗?下文为大家科普期权分仓开户资金是否安全?具体保障措施有哪些? 一、期权…

arm:day9

1。思维导图 2..I2C实验,检测温度和湿度 iic.h #ifndef __IIC_H__ #define __IIC_H__ #include "stm32mp1xx_gpio.h" #include "stm32mp1xx_rcc.h" #include "gpio.h" /* 通过程序模拟实现I2C总线的时序和协议* GPIOF ---> AHB4…

指针(初阶)

1. 指针是什么? 指针是什么? 指针理解的2个要点: 1. 指针是内存中一个最小单元的编号,也就是地址 2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量 总结:指针就是地址&…

K8s中的Namespace是什么?

如何理解Namespace默认的Namespace使用Namespace的好处创建和使用Namespace使用命令行创建使用YAML文件创建Namespace 用例切换Namespace删除Namespace 感谢 💖 hello大家好😊 由于能够无缝管理和扩展工作负载,Kubernetes (简称K8…

【图论】拓扑排序

一.定义 拓扑排序是一种对有向无环图(DAG)进行排序的算法,使得图中的每个顶点在排序中都位于其依赖的顶点之后。它通常用于表示一些任务之间的依赖关系,例如在一个项目中,某些任务必须在其他任务之前完成。 拓扑排序的…

Flutter对象状态动态监听Watcher

场景:当一个表单需要在表单全部或者特定项赋值后才会让提交按钮可点击。 1.普通实现方式: ///场景:检查[test11][test12][test13]均不为空时做一些事情,例如提交按钮变成可点击String? test11;String? test12;int? test13;///当…

rust库学习-env_logger(actix-web添加彩色日志、rust添加彩色日志 )

文章目录 介绍actix-web启用彩色日志crate地址&json格式日志 我们在进行rust的web开发时,如果不指定日志,就不会有输出,非常不友好 这里我们使用env_logger进行日志打印 介绍 env_logger 需要配合 log 库使用, env_logger 是 Rust 社区…

Docker基本部署和相关操作

1.安装docker服务,配置镜像加速器 1、yum安装并且添加源信息 yum install yum-utils device-mapper-persistent-data lvm2 -y yum-config-manager --add-repo https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/docker-ce.repo2、修改一些配置信息 sed…

视频监控平台EasyCVR视频汇聚平台档案库房图书馆等可视化管理平台应用场景全面解析

档案是一种特殊的记录留存文献,具有珍贵的精神财富价值。它们是人类活动的真实见证,是辉煌时刻的历史记录,在社会发展和经济建设中发挥着至关重要的作用。 随着市场经济的不断发展和人类文明的飞速推进,档案的价值和作用变得越来…

C#实现简单TCP服务器和客户端网络编程

在C#中进行网络编程涉及许多类和命名空间,用于创建和管理网络连接、传输数据等。下面是一些主要涉及的类和命名空间: System.Net 命名空间:这个命名空间提供了大部分网络编程所需的类,包括: IPAddress:用于…

数据结构(6)

2-3查找树 2-结点:含有一个键(及其对应的值)和两条链,左链接指向2-3树中的键都小于该结点,右链接指向的2-3树中的键都大于该结点。 3-结点:含有两个键(及其对应的值)和三条链,左链接指向的2-3树中的键都小于该结点&a…

什么是JVM ?

目录 一、JVM 简介 1.1 JVM 发展史 1.Sun Classic VM 2.Exact VM 3.HotSpot VM 4.JRockit 5.J9 JVM 6.Taobao JVM(国产研发) 1.2 JVM 和《Java虚拟机规范》 二、 JVM 运行流程 JVM 执行流程 三、JVM 运行时数据区 3.1 堆(线程共享…

【TypeScript】声明文件

在 TypeScript 中,声明文件(Declaration Files)用于描述已有 JavaScript 代码库的类型信息,以便在 TypeScript 项目中使用这些代码库时获得类型支持。 当你在 TypeScript 项目中引用外部 JavaScript 模块或库时,可能会…

【开发】tips:视频汇聚/视频云存储/视频监控管理平台EasyCVR如何提升网络稳定

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快,可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等,以及支持厂家私有协议与SDK接入,包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安…

字节跳动 从需求到上线全流程 软件工程流程 需求评估 MVP

走进后端开发流程 整个课程会带大家先从理论出发,思考为什么有流程 大家以后工作的团队可能不一样,那么不同的团队也会有不同的流程,这背后的逻辑是什么 然后会带大家按照走一遍从需求到上线的全流程,告诉大家在流程的每个阶段&am…

iOS 分别对一张图的局部进行磨砂,拼接起来不能贴合

效果图 需求,由于视图层级的原因,需要对图片分开进行磨砂, 然后组合在一起 如图,上下两部分,上下两个UIImageVIew大小相同,都是和图片同样的大小,只是上面的UIimageVIew 只展示上半部份 &#…

flutter TARGET_SDK_VERSION和android 13

config.gradle ext{SDK_VERSION 33MIN_SDK_VERSION 23TARGET_SDK_VERSION 33COMPILE_SDK_VERSION SDK_VERSIONBUILD_TOOL_VERSION "33.0.0"//兼容库版本SUPPORT_LIB_VERSION "33.0.0"}app/build.gradle里面的 defaultConfig {// TODO: Specify your…

VMWare Workstation 17 Pro 网络设置 桥接模式 网络地址转换(NAT)模式 仅主机模式

文章目录 网络模式配网要求CentOSDHCP虚拟网络桥接模式默认配置测试手动配置测试 网络地址转发模式 (NAT)还原配置虚拟网络配置默认配置测试手动配置测试 仅主机模式 网络模式 桥接模式: 主机与虚拟机对等, 虚拟机注册到主机所在的局域网, 会占用该网络的IP该局域网内的所有机…

Git企业开发控制理论和实操-从入门到深入(二)|Git的基本操作

前言 那么这里博主先安利一些干货满满的专栏了! 首先是博主的高质量博客的汇总,这个专栏里面的博客,都是博主最最用心写的一部分,干货满满,希望对大家有帮助。 高质量博客汇总https://blog.csdn.net/yu_cblog/cate…

solidity0.8.0的应用案例11:透明代理合约

选择器冲突 智能合约中,函数选择器(selector)是函数签名的哈希的前4个字节。例如mint(address account)的选择器为bytes4(keccak256("mint(address)")),也就是0x6a627842. 由于函数选择器仅有4个字节,范围很小,因此两个不同的函数可能会有相同的选择器,例如…