分布式微服务系统架构第122集:NestJS是一个用于构建高效、可扩展的服务器端应用程序的开发框架

加群联系作者vx:xiaoda0423

仓库地址:webvueblog.github.io/JavaPlusDoc...

1024bat.cn/

nestjs + typescript技术,ORM使用sequelize,数据库采用mysql。前端采用vue2全家桶,搭配SSR技术

安装

开始之前,你可以使用 [Nest CLI] 创建项目,也可以克隆一个 starter project(两者的结果是一样的)。

若要使用 Nest CLI 构建项目,请运行以下命令。这将创建一个新的项目目录,并使用核心的 Nest 文件和支撑模块填充该目录,从而为项目创建一个传统的基本结构。建议初学者使用 Nest CLI 创建新项目。

ruby 复制代码
$ npm i -g @nestjs/cli
$ nest new project-name

复制

其他安装方式

或者,使用 Git 安装采用 TypeScript 开发的 starter 项目:

shell 复制代码
$ git clone https://github.com/nestjs/typescript-starter.git project
$ cd project
$ npm install
$ npm run start

打开浏览器并导航到 [http://localhost:3000/] 地址。

若要安装基于 JavaScript 的 starter project,请在执行上面的命令时使用 javascript-starter.git

你还可以通过 npm (或 yarn)来安装的核心和支撑文件,从头开始手动创建一个新项目。当然,在这种情况下,你将自己担负起创建项目样板文件的工作。

css 复制代码
$ npm i --save @nestjs/core @nestjs/common rxjs reflect-metadata

这个错误信息说明你的 Node.js 项目在使用 Sequelize 连接 MySQL 数据库时发生了 连接超时(ETIMEDOUT) ,根本原因通常是网络层面的问题。我们可以逐步排查:


✅ 可能原因 & 解决方案:

1. 数据库地址无法访问

  • Sequelize 配置中设置的 host 是不是写错了?是内网 IP、localhost 还是公网域名?

  • 如果是远程数据库(例如云数据库),你当前的机器是否能 ping 通或 telnet 它?

    yaml 复制代码
    ping your-database-host
    telnet your-database-host 3306

2. MySQL 未启动或监听端口错误

  • 确认你的 MySQL 服务是否启动?
  • 监听的是默认端口 3306 吗?

3. 防火墙、网络安全组拦截

  • 服务器防火墙是否放行了 3306 端口?
  • 云服务提供商(如阿里云、腾讯云)需要你配置安全组允许你的 IP 访问 MySQL。

4. 数据库连接配置错误

请检查 Sequelize 的连接参数是否正确(尤其是 host、port、user、password、database):

arduino 复制代码
const sequelize = new Sequelize('database_name', 'username', 'password', {
  host: 'localhost', // 或者云数据库地址
  dialect: 'mysql',
  port: 3306,         // 确保端口对得上
  dialectOptions: {
    connectTimeout: 10000 // 延长连接超时时间,单位是毫秒
  }
});

5. 网络不稳定 / 连接超时设置太短

你可以尝试临时增加 connectTimeout 参数(单位毫秒):

css 复制代码
dialectOptions: {
  connectTimeout: 15000 // 默认 10000,试着调大
}

6. 本地开发网络有问题(如 VPN、代理)

如果你连的是远程数据库,但本地连不上,试着关闭 VPN、代理软件再试。


🔧 你可以先这么试试:

  1. 确认数据库服务是否在运行,并且监听在正确的地址和端口。
  2. 确认你的 Sequelize 配置是否写对。
  3. 在命令行用 telnetmysql -h 命令测试连接。
  4. 适当增加 connectTimeout 看是否是网络抖动。

你的数据库连接报错 ETIMEDOUT,而配置中 host 是 localhost,说明:

  • 你运行 Node.js 项目的这台电脑上没有跑起 MySQL 服务,或者服务挂了。
  • 或者 MySQL 没监听 127.0.0.1:3306(有可能只监听 socket 或者其他 IP)。

✅ 解决步骤建议:

✅ 1. 检查 MySQL 是否在本机启动

打开终端或 CMD:

css 复制代码
mysql -u root -p

如果提示无法连接,就说明 MySQL 没启动,使用以下命令尝试启动(根据你安装方式不同):

  • Windows:

    sql 复制代码
    net start mysql
  • macOS(使用 Homebrew):

    sql 复制代码
    brew services start mysql
  • Linux:

    sql 复制代码
    sudo systemctl start mysql

✅ 2. 确认 MySQL 监听了 127.0.0.1:3306

运行以下命令确认监听端口:

yaml 复制代码
netstat -an | findstr 3306    # Windows
lsof -i:3306                  # mac/Linux

如果没有看到 127.0.0.1:3306localhost:3306,说明 MySQL 配置问题。

你可以修改 my.cnfmy.ini 文件,确认配置中 bind-address = 127.0.0.1,然后重启 MySQL。


✅ 3. 确认数据库 mp_release_platform 存在

用客户端工具(如 Navicat、DBeaver)或者命令行连接:

css 复制代码
mysql -u root -p
SHOW DATABASES;

确认 mp_release_platform 是存在的。


✅ 4. Sequelize 初始化代码增加日志与超时配置

建议你初始化 Sequelize 时加点日志,方便排查连接过程:

yaml 复制代码
const Sequelize = require('sequelize');
const { mysql } = configure;

const sequelize = new Sequelize(
  mysql.database,
  mysql.user,
  mysql.password,
  {
    host: mysql.host,
    port: mysql.port,
    dialect: 'mysql',
    pool: {
      max: mysql.connectionLimit,
      min: 0,
      acquire: 30000,
      idle: 10000,
    },
    logging: console.log, // 打印 SQL 日志,便于调试
    dialectOptions: {
      connectTimeout: 10000, // 默认超时时间延长一点
    }
  }
);

🔧 补充建议

如果还是连不上,你可以试试把 host 从 localhost 改成 127.0.0.1

vbnet 复制代码
host: '127.0.0.1',

有些系统把 localhost 映射成 ::1(IPv6),而 MySQL 默认可能没监听 IPv6。

🔍 你的 MySQL 当前监听状态分析:

ini 复制代码
TCP    0.0.0.0:3306           0.0.0.0:0              LISTENING
TCP    [::]:3306              [::]:0                 LISTENING

这说明你的 MySQL:

  • ✅ 已经成功监听了所有 IPv4 和 IPv6 地址的 3306 端口
  • [::1]:3306 有连接,说明你本机某些程序(也许是 Navicat 或命令行)已经成功连接 MySQL。

✅ 结论:MySQL 是开启的,监听也没问题,但 Sequelize 用的 localhost 可能存在 IPv6 映射问题


🔧 解决建议

✅ 1. Sequelize 配置中将 host: 'localhost' 改为 127.0.0.1

资源文件read-image文件夹

项目组模块,系统管理模块,发布预览,发布体验版,发布任务记录,日志,小程序二维码,用户操作选项卡。

预览 分支, 启动页面,启动参数

发布体验版:分支,版本,自动生成,格式:年月日,今日发布次数

bash 复制代码
# 安装所需包
npm i
arduino 复制代码
# 运行项目
npm run start:dev
bash 复制代码
平台打包为docker镜像
make
# 通过docker服务编排同时生成并启动
docker-compose up
ini 复制代码
# 完整配置
mysql:
    restart: always
    image: mysql:5.6
    volumes:
        - ./data:/var/lib/mysql
    command: [
      '--character-set-server=utf8',
      '--collation-server=utf8_general_ci',
      --default-authentication-plugin=mysql_native_password,
    ]
    environment:
        - MYSQL_ROOT_PASSWORD=root
        - MYSQL_DATABASE=mp_release_platform
        - MYSQL_USER=mp
        - MYSQL_PASSWORD=mp2020
    ports:
        - "3306:3306"
bash 复制代码
# 单独启动mysql
docker-compose up -d mysql
# 查看mysql容器的id
docker ps
# 进入mysql容器
docker exec -it mysql容器id bash
# 连接mysql
mysql -ump -pmp2020
# 删除数据库
DROP DATABASE mp_release_platform
# 创建数据库并执行字符集
CREATE DATABASE mp_release_platform DEFAULT CHARSET utf8 COLLATE utf8_general_ci

npm install或yarn install只会安装dependencies,而不会安装devDependencies列表中的包,需要将安装命令改为npm install --dev或yarn install --production=false

排查k8s分配给docker容器的内存大小是否足够,若k8s分配给docker容器的内存足够大,依然报内存溢出,则可能是系统分配给node的内存不足(tips: 分配给node程序的内存64位系统下约为1.4GB,32位系统下约为0.7GB)

在小程序中,发起请求时会涉及到多个并发流程和事件循环,下面详细描述了从发起请求到结束请求的整个过程,以及超时、挂起和低端手机性能慢时可能会遇到的各种情况。

执行流程:从发起请求到结束请求

这个流程将详细说明从发起请求到请求结束时的整个过程,包括并发请求、超时、挂起、低端手机性能慢的影响,以及如何优化这些问题。

1. 发起请求

1.1 初始化请求

  • 请求函数 request(options) 被调用
  • 用户发起的请求可以是 GETPOSTPUT 等,其中会包含 URL、请求参数、配置项等。

1.2 构建请求头

  • token(如果存在)会加入请求头,用于鉴权。
  • cnrLabelfuLabel 等是请求所需的额外数据(如城市编码、标签等),这些信息从缓存中读取并附加到请求头中。

1.3 记录请求的开始时间

  • 请求发起前,记录请求的起始时间 (使用 Date.now()performance.now())。

1.4 发起请求

  • 使用 Taro.request() 发送 HTTP 请求。此时,网络请求进入 事件循环的宏任务队列
  • 请求的状态会在 successfail 回调中处理。

2. 等待响应

2.1 网络请求等待响应

  • 网络请求处于挂起状态,等待从服务器返回响应。在此期间,事件循环中的其他任务(如页面渲染、异步计算、定时器等)会被继续处理。
  • 如果有多个并发请求,它们会分别进入事件循环,并按照响应顺序依次处理。

2.2 超时设置

  • 如果请求在规定时间内(例如 timeout: 60s)未能得到响应,超时错误 会触发,并由 catch 处理。
  • 低端手机性能慢时,网络请求可能会由于手机的处理能力不足,导致响应慢或超时。

2.3 挂起(网络差)

  • 网络质量差高延迟 的情况下,尽管设备能够正常发起请求,但网络响应时间较长,导致请求处于 挂起状态
  • 此时,界面可能会冻结,用户无法交互,或者显示等待/加载中的状态。

3. 响应处理与数据解析

3.1 收到响应

  • 当服务器返回响应时,进入微任务队列 ,并会调用 success 回调处理。
  • 在响应到达时,记录 响应的结束时间 (通过 Date.now())。

3.2 状态码校验

  • 服务器的响应会根据状态码进行校验。

    • 如果状态码在 200 到 299 之间,视为成功,继续处理响应。
    • 如果响应状态码不在此范围,触发 错误处理 ,通过 Taro.showToast 提示用户错误。
  • 如果 response.code !== 0,表示业务层的错误,显示具体的错误信息并中止当前操作。

3.3 数据解析与处理

  • 解析响应体:

    • 如果请求处理正常,数据会返回给调用方。

3.4 网络请求错误处理

  • 如果在请求过程中发生错误(如超时、网络中断、服务器错误等),会进入 catch 中:

    • 记录错误信息。
    • 根据 超时重试策略(如指数退避),决定是否重试请求。
    • 显示相应的错误提示,提示用户网络不稳定或请求失败。

4. 并发请求与性能考虑

4.1 并发请求

  • 当多个请求同时发起时,它们会进入 事件循环,并按照顺序处理回调。

  • 由于事件循环中每次只能处理一个宏任务(即只有一个请求会被立即处理),可能会导致在低端设备上 请求积压

    • 优化方式:使用 请求并发限制,将大量并发请求拆分成多个批次,避免一次性发送大量请求。
    • 或者将请求合并,减少请求的数量。

4.2 低端手机性能慢

  • 低端手机CPU 和内存限制 会导致:

    • 页面渲染慢,尤其在处理大量请求或复杂数据时。
    • 请求响应慢,尤其在网络环境不佳时。
  • 优化方式

    • 骨架屏:避免白屏,展示一个占位图,提示用户正在加载。
    • 请求延迟加载:不需要立即加载的数据可以延迟加载,避免一开始加载大量数据。
    • 批量请求:避免一次性发起大量请求,优先选择合并请求或者分批请求。

4.3 网络差与请求挂起

  • 在网络状况不佳时,设备可能会一直处于等待响应的状态,直到超时。

  • 优化方式

    • 使用 超时重试机制,在网络条件恢复时进行重试,减少超时失败。
    • 请求取消:在页面销毁时,取消所有挂起的请求,避免无效的请求占用资源。

5. 超时与重试机制

5.1 超时

  • 设置合理的 超时参数,通常在低端设备上可以适当增加超时时间(例如 60s)。
  • 当请求超时时,通过 catch 进行处理,显示用户友好的错误提示。

5.2 重试机制

  • 当请求超时或出现暂时的网络问题时,自动重试机制 可以尝试重新发起请求。常用的重试策略是 指数退避,每次失败后重试时,间隔时间成倍增加:

    • 第一次重试:1s
    • 第二次重试:2s
    • 第三次重试:4s
    • 依此类推,避免请求过于频繁。

执行流程:

  1. 用户发起请求,进入宏任务队列。
  2. 网络请求挂起,等待服务器响应。
  3. 响应返回后,进入微任务队列处理回调。
  4. 状态码校验、数据解析与业务处理。
  5. 发生错误时,通过 catch 进行错误处理,必要时重试。
  6. 并发请求时,按照顺序依次执行,避免一次性处理大量请求。
  7. 在低端设备上,优化请求和渲染逻辑,避免掉帧和性能瓶颈。

关键点:

  • 超时与重试机制,适应低端手机的网络和性能问题。
  • 请求合并与并发限制,减少请求数量,提高性能。

1. 请求发起与事件循环

  • 用户调用 Taro.request() 时会创建一个异步请求。
  • 请求会被推入 事件循环(Event Loop) ,此时执行上下文并不等待网络请求完成,而是继续执行后续代码。
  • 在请求前,我们会先构建请求头(包括 tokenapp-type、 等信息),并将这些数据通过 请求头请求参数 传递。
  • 使用 Taro.request() 发送网络请求。此时,请求进入宏任务队列,会异步执行,并且不会阻塞后续代码的执行。

2.1 响应接收

  • 一旦服务器响应返回,回调函数(例如 .then().catch())会被加入到 微任务队列,并在当前宏任务完成后执行。
  • 请求的响应数据会被进一步处理(如状态码校验、数据解析等)。

2.2 响应超时与挂起

  • 如果网络请求在规定时间内(如 60s)没有收到响应,或因为网络不稳定导致请求挂起,超时错误会被触发并处理。
  • 网络请求会被终止,并且错误信息会通过 .catch() 捕获。

代码示例

javascript 复制代码
catch(e => {
  // 处理网络请求超时或错误
  console.error('请求失败', e);
  Taro.showToast({ title: "网络请求错误,请稍后重试", icon: "none" });
})

2.3 状态码与错误处理

  • 网络请求成功后,首先会校验响应的状态码,确保状态码在 200 到 299 之间。如果状态码不符合,则会弹出错误提示并记录错误日志。

2.5 性能优化:请求并发与合并

  • 在高并发请求场景下,避免一次性发起大量请求,可以通过 限制并发请求数合并请求 来优化性能。

3. 请求超时与重试机制

3.1 设置超时

  • 可以在请求中设置合理的 超时时间,例如 60s。在低端设备上,可以适当增加超时时间以适应网络差或设备性能差的情况。

代码示例

yaml 复制代码
timeout: 60 * 1000 // 设置超时时间为60秒

3.2 重试机制

  • 如果请求由于超时或偶发网络错误失败,可以通过 重试机制 来恢复请求。
  • 使用 指数退避 策略来增加每次失败后的重试间隔。

3.3 请求取消

  • 在低端设备或网络差的情况下,可以通过请求取消来避免无效的请求占用系统资源。
  • 使用 Taro 提供的 abort 方法来取消挂起的请求。

代码示例

ini 复制代码
const requestTask = Taro.request({ url, data });
requestTask.abort(); // 在需要时取消请求

4. 低端设备的性能优化

4.1 UI 渲染优化

  • 在低端设备上,频繁的页面渲染和状态更新会导致 UI 卡顿。优化建议:

    • 使用 骨架屏,提前显示占位图,避免页面空白。
    • 合并多个 setState() 操作,避免多次触发组件重渲染。

4.2 懒加载与分页加载

  • 对于数据量大的场景,可以使用 懒加载分页加载 来减少一次性加载的数据量。
  • 例如,在页面加载时,首先加载核心数据,其他数据通过滚动加载或触底加载来逐步获取。

小程序发起请求 ,请求被挂起或等待超过 100 秒 才返回数据。这类问题通常和网络环境、请求超时、服务器响应时间或客户端性能等多方面因素有关

1. 请求发起过程

1.1 用户发起请求

  • 小程序用户通过调用如 Taro.request() 等 API 发起网络请求。这个请求会进入 事件循环的宏任务队列,等待执行。
  • 请求包含了所需的 URL、参数、请求头等数据。

1.2 请求被发送到服务器

  • 请求会发送到后端服务器,服务器开始处理请求。此时,小程序进入 等待响应 状态。UI 可能显示加载框或骨架屏,提示用户正在加载数据。
  • 如果是一个耗时较长的操作(例如复杂计算、数据库查询等),服务器可能需要一些时间来生成响应。

1.3 小程序继续执行其他任务

  • 在网络请求发起后,小程序的 主线程 会继续执行 其他 JavaScript 代码(例如处理 UI 渲染、事件监听、定时任务等),而不会被阻塞。

2.2 网络不稳定或带宽限制

  • 服务器可能已经处理完请求并生成了响应,但由于 网络环境不佳 ,数据无法及时传输到客户端。这种情况下,数据传输过程中可能会遇到 网络瓶颈,导致请求延迟。

    • 移动网络 下,带宽的波动、信号不稳定、丢包等问题可能导致数据传输速率变慢。
    • 网络丢包或网络中断可能导致客户端无法接收到数据,需要重新连接。

2.3 请求未设置合理的超时时间

  • 如果请求的 超时时间 设置过长或没有设置,可能会导致 请求挂起,即使服务器已经响应,客户端仍然等待数据。

    • 如果没有设置 timeout 参数,或者设置时间过长(如 100s),客户端会一直等待直到请求完成或超时。
    • 客户端等待期间,UI 可能仍处于 等待状态,而实际请求已完成。

2.5 低端设备性能问题

  • 如果请求涉及大量数据处理,或者设备性能不足(例如 低端手机),可能会导致:

    • 请求队列积压:多个请求并发时,设备处理能力有限,导致请求无法及时得到处理。
    • UI 卡顿或掉帧:设备性能差时,UI 更新和事件响应可能变慢,导致数据加载过程中的界面冻结或延迟响应。

3.1 网络请求的超时和重试机制

  • 为了避免请求挂起,应该 设置合理的超时时间。例如,可以将超时时间设置为 30s 或 60s,超时后重新发起请求。
  • 可以实现 超时重试机制 ,当请求失败时,可以设置重试次数,并使用 指数退避策略(每次重试间隔增加)来避免频繁请求。

如果条件允许,可以使用 CDN边缘计算 加速服务器响应速度,并在服务器与客户端之间提供稳定的连接。

3.4 低端设备优化

  • 优化请求和渲染流程 ,避免同时发起大量请求。可以采用 懒加载分页加载 只加载必要的数据,减少页面渲染负担。
  • 在低端设备上,避免复杂的 UI 渲染 ,减少组件的更新次数。例如,可以使用 骨架屏加载动画,避免用户在数据加载时看到空白页面。

3.5 合理设置超时

  • 设置 合适的超时时间,避免请求长时间挂起。通常,可以将超时时间设置为 30s 或 60s。超时后可以通过重试机制重新发起请求。

代码示例

php 复制代码
Taro.request({
  url: 'https://example.com/api',
  timeout: 30000  // 设置超时为 30s
})

1. 请求发起过程

1.1 用户发起请求

  • 用户通过 Taro.request() 或类似的请求 API 发起网络请求。此时,网络请求被加入到 事件循环的宏任务队列,并且执行后续代码。

1.2 请求的超时设置

  • 在请求选项中,通常会设置 timeout 参数(例如 60 秒)。这是用来限制网络请求的最大等待时间。如果请求超时,会触发超时错误,停止请求并返回超时异常。

设置 timeout 参数后,理论上,当请求超过 30 秒(例如),就会自动中止请求并抛出超时异常。

超时机制的触发时机问题

超时机制实际上是 由底层 HTTP 客户端(如 XMLHttpRequestfetch)管理的,在请求过程中,如果响应时间超过了设定的超时限制,应该会抛出超时错误。然而,某些情况下,超时设置未能生效,可能是因为:

  • 网络延迟问题:当网络信号非常差时,尽管设定了超时,底层 HTTP 客户端可能并不会立即中断请求。相反,网络会持续等待数据传输完毕,即使请求超时,也可能会挂起一段时间。
  • 请求挂起:如果服务器响应非常慢或挂起,可能会导致请求持续等待直到超时发生,但在等待过程中并没有主动中止。这是因为超时机制依赖于底层网络层的反馈,而底层可能并没有对请求进行适当的超时控制。
  • 低级网络错误 :某些类型的 网络错误 (例如 DNS 查找失败、网络断开等)可能不会触发 timeout 错误,而是导致请求卡住。此时,浏览器或小程序的底层并未检测到异常状态,因此并没有立即中止请求。
  • 小程序特有问题 :在某些 小程序框架 中,可能存在特定的 bug实现问题,使得超时参数未能正常触发。

服务器延迟或处理时间长

  • 请求可能需要较长时间来处理,尤其是涉及 复杂计算数据库查询外部 API 调用 时。如果服务器响应时间过长,超时机制可能并未中断正在处理的请求,从而导致请求一直挂起直到超时。

客户端不具备主动中断请求的机制

  • 即使设置了超时,某些 请求的底层实现 可能并不具备 主动中断 请求的机制。在请求等待过程中,超时只会触发一个警告,而不是主动终止该请求。

增加超时时间与重试机制

  • 优化超时设置:在超时设置较短的情况下,可以适当增加超时时间,避免短时间内的请求失败。
  • 重试机制:如果请求超时,可以通过重试机制来恢复请求,避免单次请求的失败。

代码示例

php 复制代码
const requestTask = Taro.request({
  url: 'https://example.com/api',
  timeout: 30000
});

requestTask.abort();  // 超时后主动中止请求

优化低端设备上的请求性能

  • 低端设备 下,请求响应慢 可能是由于设备性能差,或者 UI 渲染过于频繁导致的性能瓶颈。可以考虑 分批加载数据使用骨架屏 来优化用户体验。
相关推荐
晚风予星1 分钟前
简记|React+Antd中实现 tooltip、ellipsis、copyable功能组件
前端·react.js
Asthenia04122 分钟前
Feign结构与请求链路详解及面试重点解析
后端
左灯右行的爱情5 分钟前
缓存并发更新的挑战
jvm·数据库·redis·后端·缓存
brzhang8 分钟前
告别『上线裸奔』!一文带你配齐生产级 Web 应用的 10 大核心组件
前端·后端·架构
程序员Bears9 分钟前
深入理解CSS3:Flex/Grid布局、动画与媒体查询实战指南
前端·css3·媒体·visual studio code
shepherd1119 分钟前
Kafka生产环境实战经验深度总结,让你少走弯路
后端·面试·kafka
南客先生15 分钟前
多级缓存架构设计与实践经验
java·面试·多级缓存·缓存架构
David凉宸20 分钟前
凉宸推荐给大家的一些开源项目
前端
袋鱼不重22 分钟前
Cursor 最简易上手体验:谷歌浏览器插件开发3s搞定!
前端·后端·cursor
hyyyyy!23 分钟前
《从分遗产说起:JS 原型与继承详解》
前端·javascript·原型模式