参考方案:设计一个全站请求耗时统计工具
一、目标与价值
✅ 目标
构建一个覆盖全站请求的性能监控系统,用于收集、分析和展示每个 HTTP 请求的耗时情况,包括:
- 用户端视角(前端)
- 网络传输耗时(CDN/中间层)
- 后端服务处理时间(接口层)
- 数据库/第三方服务响应时间
📈 价值
- 提升用户体验:识别慢请求,优化页面加载速度
- 故障排查:快速定位性能瓶颈或异常接口
- 性能趋势分析:监控不同时间段的请求性能变化
- 支持 A/B 测试:对比不同版本接口性能差异
- 保障 SLA:确保接口响应时间符合服务等级协议
二、整体架构设计
[用户浏览器] → [CDN / Nginx] → [网关 / 接入层] → [后端服务] → [数据库 / 第三方服务]
↓ ↓ ↓ ↓
[前端埋点] [日志采集 & 中间层追踪] [链路追踪] [DB Profiling]
↓ ↓ ↓ ↓
└──────→ [统一上报中心] ←────────┘
↓
[数据存储(ES / MySQL)]
↓
[可视化看板 / 报警系统]
三、核心功能模块详解
1. 前端埋点(Client-Side Monitoring)
功能点:
- 页面首次加载时间(FP、FCP、LCP)
- 每个 API 请求的发起时间、响应时间
- 资源加载耗时(JS/CSS/图片)
- 网络环境(是否使用 CDN、运营商信息)
实现方式:
- 使用
Performance API
(如performance.getEntries()
) - 封装 Axios 插件记录接口耗时
- 上报格式示例:
{
"url": "/api/user/list",
"method": "GET",
"startTime": "2025-05-01T12:00:00Z",
"endTime": "2025-05-01T12:00:00.350Z",
"duration": 350,
"userId": "user_123",
"env": "production",
"device": "Chrome 117, Windows 10"
}
定义请求耗时数据的接口:
// 定义请求耗时数据的接口
interface RequestPerformanceData {
url: string; // 请求的 URL
method: string; // 请求方法(GET、POST 等)
startTime: string; // 请求开始时间(ISO 8601 格式)
endTime: string; // 请求结束时间(ISO 8601 格式)
duration: number; // 请求耗时(毫秒)
userId: string; // 用户 ID
env: string; // 环境(如 production、development)
device: string; // 设备信息(浏览器和操作系统)
}
// 示例数据
const performanceData: RequestPerformanceData = {
url: "/api/user/list",
method: "GET",
startTime: "2025-05-01T12:00:00Z",
endTime: "2025-05-01T12:00:00.350Z",
duration: 350,
userId: "user_123",
env: "production",
device: "Chrome 117, Windows 10"
};
// 解析并注释请求耗时数据
function parseAndLogPerformanceData(data: RequestPerformanceData): void {
// 解析请求 URL
console.log(`1. 请求 URL: ${data.url}`);
// 解析请求方法
console.log(`2. 请求方法: ${data.method}`);
// 解析请求开始时间
console.log(`3. 请求开始时间: ${data.startTime}`);
// 解析请求结束时间
console.log(`4. 请求结束时间: ${data.endTime}`);
// 计算并解析请求耗时
console.log(`5. 请求耗时: ${data.duration} 毫秒`);
// 解析用户 ID
console.log(`6. 用户 ID: ${data.userId}`);
// 解析环境
console.log(`7. 环境: ${data.env}`);
// 解析设备信息
console.log(`8. 设备信息: ${data.device}`);
}
// 调用函数并传入示例数据
parseAndLogPerformanceData(performanceData);
代码说明:
-
接口定义:
RequestPerformanceData
接口定义了请求耗时数据的结构,包括 URL、方法、时间戳、耗时、用户 ID、环境和设备信息。
-
示例数据:
performanceData
是一个符合RequestPerformanceData
接口的对象,包含示例数据。
-
解析函数:
parseAndLogPerformanceData
函数接收RequestPerformanceData
类型的参数,逐行解析并打印数据。- 每行代码都包含详细的注释,说明解析的字段及其含义。
-
输出:
- 调用
parseAndLogPerformanceData(performanceData)
后,控制台会输出以下内容:1. 请求 URL: /api/user/list 2. 请求方法: GET 3. 请求开始时间: 2025-05-01T12:00:00Z 4. 请求结束时间: 2025-05-01T12:00:00.350Z 5. 请求耗时: 350 毫秒 6. 用户 ID: user_123 7. 环境: production 8. 设备信息: Chrome 117, Windows 10
- 调用
扩展建议:
- 如果需要进一步处理这些数据(如存储到数据库或发送到监控系统),可以在
parseAndLogPerformanceData
函数中添加相关逻辑。 - 对于时间戳(
startTime
和endTime
),可以使用Date
对象进行更复杂的操作(如计算时间差、格式化等)。 - 如果数据量较大,可以考虑使用日志库(如
winston
或pino
)替代console.log
。
埋点时机:
- 页面加载完成
- 每次接口调用结束(成功/失败)
- 页面关闭前(beforeunload)
2. 网络层埋点(CDN/Nginx/反向代理)
功能点:
- 客户端到服务器之间的网络延迟
- CDN 缓存命中率
- SSL 握手耗时
- 请求排队等待时间(如限流队列)
实现方式:
- 配置 Nginx 日志输出字段,例如:
log_format timing '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" $request_time $upstream_response_time';
- 字段说明:
$request_time
: 从客户端建立连接到请求完成的总时间$upstream_response_time
: 后端服务响应时间
示例日志:
192.168.1.1 - - [01/May/2024:12:00:00 +0800] "GET /api/user/list HTTP/1.1" 200 1234 "-" "Mozilla/5.0" 0.350 0.280
3. 后端服务埋点(Server-Side Tracing)
功能点:
- 接口处理耗时(排除网络因素)
- 方法执行时间(如 Controller 层、Service 层、DAO 层)
- 异常堆栈记录
- 请求上下文传递(Trace ID、User ID)
实现方式:
- 使用 OpenTelemetry / Zipkin / Jaeger 等分布式追踪框架
- 使用拦截器封装耗时统计逻辑(Spring AOP / Node.js Middleware)
示例结构(Node.js Express):
// 定义一个 Express 中间件函数,用于记录请求的耗时和相关信息
app.use((req, res, next) => {
// 记录请求开始时间(使用 Date.now() 获取当前时间戳)
const start = Date.now();
// 生成一个唯一的请求标识符(traceId),用于跟踪一次完整的请求
// 假设 uuidv4() 是一个生成 UUID v4 的函数(需要提前导入或定义)
const traceId = uuidv4();
// 将 traceId 附加到请求对象上,以便后续中间件或路由处理程序使用
req.traceId = traceId;
// 监听响应对象的 'finish' 事件,该事件在响应结束时触发
res.on('finish', () => {
// 计算请求耗时:响应结束时间 - 请求开始时间
const duration = Date.now() - start;
// 调用日志记录函数,记录请求的详细信息
logToMonitor({
traceId, // 唯一标识符,用于跟踪请求
url: req.originalUrl, // 请求的原始 URL(包含查询参数)
method: req.method, // 请求方法(GET、POST、PUT、DELETE 等)
status: res.statusCode, // 响应状态码(如 200、404、500 等)
duration, // 请求耗时(毫秒)
userId: req.user?.id || 'anonymous', // 用户 ID(如果已认证),否则为 'anonymous'
});
});
// 调用 next() 将控制权传递给下一个中间件或路由处理程序
next();
});
代码说明:
-
中间件定义:
app.use((req, res, next) => { ... })
是 Express 的中间件定义方式,用于处理所有传入的请求。
-
请求开始时间:
const start = Date.now()
记录请求的开始时间,用于后续计算请求耗时。
-
唯一请求标识符:
const traceId = uuidv4()
生成一个唯一的 UUID v4 标识符,用于跟踪一次完整的请求生命周期。
-
附加 traceId 到请求对象:
req.traceId = traceId
将生成的traceId
附加到请求对象req
上,后续中间件或路由可以通过req.traceId
访问它。
-
监听响应结束事件:
res.on('finish', () => { ... })
监听响应对象的finish
事件,该事件在响应发送给客户端后触发。
-
计算请求耗时:
const duration = Date.now() - start
计算从请求开始到响应结束的总耗时(毫秒)。
-
记录请求信息:
logToMonitor({ ... })
调用日志记录函数,记录请求的详细信息,包括:traceId
:唯一标识符。url
:请求的原始 URL。method
:请求方法。status
:响应状态码。duration
:请求耗时。userId
:用户 ID(如果已认证),否则为'anonymous'
。
-
传递控制权:
next()
调用next()
将控制权传递给下一个中间件或路由处理程序。
注意事项:
- 需要确保
uuidv4()
函数已正确定义或导入(例如使用uuid
库的v4
方法)。 logToMonitor
是一个自定义函数,需要根据实际需求实现(如写入数据库、发送到监控系统等)。- 如果用户认证信息存储在
req.user
中,需要确保认证中间件已正确设置req.user
。
4. 数据库层埋点(Database Profiling)
功能点:
- 单条 SQL 执行时间
- 是否命中索引
- 是否进行文件排序、临时表等操作
- 连接池状态(是否有等待)
实现方式:
- 开启慢查询日志(MySQL 的
slow query log
) - 使用 ORM 工具插件记录 SQL 耗时(如 TypeORM、Sequelize、MyBatis Interceptor)
- 使用 APM 工具(如 SkyWalking、Pinpoint)自动采集
示例日志:
# Time: 2024-05-01T12:00:00.100Z
# Query_time: 0.250
SELECT * FROM users WHERE name LIKE '%john%';
5. 第三方服务埋点(External Service Monitoring)
功能点:
- 外部 API 调用耗时(如支付、短信、地图服务)
- 服务可用性(是否超时、失败次数)
- 并发请求数、失败重试机制
实现方式:
- 使用 SDK 包装外部调用(如 axios.interceptors)
- 记录每次调用的开始时间和结束时间
- 添加唯一 Trace ID 以便关联整条链路
示例代码(axios):
使用 Axios 拦截器记录第三方服务请求耗时和状态的实现:
// 创建一个自定义的 Axios 实例,用于发送 HTTP 请求
const apiClient = axios.create();
// 添加请求拦截器
apiClient.interceptors.request.use(config => {
// 在请求配置中添加 metadata 对象,记录请求的开始时间
config.metadata = { startTime: new Date() };
// 返回修改后的请求配置
return config;
});
// 添加响应拦截器
apiClient.interceptors.response.use(
// 成功响应的处理函数
response => {
// 计算请求耗时:当前时间 - 请求开始时间
const duration = new Date() - response.config.metadata.startTime;
// 调用日志记录函数,记录第三方服务调用的详细信息
logThirdParty({
service: 'payment', // 服务名称(例如支付服务)
endpoint: response.config.url, // 请求的端点 URL
duration, // 请求耗时(毫秒)
success: true, // 请求是否成功
});
// 返回响应对象,继续后续处理
return response;
},
// 错误响应的处理函数
error => {
// 计算请求耗时:当前时间 - 请求开始时间
const duration = new Date() - error.config.metadata.startTime;
// 调用日志记录函数,记录第三方服务调用的错误信息
logThirdParty({
service: 'sms', // 服务名称(例如短信服务)
endpoint: error.config.url, // 请求的端点 URL
duration, // 请求耗时(毫秒)
success: false, // 请求是否成功
error: error.message, // 错误信息
});
// 拒绝 Promise,将错误传递给调用方
return Promise.reject(error);
}
);
代码说明:
-
创建 Axios 实例:
const apiClient = axios.create()
创建一个自定义的 Axios 实例,用于发送 HTTP 请求。
-
请求拦截器:
apiClient.interceptors.request.use(config => { ... })
添加一个请求拦截器,用于在请求发送前修改请求配置。config.metadata = { startTime: new Date() }
在请求配置中添加metadata
对象,记录请求的开始时间。
-
响应拦截器:
apiClient.interceptors.response.use(response => { ... }, error => { ... })
添加一个响应拦截器,用于处理成功和失败的响应。
-
成功响应处理:
const duration = new Date() - response.config.metadata.startTime
计算请求耗时。logThirdParty({ ... })
调用日志记录函数,记录第三方服务调用的详细信息(服务名称、端点、耗时、成功状态)。return response
返回响应对象,继续后续处理。
-
错误响应处理:
const duration = new Date() - error.config.metadata.startTime
计算请求耗时。logThirdParty({ ... })
调用日志记录函数,记录第三方服务调用的错误信息(服务名称、端点、耗时、成功状态、错误信息)。return Promise.reject(error)
拒绝 Promise,将错误传递给调用方。
注意事项:
logThirdParty
是一个自定义函数,需要根据实际需求实现(如写入数据库、发送到监控系统等)。- 示例中
service
字段在成功和失败时分别设置为'payment'
和'sms'
,实际使用时可以根据请求的 URL 或配置动态设置。 - 确保
axios
已正确导入(如import axios from 'axios'
)。
四、数据聚合与分析平台
1. 统一上报中心
- 接收来自各层的日志数据(前端、Nginx、后端、数据库、第三方)
- 格式标准化(JSON 统一字段)
- 支持批量上报(提升性能)
2. 数据存储
存储类型 | 用途 |
---|---|
Elasticsearch | 全文搜索、实时分析、日志聚合 |
MySQL / PostgreSQL | 结构化数据报表、持久化存储 |
Redis | 缓存最近请求、热数据统计 |
Kafka / RabbitMQ | 异步日志消费、削峰填谷 |
3. 可视化分析
工具 | 用途 |
---|---|
Kibana | ELK 可视化,查看请求分布、趋势图 |
Grafana | Prometheus + Metrics 实时监控 |
自研仪表盘 | 展示关键指标(如 P95 耗时、错误率、Top 慢接口) |
五、典型业务场景详解
场景一:页面加载缓慢问题排查
问题描述:
用户反馈首页加载慢。
分析步骤:
- 查看前端埋点数据:LCP 时间为 5s
- 查看 Nginx 日志:请求到达服务器时间为 0.2s
- 查看后端日志:某接口
/api/home/data
耗时 4.5s - 查看数据库日志:该接口对应 SQL 耗时 4s,未命中索引
- 修复建议:添加索引、优化 SQL 查询
场景二:某个接口频繁超时
问题描述:
接口 /api/order/detail
响应时间不稳定,偶发超时。
分析步骤:
- 查看链路追踪数据:发现部分请求在调用第三方支付服务时卡住
- 查看第三方服务日志:发现某些请求在高峰期出现 502 错误
- 查看重试机制:未配置失败重试,导致用户看到错误
- 修复建议:增加熔断、降级、重试策略
场景三:移动端用户访问慢
问题描述:
iOS 用户反映访问慢。
分析步骤:
- 查看设备维度数据:iPhone 用户平均请求耗时比 PC 高 30%
- 查看网络维度数据:移动网络下 DNS 解析慢
- 查看 CDN 日志:部分区域 CDN 节点未命中
- 修复建议:优化 CDN 配置、预解析域名、启用 HTTP/2
场景四:A/B 测试性能对比
问题描述:
新旧两个版本接口性能差异不明。
分析步骤:
- 通过 Trace ID 关联请求,打上版本标签
- 对比两个版本接口的平均耗时、P95 耗时
- 发现新版接口在并发高时性能下降明显
- 修复建议:优化线程池配置、减少锁竞争
场景五:突发流量压垮服务
问题描述:
促销期间服务不可用。
分析步骤:
- 查看请求量曲线:短时间内 QPS 翻倍
- 查看数据库日志:大量慢查询堆积
- 查看链路追踪:多个接口响应时间暴涨
- 修复建议:限流、缓存、扩容、异步处理
六、报警机制设计
报警规则 | 触发条件 | 通知方式 |
---|---|---|
慢接口报警 | P95 > 1s 或单次请求 > 3s | 邮件、钉钉、企业微信 |
错误率升高 | 错误率 > 5% 持续 5 分钟 | 邮件、Slack |
高负载预警 | CPU > 90% / 内存 > 95% | 短信、电话 |
新增慢 SQL | 出现新的慢查询日志 | 邮件通知 DBA |
权限越权尝试 | 无权限接口调用超过阈值 | 邮件 + 安全告警 |
七、技术选型建议
模块 | 推荐技术 |
---|---|
前端埋点 | Performance API、Axios Interceptor |
后端追踪 | OpenTelemetry、Zipkin、Jaeger |
日志采集 | Filebeat、Logstash |
数据存储 | Elasticsearch、Prometheus、MySQL |
可视化 | Kibana、Grafana、自研 Dashboard |
报警系统 | AlertManager、钉钉机器人、企业微信 Webhook |
八、性能指标定义(SRE/KPI)
指标名称 | 定义 | 建议值 |
---|---|---|
平均请求耗时 | 所有请求的平均响应时间 | < 300ms |
P95 请求耗时 | 95% 的请求响应时间 | < 800ms |
接口成功率 | 成功请求数 / 总请求数 | > 99.9% |
慢请求比例 | 耗时 > 1s 的请求占比 | < 1% |
页面 LCP | Largest Contentful Paint | < 2.5s |
首屏加载时间 | 页面首次可交互时间 | < 3s |
接口错误率 | 5xx 错误占比 | < 0.1% |
✅ 总结
一个完整的全站请求耗时统计工具需要从前端、网络、后端、数据库、第三方等多个层面进行埋点和分析。它不仅是性能优化的基础,更是系统稳定性保障的重要组成部分。
核心要点总结如下:
- 全链路埋点:覆盖用户端、网关、服务层、数据库、第三方服务
- 统一数据格式:便于后续分析和聚合
- 多维分析能力:支持按 URL、用户、设备、地区等维度分析
- 报警机制完善:及时发现性能问题
- 可视化展示:帮助团队快速理解性能现状
📌 附录推荐