我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。

本文作者:琉易 liuxianyu.cn

这一篇是系列文章:

搭建自动化 Web 页面性能检测系统 —— 设计篇

搭建自动化 Web 页面性能检测系统 —— 实现篇

作为一个前端想去做全栈的项目时,可能第一个思路是 node + vue/react。一开始可能会新建多个工程目录去实现,假设分别为 web 和 server,也许还有管理后台的代码 admin,那么就有了三个工程的代码。此时为了方便管理就需要在远程仓库新建一个 group 统一管理代码,一般这种方式称之为 MultiRepo。

这显然是不够简洁的,对于开发者而言也不便于开发和部署。这类多模块的项目我们可以引入 Monorepo 的概念,下面是一些优化方法的尝试,以 yice-performance(易测) 作为例子讲解,本地设备为 M1 芯片的 arm64v8 平台。

一、node 托管静态页面

可以将 web 打包的代码交给 node 托管,此时就可以将 web 的代码作为一个文件夹放到 server 的目录中,这时候我们一般直接访问后端接口的根路径即可。如:yice-performance - v1.0

对应的 nginx 配置一般为:

server {
listen 80;
server_name yice.dtstack.cn; location / {
proxy_pass http://localhost:4000/;
}
}

常见的 node 框架都支持托管静态文件目录:

// express
app.use(express.static(path.join(__dirname, 'web/dist'))); // NestJS
import { ServeStaticModule } from '@nestjs/serve-static'; ServeStaticModule.forRoot({
serveRoot: '/',
rootPath: join(__dirname, '.', 'web/dist'),
}), // egg
{
static: {
dir: path.join(appInfo.baseDir, 'web/dist'),
}
}

代码基本大同小异,从 nginx 配置和项目结构我们也能看出这还是属于一个 node 项目的结构,前端项目的 nginx 配置一般为:

server {
listen 80;
server_name yice.dtstack.cn;
root /opt/dtstack/yice-performance/web/dist/ location /api {
proxy_pass http://localhost:4000/;
}
location / {
try_files $uri $uri/ /index.html;
}
}

二、Turborepo

Turborepo 是用于 JavaScript 和 TypeScript 代码库的高性能构建系统。

借助 Turborepo 我们可以并行的运行和构建代码,当我们使用传统的 yarn workspace 管理代码时,我们的一般会执行以下命令:

# server
yarn
yarn dev # web
cd web
yarn
yarn dev

此时,本地开发不仅需要同时开启两个终端,而且还得分别注意两个终端所在的路径,lint、build、test 等命令皆如此。

想要更快的完成以上工作,可以使用 turbo run lint test build

新项目往往更容易使用 Turborepo,使用 create-turbo 创建即可,参考 官方文档。历史项目想要使用 Turborepo 时需要注意一下项目结构:

yice-performance
├─package.json
├─pnpm-lock.yaml
├─pnpm-workspace.yaml
├─turbo.json
├─apps
| ├─server
| └─web

将历史项目的代码整合到单个文件夹后移入 apps ,注意需要修改相对路径等代码,比如 tsconfig.json 文件中关于 @/* 等路径别名的写法,以及 import 依赖的路径,将公共依赖包统一提到根目录的 package.json 中。

在根目录添加 turbo.json 文件,这里是 dev 和 build 命令为例:

{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": [".apps/server/dist/**", "!.apps/server/cache/**"]
},
"dev": {
"persistent": true,
"cache": false
}
}
}

然后在 apps 下的产品中依次添加两种命令:

{
"scripts": {
"dev": "NODE_ENV=development nest start --watch",
"build": "NODE_ENV=production nest build"
}
}
{
"scripts": {
"dev": "NODE_ENV=development vite --port 7001",
"build": "tsc && NODE_ENV=production vite build"
}
}

这样就可以通过 pnpm dev 一条命令同时启多个服务了,pnpm build 可以快速完成多个项目的打包工作。

三、docker

以易测依赖的 Puppeteer 为例,对于设备环境的要求就比较多,参考 Puppeteer 故障排除;再比如易测 v2.x 版本新增的数据周报功能使用到 node 端的 echarts,最终依赖 node-canvas,对设备环境的要求也很苛刻。

同时,部署命令写的脚本中还需要考虑不同环境的差异,比如 Windows 中的情况。

docker 在这里的作用就是抹平不同设备间的环境差异,减少补充安装依赖包的痛苦,amd64、arm64 等环境差异导致的依赖包安装失败问题,我们可以构建适用于不同平台的 docker 镜像包(以下以 linux/amd64 为例,也就是常说的 x86_64 架构)。

Dockerfile

本地编写 Dockerfile 文件,然后执行 docker build命令构建镜像。在构建镜像之前,需要注意下 Dockerfile 构建镜像时有一个 的概念,对于构建时间会有较大影响。

Docker 镜像是由多个只读的层叠加而成的,每一层都是基于前一层构建。Dockerfile 文件中的每条指令都会创建一个新的层,并对镜像进行修改,执行 docker build 命令时会使用缓存,当前面的层不发生变化时,我们再次构建镜像时就会更快速。但因为每一层都是基于前一层构建,所以我们应该把变化可能性小的操作放到前面,后续改动只会构建变化的内容,而无需构建整个镜像,这能大大加快镜像的构建速度。

比如下方 Dockerfile.server 中的 nodejs 的安装,如果放在 COPY . . 之后,则每次构建都需要安装一次 nodejs,我们利用缓存可以大大减少构建时间。

FROM ubuntu:22.04

# 设置时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone \
&& apt-get update -y && apt-get install -y tzdata # puppeteer 和 node-canvas 对系统依赖的要求
# https://github.com/Automattic/node-canvas?tab=readme-ov-file#compiling
# https://github.com/puppeteer/puppeteer/blob/puppeteer-v19.6.3/docs/troubleshooting.md#chrome-headless-doesnt-launch-on-unix
RUN apt-get update -y \
&& apt-get install -y build-essential libcairo2-dev libpango1.0-dev libnss3 libatk1.0-0 \
&& apt-get install -y ca-certificates fonts-liberation libasound2 libatk-bridge2.0-0 \
&& apt-get install -y libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 \
&& apt-get install -y libgbm1 libgcc1 libglib2.0-0 libgtk-3-0 libnspr4 libpangocairo-1.0-0 \
&& apt-get install -y libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 \
&& apt-get install -y libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 \
&& apt-get install -y libxss1 libxtst6 # 处理 chromium 等依赖问题
# https://github.com/puppeteer/puppeteer/blob/puppeteer-v19.6.3/docker/Dockerfile
RUN apt-get update -y \
&& apt-get install -y wget gnupg \
&& wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | gpg --dearmor -o /usr/share/keyrings/googlechrome-linux-keyring.gpg \
&& sh -c 'echo "deb [arch=amd64 signed-by=/usr/share/keyrings/googlechrome-linux-keyring.gpg] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
&& apt-get update -y \
&& apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-khmeros fonts-kacst fonts-freefont-ttf libxss1 --no-install-recommends \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get remove -y wget gnupg
# deb [arch=amd6 配置可能会在 /etc/apt/sources.list.d/google.list 和 /etc/apt/sources.list.d/google-chrome.list 中重复,再尝试一次
RUN rm -rf /etc/apt/sources.list.d/google-chrome.list \
&& apt-get update -y \
&& apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-khmeros fonts-kacst fonts-freefont-ttf libxss1 --no-install-recommends # 安装 nodejs
RUN apt-get update -y && apt-get install -y curl \
&& curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
&& apt-get remove -y curl \
&& apt-get install -y nodejs \
&& npm config set registry https://registry.npmmirror.com/ \
&& npm install pnpm@6.35.1 -g # 设置工作目录
WORKDIR /yice-performance # 拷贝代码安装依赖
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json ./
COPY apps/server/package.json ./apps/server/
COPY apps/web/package.json ./apps/web/
RUN pnpm install # 复制项目文件
COPY apps .env ./
# 减少 node_modules 的磁盘占用
RUN pnpm build \
&& find . -name "node_modules" -type d -prune -exec rm -rf '{}' + \
&& pnpm install --production # 暴露端口
EXPOSE 4000 # 定义环境变量
ENV NODE_ENV=production
# Dockerfile 中需指定 chromium 路径
ENV PUPPETEER_EXECUTABLE_PATH='google-chrome-stable' VOLUME [ "/yice-performance/apps/server/yice-report" ] # 启动应用程序
CMD ["node", "apps/server/dist/main.js"]
ARG BASE_IMAGE=mysql:5.7
FROM ${BASE_IMAGE} # 当容器启动时,会自动执行 /docker-entrypoint-initdb.d/ 下的所有 .sql 文件
COPY ./mysql/demo-data.sql /docker-entrypoint-initdb.d/
# 附加的 mysql 配置
COPY ./mysql/my_custom.cnf /etc/mysql/conf.d/ # 设置 MySQL root 用户的密码
ENV MYSQL_ROOT_PASSWORD=123456
ENV MYSQL_DATABASE=yice-performance # 设置时区
RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime # 暴露端口
EXPOSE 3306

根据 Dockerfile 文件本地构建镜像,构建完成后在 Docker Desktop 中就可以看到刚刚构建的镜像。我们新建一个脚本文件来统一管理命令,并在 package.json 中添加 build:docker命令:

#!/bin/sh

cd docker

# amd64
docker buildx build --platform linux/amd64 -f Dockerfile.mysql -t liuxy0551/yice-mysql .
docker buildx build --platform linux/amd64 -f Dockerfile.server -t liuxy0551/yice-server ../

此时执行 pnpm build:docker 即可打包镜像。

多平台打包镜像

由于我们目前使用的 Mac M 系列芯片较多,这是 arm64 v8 平台的,但往往我们打包后的镜像是在 x86 的机器上使用,比如 Centos、Ubuntu 等服务器系统,这就要求我们应该兼容 x86 平台。

使用 docker inspect 的命令可以查看镜像架构,如下:

docker pull alpine
docker inspect alpine | grep Architecture

修改刚刚写的 Dockerfile 文件,支持通过 docker build 命令的 build argument 传递参数,这在明确不同平台使用的基础镜像时比较有用。有些常用的基础镜像是支持多平台,只需要添加 --platform linux/amd64, linux/arm64 即可,docker buildx 会自动处理一切,yice-mysql 支持了 arm64 v8,其他内容可以自行研究。

镜像发布

这里使用的是阿里云容器镜像服务:https://cr.console.aliyun.com/

 docker login --username=your_username -p your_password registry.cn-hangzhou.aliyuncs.com
docker tag liuxy0551/yice-mysql registry.cn-hangzhou.aliyuncs.com/liuxy0551/yice-mysql:latest
docker tag liuxy0551/yice-server registry.cn-hangzhou.aliyuncs.com/liuxy0551/yice-server:latest docker push registry.cn-hangzhou.aliyuncs.com/liuxy0551/yice-mysql:latest
docker push registry.cn-hangzhou.aliyuncs.com/liuxy0551/yice-server:latest

docker run

为了保证 yice-server 可以访问到 yice-mysql两个容器需要使用同一个网络

docker network create yice-network
docker run -p 3306:3306 -d --name yice-mysql --network=yice-network -v /opt/dtstack/yice-performance/yice-mysql/conf:/etc/mysql/conf.d -v /opt/dtstack/yice-performance/yice-mysql/log:/var/log/mysql -v /opt/dtstack/yice-performance/yice-mysql/data:/var/lib/mysql registry.cn-hangzhou.aliyuncs.com/liuxy0551/yice-mysql:latest
docker run -p 4000:4000 -d --name yice-server --network=yice-network -v /opt/dtstack/yice-performance/yice-report:/yice-performance/apps/server/yice-report registry.cn-hangzhou.aliyuncs.com/liuxy0551/yice-server:latest
  • -p 表示端口映射,-p 宿主机 port:容器 port,这里暴漏端口是为了外部可以通过 GUI 工具查看数据
  • -d 表示后台运行并返回容器 id
  • --name 表示给容器指定的名称
  • -v /opt/dtstack/yice-performance/yice-mysql:/etc/mysql/conf.d 等挂载路径表示将容器中的配置项、数据、日志都挂载到主机的 /opt/dtstack/yice-performance/yice-mysql
  • -v /opt/dtstack/yice-performance/yice-report:/yice-performance/apps/server/yice-report 表示将容器中的检测报告挂载到宿主机
  • 挂载的目的是为了在删除容器时数据不丢失,且尽量保持容器存储层不发生写操作。

执行 docker run 命令生成容器并运行,访问 http://localhost:4000 即可看到页面了。

docker-compose

docker-compose 是 Docker 官方提供的一个工具,用于管理多个 Docker 容器的应用程序,使用 docker-compose 可以协同多个容器运行。

新增 docker-compose.yml 文件,在这个文件里定义应用程序所需的服务和容器,包括镜像、环境变量、端口映射、挂载目录等信息。

version: '3'

services:
mysql-service:
container_name: yice-mysql
image: registry.cn-hangzhou.aliyuncs.com/liuxy0551/yice-server:latest
ports:
- '3306:3306'
restart: always
networks:
- yice-network server-service:
container_name: yice-server
image: registry.cn-hangzhou.aliyuncs.com/liuxy0551/yice-mysql:latest
ports:
- '4000:4000'
restart: always
depends_on:
- mysql-service
networks:
- yice-network networks:
yice-network:
driver: bridge
docker-compose -f docker/docker-compose.yml -p yice-performance up -d
命令 作用
docker-compose up 启动程序,-d 后台运行
docker-compose down 停止并移除容器、卷、镜像等
docker-compose ps 列出正在运行的容器
docker-compose logs 查看日志
docker-compose stop 停止服务
docker-compose start 启动服务
docker-compose restart 重启服务

四、常见问题

yice-server 无法启动

可能是 docker 版本较低,建议升级到 docker v24 及以上,升级前应当备份。

yum install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin



node[1]: ../src/node_platform.cc:61:std::unique_ptr<long unsigned int> node::WorkerThreadsTaskRunner::DelayedTaskScheduler::Start(): Assertion `(0) == (uv_thread_create(t.get(), start_thread, this))' failed.
1: 0xb090e0 node::Abort() [node]
2: 0xb0915e [node]
3: 0xb7512e [node]
4: 0xb751f6 node::NodePlatform::NodePlatform(int, v8::TracingController*) [node]
5: 0xacbf74 node::InitializeOncePerProcess(int, char**, node::InitializationSettingsFlags, node::ProcessFlags::Flags) [node]
6: 0xaccb59 node::Start(int, char**) [node]
7: 0x7f2ffac64d90 [/lib/x86_64-linux-gnu/libc.so.6]
8: 0x7f2ffac64e40 __libc_start_main [/lib/x86_64-linux-gnu/libc.so.6]
9: 0xa408ec [node]
gcc 版本过低

主机部署时建议使用 Ubuntu。

主机模式部署时 CentOS7 上启动服务时报错:Error: /lib64/libstdc++.so.6: version 'CXXABI_1.3.9' not found,这是因为 CentOS7gcc 版本过低,需要升级到 gcc-4.8.5 以上,执行下方命令可以看到没有 CXXABI_1.3.9

strings /lib64/libstdc++.so.6 | grep CXXABI

相关链接:

https://github.com/Automattic/node-canvas/issues/1796

https://gist.github.com/nchaigne/ad06bc867f911a3c0d32939f1e930a11

https://ftp.gnu.org/gnu/gcc/

cd /etc/gcc
wget https://ftp.gnu.org/gnu/gcc/gcc-9.5.0/gcc-9.5.0.tar.gz
tar xzvf gcc-9.5.0.tar.gz
mkdir obj.gcc-9.5.0
cd gcc-9.5.0
./contrib/download_prerequisites
cd ../obj.gcc-9.5.0
../gcc-9.5.0/configure --disable-multilib --enable-languages=c,c++
make -j $(nproc)
make install

最后

欢迎关注【袋鼠云数栈UED团队】~

袋鼠云数栈 UED 团队持续为广大开发者分享技术成果,相继参与开源了欢迎 star

搭建自动化 Web 页面性能检测系统 —— 部署篇的更多相关文章

  1. Grunt搭建自动化web前端开发环境--完整流程

    Grunt搭建自动化web前端开发环境-完整流程 jQuery在使用grunt,bootstrap在使用grunt,百度UEditor在使用grunt,你没有理由不学.不用! 1. 前言 各位web前 ...

  2. 隔壁老主精讲web页面性能优化。

    首先说一下为什么要进行web页面性能优化,在同样的网络环境下,两个同样能满足你的需求的网站,一个“Biu”的一下就加载出来了,一个卡--卡--卡--卡--卡--才出来,你会选择哪个?研究表明:用户最满 ...

  3. Web页面性能优化(YSlow)

    YSlow(解析为Why Slow)是雅虎基于网站优化规则推出的工具,帮助你分析并优化网站性能.旧版Yslow 有13条规则,新版Yslow有23项规则,YSlow会根据这些规则分析你的网站,并给出评 ...

  4. Nginx + FastCGI 程序(C/C++) 搭建高性能web service的Demo及部署发布

       FastCGI编程包括四部分:初始化编码.接收请求循环.响应内容.响应结束循环. FCGX_Request request; FCGX_Init(); ); FCGX_InitRequest(& ...

  5. 【入门篇】Nginx + FastCGI 程序(C/C++) 搭建高性能web service的Demo及部署发布

    http://blog.csdn.net/allenlinrui/article/details/19419721 1.介绍     Nginx - 高性能web server,这个不用多说了,大家都 ...

  6. 好用的前端页面性能检测工具—sitespeed.io

    引言 最近在做HTTP2技术相关调研,想确认一下HTTP2在什么情境下性能会比HTTP1.x有显著提升,当我把http2的本地环境(nginx+PHP)部署完成后进行相关测试时,我遇到了以下问题: ( ...

  7. web页面性能优化

    web前端页面性能优化 网站的划分一般为二:前端和后台.我们可以理解成后台是用来实现网站的功能的,比如:实现用户注册,用户能够为文章发表评论等等.而前端呢? 其实应该是属于功能的表现.并且影响用户访问 ...

  8. base64:URL背景图片与web页面性能优化

    一.base64百科 Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一,可用于在HTTP环境下传递较长的标识信息. 某人: 唉,我彻底废柴了,为何上面明明是中文,洒家却看不懂嘞,为什 ...

  9. 小tip: base64:URL背景图片与web页面性能优化——张鑫旭

    一.base64百科 Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一,可用于在HTTP环境下传递较长的标识信息. 某人: 唉,我彻底废柴了,为何上面明明是中文,洒家却看不懂嘞,为什 ...

  10. 小tip: base64:URL背景图片与web页面性能优化

    转自:http://www.zhangxinxu.com/wordpress/?p=2341 一.base64百科 Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一,可用于在HTTP ...

随机推荐

  1. iOS符号表手工还原

    1.通过Xcode的Device工具导出app.crash文件 2.将.crash 和 .dSYM符号 app放在同一个目录中 3.寻找symbolicatecrash,将symbolicatecra ...

  2. 穿透 wsl 和 ssh, 新版本 neovim 跨设备任意复制,copy anywhere!

    获得更好的阅读体验,欢迎查看原文:穿透 wsl 和 ssh, 新版本 neovim 跨设备任意复制,copy anywhere! 1. 创作动机 最近一个星期,我入坑了 neovim, 然后开始配置各 ...

  3. 地址栏hash模式以?问号分割也可以分割的

    可以看到href里面hash没有? 但是还是以?分割了 就很不明白 但是我就indexof判断有没有? 再进行下一步逻辑 这里记录一下坑

  4. 使用线程池实现为多个客户端提供Echo服务

    import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner; im ...

  5. 解读surging 的内存过高的原因

    前言 对于.NET开发人员来讲,一个程序占用内存过高,是极其糟糕,是一款不合格的程序软件,.NET开发人员也不会去使用服务器垃圾收集器(ServerGarbageCollection),而是选用工作站 ...

  6. @Transactional事务注解及请求接口的定义先后执行顺序设计

    @Transactional事务注解及请求接口的定义先后执行顺序设计1.事务内查询,可能存在事务没有提交,导致查询数据查不出来. 2.或者可能跟请求参数作为查询条件,在某个条件下,请求参数发生变化,也 ...

  7. 调用支付JSAPI缺少参数:total_fee解决方法

    1.https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7 调用支付JSAPI缺少参数:total_fee 1.请检查预支付会话标识p ...

  8. CentOS7学习笔记(七) 磁盘管理

    查看硬盘分区信息 在Linux中使用lsblk命令查看硬盘以及分区信息 [root@192 ~]# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda ...

  9. MySQL Docker搭建挂载并启用远程连接

    1.拉取镜像 后面可以指定版本号,这里使用8.0 docker pull docker.io/mysql:8.0 2.查看mysql镜像 docker images 3.启动docker并挂载 doc ...

  10. Linux下挂载NTFS格式的U盘

    NTFS是Windows下的格式,在Linux下是识别不了的,要想在Linux上挂载NTFS格式的U盘需要安装软件以提供支持.软件名为ntfs-3g. 1.下载安装包 https://tuxera.c ...