客户端性能指标
客户端性能指标
在客户端性能优化中,理解和正确使用性能指标是至关重要的。本文将深入介绍客户端性能分析中的核心概念,包括延迟(Latency)、百分位数(P50/P95/P99)等关键指标,以及它们在实际应用中的意义和使用方法。
什么是延迟(Latency)
延迟的定义
延迟(Latency)是指从发起请求到收到响应所经过的时间。在客户端性能分析中,延迟通常指的是:
- 网络延迟:数据包从客户端传输到服务器再返回的时间
- 渲染延迟:浏览器渲染页面所需的时间
- 交互延迟:用户操作到界面响应的时间
- API延迟:API请求从发起到收到响应的时间
延迟通常以毫秒(ms)为单位进行测量。
延迟的组成
一个完整的请求延迟通常包含以下几个部分:
总延迟 = DNS解析时间 + TCP连接时间 + TLS握手时间 + 请求发送时间 + 服务器处理时间 + 响应传输时间
1. DNS解析时间
- 将域名解析为IP地址所需的时间
- 通常:10-100ms(首次),0-10ms(缓存后)
2. TCP连接时间
- 建立TCP连接所需的时间(三次握手)
- 通常:10-100ms(取决于网络距离)
3. TLS握手时间
- HTTPS连接建立时的SSL/TLS握手时间
- 通常:50-200ms(首次),10-50ms(会话复用)
4. 请求发送时间
- 将请求数据发送到服务器的时间
- 取决于请求大小和带宽
5. 服务器处理时间
- 服务器处理请求并生成响应的时间
- 这是后端性能优化的重点
6. 响应传输时间
- 响应数据从服务器传输回客户端的时间
- 取决于响应大小和带宽
延迟的重要性
延迟直接影响用户体验:
- 0-100ms:用户感觉即时响应
- 100-300ms:用户能感觉到轻微延迟
- 300-1000ms:用户明显感觉到延迟
- >1000ms:用户可能认为系统有问题
Google的研究表明:
- 页面加载时间每增加100ms,转化率下降约1%
- 移动端延迟增加400ms,搜索量下降0.44%
百分位数(Percentiles)
什么是百分位数
百分位数(Percentile)是统计学中的概念,表示在一组数据中,有百分之多少的数据小于或等于该值。
在性能分析中,百分位数用于描述延迟的分布情况,帮助我们理解:
- 大多数请求的性能如何
- 极端情况下的性能如何
- 性能的稳定性如何
常见的百分位数指标
P50(中位数)
P50,也称为中位数(Median),表示50%的请求延迟小于或等于该值。
特点:
- 不受极值影响,更能反映”典型”性能
- 如果P50 = 100ms,意味着50%的请求在100ms内完成
示例:
假设100个请求的延迟数据(已排序):
[50, 60, 70, 80, 90, 100, 110, 120, 130, 140, ...]
P50 = 第50个请求的延迟 = 100ms
应用场景:
- 了解”典型”用户的体验
- 日常性能监控和报告
- 性能基准测试
P95(95百分位数)
P95表示95%的请求延迟小于或等于该值。
特点:
- 关注大多数用户的体验(95%的用户)
- 对极值敏感,能反映性能波动
- 如果P95 = 500ms,意味着95%的请求在500ms内完成,但5%的请求超过500ms
示例:
假设100个请求的延迟数据(已排序):
[50, 60, 70, ..., 100, 110, ..., 500, 1000, 2000, 5000]
P95 = 第95个请求的延迟 = 500ms
应用场景:
- SLA(服务级别协议)定义:通常将P95作为性能目标
- 性能告警:P95超过阈值时触发告警
- 用户体验评估:确保大多数用户有良好体验
P99(99百分位数)
P99表示99%的请求延迟小于或等于该值。
特点:
- 关注极端情况下的性能
- 对极值非常敏感
- 如果P99 = 2000ms,意味着99%的请求在2000ms内完成,但1%的请求超过2000ms
示例:
假设100个请求的延迟数据(已排序):
[50, 60, 70, ..., 100, 110, ..., 500, 1000, 2000, 5000]
P99 = 第99个请求的延迟 = 2000ms
应用场景:
- 关键业务场景:金融、支付等对性能要求极高的场景
- 性能优化目标:优化最慢的1%请求
- 问题排查:识别性能瓶颈和异常情况
其他百分位数
- P90:90%的请求延迟小于或等于该值
- P99.9:99.9%的请求延迟小于或等于该值(更严格的指标)
- P99.99:99.99%的请求延迟小于或等于该值(极端情况)
百分位数的计算
简单计算方法
对于n个已排序的数据点,第p百分位数的位置为:
位置 = (p / 100) × n
示例:计算P95
有100个请求,延迟数据已排序:
位置 = (95 / 100) × 100 = 95
P95 = 第95个请求的延迟值
实际计算示例
// JavaScript示例:计算百分位数
function calculatePercentile(data, percentile) {
// 排序数据
const sorted = [...data].sort((a, b) => a - b);
// 计算位置
const index = Math.ceil((percentile / 100) * sorted.length) - 1;
return sorted[index];
}
// 示例数据
const latencies = [50, 60, 70, 80, 90, 100, 110, 120, 130, 140,
150, 160, 170, 180, 190, 200, 250, 300, 400, 500,
600, 700, 800, 900, 1000, 1200, 1500, 2000, 3000, 5000];
console.log('P50:', calculatePercentile(latencies, 50)); // 中位数
console.log('P95:', calculatePercentile(latencies, 95)); // 95百分位数
console.log('P99:', calculatePercentile(latencies, 99)); // 99百分位数
为什么需要多个百分位数
不同的百分位数告诉我们不同的信息:
| 指标 | 关注点 | 适用场景 |
|---|---|---|
| 平均值(Mean) | 所有请求的平均性能 | 总体性能评估,但容易被极值影响 |
| P50(中位数) | 典型用户的体验 | 日常监控,性能基准 |
| P95 | 大多数用户的体验 | SLA定义,性能告警 |
| P99 | 极端情况下的性能 | 关键业务,问题排查 |
| 最大值(Max) | 最坏情况 | 问题定位,异常检测 |
示例对比:
假设有100个请求的延迟数据:
平均值 = 200ms
P50 = 100ms
P95 = 500ms
P99 = 2000ms
最大值 = 10000ms
分析:
- 平均值200ms:看起来不错,但可能被极值拉高
- P50 = 100ms:50%的用户体验很好
- P95 = 500ms:95%的用户体验可接受
- P99 = 2000ms:1%的用户体验很差,需要优化
- 最大值10000ms:存在严重异常,需要排查
关键路径与P99的重要性
为什么P99要看路径
关键洞察:慢1% = 可能就是正在付费 / 打Boss / 下单的那一刻
P99的重要性不是绝对的,而是取决于它发生在什么业务路径上。如果这1%的慢请求发生在关键业务路径上,其影响可能是灾难性的。
业务路径优先级分类
在性能监控中,我们需要根据业务重要性对路径进行分类:
P0(最高优先级)- 关键业务路径
特点:
- 直接影响核心业务指标(收入、转化率等)
- 用户正在执行关键操作
- 失败或延迟会直接导致业务损失
典型场景:
- 支付/下单:用户正在完成购买,延迟可能导致订单失败
- 游戏关键操作:打Boss、PK等关键时刻,延迟影响游戏体验
- 登录/注册:用户首次接触产品,延迟影响第一印象
- 核心功能:搜索、提交表单等高频关键操作
P0路径的P99要求:
- 必须严格监控:P99超过阈值立即告警
- 优化优先级最高:即使只有1%的慢请求,也要优先处理
- 目标更严格:P99目标通常比其他路径更严格
P1(高优先级)- 重要业务路径
特点:
- 影响用户体验,但不直接影响核心业务
- 用户正在执行重要操作
- 延迟会影响用户满意度
典型场景:
- 商品详情页加载
- 用户中心操作
- 内容浏览
P2(中优先级)- 一般业务路径
特点:
- 影响用户体验,但影响相对较小
- 可以容忍一定的延迟
典型场景:
- 帮助页面
- 设置页面
- 非核心功能
P3(低优先级)- 辅助功能路径
特点:
- 对业务影响最小
- 可以接受较高的延迟
典型场景:
- 日志上报
- 数据分析
- 非关键API调用
路径优先级与P99监控策略
// 路径优先级配置
const pathPriority = {
// P0 - 关键业务路径
P0: {
paths: [
'/api/payment/create', // 支付创建
'/api/order/submit', // 订单提交
'/api/user/login', // 用户登录
'/api/game/battle', // 游戏战斗
'/api/checkout' // 结算
],
p99Threshold: 500, // P99超过500ms告警
p95Threshold: 300, // P95超过300ms告警
alertLevel: 'critical', // 严重告警
optimizationPriority: 'highest' // 最高优化优先级
},
// P1 - 重要业务路径
P1: {
paths: [
'/api/product/detail', // 商品详情
'/api/user/profile', // 用户资料
'/api/search' // 搜索
],
p99Threshold: 1000, // P99超过1s告警
p95Threshold: 500, // P95超过500ms告警
alertLevel: 'warning', // 警告级别
optimizationPriority: 'high' // 高优化优先级
},
// P2 - 一般业务路径
P2: {
paths: [
'/api/content/list', // 内容列表
'/api/settings' // 设置
],
p99Threshold: 2000, // P99超过2s告警
p95Threshold: 1000, // P95超过1s告警
alertLevel: 'info', // 信息级别
optimizationPriority: 'medium' // 中等优化优先级
},
// P3 - 辅助功能路径
P3: {
paths: [
'/api/analytics', // 数据分析
'/api/log' // 日志上报
],
p99Threshold: 5000, // P99超过5s告警
p95Threshold: 3000, // P95超过3s告警
alertLevel: 'low', // 低优先级
optimizationPriority: 'low' // 低优化优先级
}
};
// 根据路径获取优先级
function getPathPriority(path) {
for (const [priority, config] of Object.entries(pathPriority)) {
if (config.paths.some(p => path.includes(p))) {
return priority;
}
}
return 'P2'; // 默认P2
}
P0路径的P99监控实践
1. 实时监控P0路径的P99
// P0路径的实时P99监控
class CriticalPathMonitor {
constructor() {
this.metrics = new Map(); // path -> latency array
this.checkInterval = 1000; // 每秒检查一次
setInterval(() => this.checkP0Paths(), this.checkInterval);
}
// 记录指标
record(path, latency) {
const priority = getPathPriority(path);
if (!this.metrics.has(path)) {
this.metrics.set(path, []);
}
this.metrics.get(path).push({
latency,
priority,
timestamp: Date.now()
});
// 只保留最近1分钟的数据
const oneMinuteAgo = Date.now() - 60000;
const recent = this.metrics.get(path).filter(m => m.timestamp > oneMinuteAgo);
this.metrics.set(path, recent);
}
// 计算P99
calculateP99(latencies) {
if (latencies.length === 0) return 0;
const sorted = [...latencies].sort((a, b) => a - b);
const index = Math.ceil(0.99 * sorted.length) - 1;
return sorted[index];
}
// 检查P0路径
checkP0Paths() {
const p0Config = pathPriority.P0;
p0Config.paths.forEach(path => {
const metrics = this.metrics.get(path) || [];
const latencies = metrics.map(m => m.latency);
if (latencies.length < 10) return; // 数据不足
const p99 = this.calculateP99(latencies);
const p95 = this.calculateP95(latencies);
// P99告警
if (p99 > p0Config.p99Threshold) {
this.alert('critical', {
path,
p99,
threshold: p0Config.p99Threshold,
message: `P0路径 ${path} 的P99延迟 ${p99}ms 超过阈值 ${p0Config.p99Threshold}ms`
});
}
// P95告警
if (p95 > p0Config.p95Threshold) {
this.alert('warning', {
path,
p95,
threshold: p0Config.p95Threshold,
message: `P0路径 ${path} 的P95延迟 ${p95}ms 超过阈值 ${p0Config.p95Threshold}ms`
});
}
});
}
// 告警
alert(level, data) {
console.error(`[${level.toUpperCase()}]`, data);
// 发送到监控系统
// sendToMonitoringSystem(level, data);
}
calculateP95(latencies) {
if (latencies.length === 0) return 0;
const sorted = [...latencies].sort((a, b) => a - b);
const index = Math.ceil(0.95 * sorted.length) - 1;
return sorted[index];
}
}
// 使用示例
const monitor = new CriticalPathMonitor();
// 在API调用时记录
async function callAPI(path, options) {
const startTime = performance.now();
try {
const response = await fetch(path, options);
const latency = performance.now() - startTime;
// 记录指标
monitor.record(path, latency);
return response;
} catch (error) {
const latency = performance.now() - startTime;
monitor.record(path, latency);
throw error;
}
}
2. P0路径的详细分析
// P0路径的详细性能分析
function analyzeP0Path(path, timeWindow = 60000) {
const metrics = monitor.metrics.get(path) || [];
const now = Date.now();
const recent = metrics.filter(m => now - m.timestamp < timeWindow);
if (recent.length === 0) {
return { error: '数据不足' };
}
const latencies = recent.map(m => m.latency).sort((a, b) => a - b);
return {
path,
priority: 'P0',
sampleCount: latencies.length,
p50: calculatePercentile(latencies, 50),
p95: calculatePercentile(latencies, 95),
p99: calculatePercentile(latencies, 99),
p99_9: calculatePercentile(latencies, 99.9),
max: Math.max(...latencies),
min: Math.min(...latencies),
avg: latencies.reduce((a, b) => a + b, 0) / latencies.length,
// 慢请求分析
slowRequests: {
overP99: latencies.filter(l => l > calculatePercentile(latencies, 99)).length,
overP95: latencies.filter(l => l > calculatePercentile(latencies, 95)).length,
overThreshold: latencies.filter(l => l > pathPriority.P0.p99Threshold).length
},
// 业务影响评估
businessImpact: {
estimatedAffectedUsers: Math.ceil(latencies.length * 0.01), // 1%的用户
estimatedRevenueLoss: estimateRevenueLoss(path, latencies.length * 0.01),
severity: latencies.filter(l => l > pathPriority.P0.p99Threshold).length > 0 ? 'high' : 'medium'
}
};
}
// 估算收入损失(示例)
function estimateRevenueLoss(path, affectedUserCount) {
// 根据路径类型估算收入影响
const revenuePerUser = {
'/api/payment/create': 100, // 平均订单金额
'/api/order/submit': 100,
'/api/checkout': 100,
'/api/game/battle': 10, // 游戏内购买
default: 1
};
const rate = revenuePerUser[path] || revenuePerUser.default;
const conversionRate = 0.1; // 假设10%的转化率
// 假设延迟导致50%的用户流失
return affectedUserCount * rate * conversionRate * 0.5;
}
3. 关键路径的优化策略
// P0路径的优化策略
const p0OptimizationStrategies = {
// 1. 资源预留和隔离
resourceReservation: {
// 为P0路径预留专用资源
dedicatedServers: true,
connectionPool: {
min: 10,
max: 50
},
cache: {
enabled: true,
ttl: 300, // 5分钟
priority: 'high'
}
},
// 2. 超时和重试策略
timeoutAndRetry: {
timeout: 2000, // 2秒超时(比P99阈值更严格)
retryCount: 3, // 最多重试3次
retryDelay: 100, // 100ms后重试
circuitBreaker: {
enabled: true,
failureThreshold: 5,
recoveryTimeout: 30000
}
},
// 3. 降级策略
fallback: {
enabled: true,
// 当P99超过阈值时,启用降级方案
triggers: [
{
condition: 'p99 > 500',
action: 'useCache',
message: '使用缓存数据'
},
{
condition: 'p99 > 1000',
action: 'showQueue',
message: '显示排队提示'
}
]
},
// 4. 监控和告警
monitoring: {
realTime: true, // 实时监控
alertThreshold: 500, // P99超过500ms告警
escalation: {
level1: 'oncall', // 立即通知oncall
level2: 'manager', // 通知经理
level3: 'director' // 通知总监
}
}
};
实际案例:P0路径P99优化
案例1:支付路径优化
场景:
- 路径:
/api/payment/create - 问题:P99延迟达到2000ms,导致1%的支付请求超时
- 影响:每天损失约100个订单,约10,000元收入
优化措施:
- 数据库优化:为支付表添加索引,优化查询
- 缓存策略:缓存用户信息和商品信息
- 异步处理:将非关键操作(如日志、通知)异步化
- 连接池优化:增加数据库连接池大小
- CDN加速:静态资源使用CDN
优化结果:
- P99从2000ms降低到400ms
- 支付成功率从99%提升到99.9%
- 每天挽回约90个订单
案例2:游戏战斗路径优化
场景:
- 路径:
/api/game/battle - 问题:P99延迟达到3000ms,导致关键时刻卡顿
- 影响:玩家流失,游戏体验差
优化措施:
- 预测性加载:提前加载战斗所需资源
- 本地计算:将部分计算移到客户端
- UDP协议:关键操作使用UDP减少延迟
- 区域优化:根据玩家位置选择最近的服务器
- 补偿机制:延迟时给予玩家补偿
优化结果:
- P99从3000ms降低到500ms
- 玩家满意度提升30%
- 游戏内购买转化率提升15%
关键路径监控仪表板
// P0路径监控仪表板
class P0PathDashboard {
constructor() {
this.paths = pathPriority.P0.paths;
this.updateInterval = 5000; // 每5秒更新
}
// 生成监控报告
generateReport() {
const report = {
timestamp: new Date().toISOString(),
paths: this.paths.map(path => {
const analysis = analyzeP0Path(path);
return {
path,
status: this.getStatus(analysis),
metrics: {
p50: analysis.p50,
p95: analysis.p95,
p99: analysis.p99,
sampleCount: analysis.sampleCount
},
alerts: this.getAlerts(analysis),
businessImpact: analysis.businessImpact
};
}),
summary: this.generateSummary()
};
return report;
}
// 获取路径状态
getStatus(analysis) {
const threshold = pathPriority.P0.p99Threshold;
if (analysis.p99 > threshold) {
return 'critical';
} else if (analysis.p95 > pathPriority.P0.p95Threshold) {
return 'warning';
} else {
return 'healthy';
}
}
// 获取告警信息
getAlerts(analysis) {
const alerts = [];
const threshold = pathPriority.P0.p99Threshold;
if (analysis.p99 > threshold) {
alerts.push({
level: 'critical',
message: `P99延迟 ${analysis.p99}ms 超过阈值 ${threshold}ms`,
affectedUsers: analysis.businessImpact.estimatedAffectedUsers,
revenueLoss: analysis.businessImpact.estimatedRevenueLoss
});
}
return alerts;
}
// 生成摘要
generateSummary() {
// 汇总所有P0路径的状态
const allPaths = this.paths.map(path => analyzeP0Path(path));
const criticalPaths = allPaths.filter(p =>
p.p99 > pathPriority.P0.p99Threshold
);
return {
totalPaths: this.paths.length,
healthyPaths: this.paths.length - criticalPaths.length,
criticalPaths: criticalPaths.length,
totalAffectedUsers: criticalPaths.reduce((sum, p) =>
sum + p.businessImpact.estimatedAffectedUsers, 0
),
totalRevenueLoss: criticalPaths.reduce((sum, p) =>
sum + p.businessImpact.estimatedRevenueLoss, 0
)
};
}
}
// 使用示例
const dashboard = new P0PathDashboard();
setInterval(() => {
const report = dashboard.generateReport();
console.log('P0路径监控报告:', report);
// 发送到监控系统
// sendToMonitoringSystem(report);
}, 5000);
总结:P99要看路径
核心原则:
- P99的重要性取决于路径
- P0路径的P99:必须严格监控和优化
- P1路径的P99:重点关注
- P2/P3路径的P99:可以适当放宽
- P0中的P0要高度排查
- 关键业务路径的慢请求影响最大
- 即使只有1%的慢请求,也要优先处理
- 需要实时监控和快速响应
- 业务影响评估
- 慢1%可能意味着正在付费/打Boss/下单的用户
- 需要量化业务影响(用户数、收入损失等)
- 根据业务影响决定优化优先级
- 分层监控策略
- P0路径:实时监控,严格告警
- P1路径:定期监控,适度告警
- P2/P3路径:基础监控,宽松告警
实际应用场景
1. 性能监控和告警
设置合理的告警阈值(基于路径优先级)
// 性能监控配置示例(按路径优先级)
const performanceThresholds = {
// P0路径 - 关键业务路径
P0: {
apiLatency: {
p50: 50, // P50超过50ms告警(更严格)
p95: 300, // P95超过300ms告警
p99: 500 // P99超过500ms告警(必须立即处理)
},
pageLoad: {
p50: 500, // P50超过500ms告警
p95: 1500, // P95超过1.5s告警
p99: 3000 // P99超过3s告警
}
},
// P1路径 - 重要业务路径
P1: {
apiLatency: {
p50: 100, // P50超过100ms告警
p95: 500, // P95超过500ms告警
p99: 1000 // P99超过1s告警
},
pageLoad: {
p50: 1000, // P50超过1s告警
p95: 3000, // P95超过3s告警
p99: 5000 // P99超过5s告警
}
},
// P2/P3路径 - 一般/辅助路径
P2: {
apiLatency: {
p50: 200,
p95: 1000,
p99: 2000
}
},
P3: {
apiLatency: {
p50: 500,
p95: 2000,
p99: 5000
}
}
};
// 根据路径优先级获取告警阈值
function getThreshold(path, metricType) {
const priority = getPathPriority(path);
return performanceThresholds[priority]?.[metricType] || performanceThresholds.P2[metricType];
}
监控指标选择(基于路径优先级)
- P0路径监控:
- 实时监控:P50、P95、P99都要实时监控
- 告警策略:P99超过阈值立即告警,通知oncall
- 优化优先级:最高,即使只有1%的慢请求也要优先处理
- P1路径监控:
- 定期监控:重点关注P95和P99
- 告警策略:P99超过阈值告警,通知相关团队
- 优化优先级:高,需要及时处理
- P2/P3路径监控:
- 基础监控:主要关注P95
- 告警策略:P99超过阈值记录日志,定期review
- 优化优先级:中等/低,按计划优化
2. 性能优化目标设定
设定优化目标
// 性能优化目标
const optimizationGoals = {
// 当前性能
current: {
p50: 150,
p95: 800,
p99: 3000
},
// 优化目标
target: {
p50: 100, // 提升33%
p95: 500, // 提升37.5%
p99: 1500 // 提升50%
}
};
优化策略
- 优化P50:提升整体性能,影响大多数用户
- 优化常见路径
- 减少不必要的请求
- 优化资源加载
- 优化P95:提升大多数用户体验
- 优化慢查询
- 减少数据库查询
- 优化网络请求
- 优化P99:解决极端情况
- 排查异常请求
- 优化超时处理
- 处理边界情况
- 特别关注P0路径的P99:即使只有1%的慢请求,如果是P0路径也要立即处理
3. A/B测试和性能对比
对比不同版本的性能
// A/B测试性能对比
function comparePerformance(versionA, versionB) {
return {
p50: {
a: calculatePercentile(versionA, 50),
b: calculatePercentile(versionB, 50),
improvement: ((versionA.p50 - versionB.p50) / versionA.p50 * 100).toFixed(2) + '%'
},
p95: {
a: calculatePercentile(versionA, 95),
b: calculatePercentile(versionB, 95),
improvement: ((versionA.p95 - versionB.p95) / versionA.p95 * 100).toFixed(2) + '%'
},
p99: {
a: calculatePercentile(versionA, 99),
b: calculatePercentile(versionB, 99),
improvement: ((versionA.p99 - versionB.p99) / versionA.p99 * 100).toFixed(2) + '%'
}
};
}
4. 用户体验评估
用户体验分级
// 根据延迟评估用户体验
function evaluateUserExperience(latency) {
if (latency < 100) {
return '优秀 - 用户感觉即时响应';
} else if (latency < 300) {
return '良好 - 用户能感觉到轻微延迟';
} else if (latency < 1000) {
return '一般 - 用户明显感觉到延迟';
} else {
return '差 - 用户可能认为系统有问题';
}
}
// 评估不同百分位数的用户体验
function evaluatePercentiles(latencies) {
const p50 = calculatePercentile(latencies, 50);
const p95 = calculatePercentile(latencies, 95);
const p99 = calculatePercentile(latencies, 99);
return {
p50: evaluateUserExperience(p50),
p95: evaluateUserExperience(p95),
p99: evaluateUserExperience(p99)
};
}
客户端性能指标收集
1. 使用Performance API
Navigation Timing API
// 收集页面加载性能指标
function collectPageLoadMetrics() {
const navigation = performance.getEntriesByType('navigation')[0];
return {
// DNS解析时间
dns: navigation.domainLookupEnd - navigation.domainLookupStart,
// TCP连接时间
tcp: navigation.connectEnd - navigation.connectStart,
// TLS握手时间
tls: navigation.secureConnectionStart > 0
? navigation.connectEnd - navigation.secureConnectionStart
: 0,
// 请求时间
request: navigation.responseStart - navigation.requestStart,
// 响应时间
response: navigation.responseEnd - navigation.responseStart,
// DOM处理时间
domProcessing: navigation.domComplete - navigation.domInteractive,
// 总加载时间
load: navigation.loadEventEnd - navigation.fetchStart
};
}
Resource Timing API
// 收集资源加载性能指标
function collectResourceMetrics() {
const resources = performance.getEntriesByType('resource');
return resources.map(resource => ({
name: resource.name,
type: resource.initiatorType,
duration: resource.duration,
size: resource.transferSize,
// 延迟相关指标
dns: resource.domainLookupEnd - resource.domainLookupStart,
tcp: resource.connectEnd - resource.connectStart,
request: resource.responseStart - resource.requestStart,
response: resource.responseEnd - resource.responseStart
}));
}
2. 自定义性能指标
测量API请求延迟
// 测量API请求延迟
async function measureAPILatency(url, options = {}) {
const startTime = performance.now();
try {
const response = await fetch(url, options);
const endTime = performance.now();
const latency = endTime - startTime;
// 记录指标(自动包含路径优先级)
recordMetric('api_latency', latency, {
url,
path: url, // 添加path字段用于优先级识别
method: options.method || 'GET',
status: response.status
});
return { response, latency };
} catch (error) {
const endTime = performance.now();
const latency = endTime - startTime;
// 记录错误
recordMetric('api_latency_error', latency, {
url,
method: options.method || 'GET',
error: error.message
});
throw error;
}
}
测量用户交互延迟
// 测量用户交互延迟
function measureInteractionLatency(element, eventType, callback) {
element.addEventListener(eventType, function(event) {
const startTime = performance.now();
// 执行回调
callback(event);
// 使用requestAnimationFrame确保在下一帧测量
requestAnimationFrame(() => {
const endTime = performance.now();
const latency = endTime - startTime;
recordMetric('interaction_latency', latency, {
eventType,
element: element.tagName
});
});
});
}
// 使用示例
const button = document.querySelector('#submit-button');
measureInteractionLatency(button, 'click', (event) => {
// 处理点击事件
handleSubmit();
});
3. 性能指标上报
批量上报性能指标
// 性能指标收集器
class PerformanceCollector {
constructor() {
this.metrics = [];
this.batchSize = 50;
this.flushInterval = 5000; // 5秒
// 定期上报
setInterval(() => this.flush(), this.flushInterval);
// 页面卸载前上报
window.addEventListener('beforeunload', () => this.flush());
}
// 记录指标(支持路径优先级)
record(metricName, value, tags = {}) {
// 自动识别路径优先级
const path = tags.path || tags.url || '';
const priority = getPathPriority(path);
this.metrics.push({
name: metricName,
value,
tags: {
...tags,
path,
priority, // 添加优先级标签
isP0: priority === 'P0' // 是否为P0路径
},
timestamp: Date.now()
});
// P0路径的指标立即上报(不等待批量)
if (priority === 'P0' && this.metrics.length > 0) {
this.flush();
}
// 达到批量大小时立即上报
if (this.metrics.length >= this.batchSize) {
this.flush();
}
}
// 上报指标
async flush() {
if (this.metrics.length === 0) return;
const metricsToSend = [...this.metrics];
this.metrics = [];
try {
await fetch('/api/metrics', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(metricsToSend)
});
} catch (error) {
console.error('Failed to send metrics:', error);
// 失败时重新加入队列
this.metrics.unshift(...metricsToSend);
}
}
// 计算并上报百分位数(按路径优先级分组)
calculatePercentiles(metricName, percentiles = [50, 95, 99], groupByPriority = true) {
if (groupByPriority) {
// 按优先级分组计算
const priorities = ['P0', 'P1', 'P2', 'P3'];
priorities.forEach(priority => {
const values = this.metrics
.filter(m => m.name === metricName && m.tags?.priority === priority)
.map(m => m.value)
.sort((a, b) => a - b);
if (values.length === 0) return;
const results = {};
percentiles.forEach(p => {
const index = Math.ceil((p / 100) * values.length) - 1;
results[`p${p}`] = values[index];
});
// 上报百分位数(包含优先级标签)
Object.entries(results).forEach(([key, value]) => {
this.record(`${metricName}_${key}`, value, {
priority,
metricType: 'percentile'
});
});
});
} else {
// 全局计算(原有逻辑)
const values = this.metrics
.filter(m => m.name === metricName)
.map(m => m.value)
.sort((a, b) => a - b);
if (values.length === 0) return;
const results = {};
percentiles.forEach(p => {
const index = Math.ceil((p / 100) * values.length) - 1;
results[`p${p}`] = values[index];
});
// 上报百分位数
Object.entries(results).forEach(([key, value]) => {
this.record(`${metricName}_${key}`, value);
});
}
}
}
// 使用示例
const collector = new PerformanceCollector();
// 记录API延迟
collector.record('api_latency', 150, { endpoint: '/api/users' });
collector.record('api_latency', 200, { endpoint: '/api/users' });
// ... 更多数据
// 计算百分位数
collector.calculatePercentiles('api_latency');
性能分析工具
1. Chrome DevTools
Performance面板
- 录制性能:记录页面加载和交互性能
- 分析时间线:查看各个阶段的耗时
- 识别瓶颈:找出性能问题所在
Network面板
- 请求时间线:查看每个请求的详细时间
- Waterfall视图:可视化请求的各个阶段
- 请求统计:查看请求总数、总大小等
2. Web Vitals
Google提出的核心Web指标:
LCP (Largest Contentful Paint)
- 定义:最大内容绘制时间
- 目标:< 2.5秒
- 测量:使用PerformanceObserver
// 测量LCP
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
const lastEntry = entries[entries.length - 1];
console.log('LCP:', lastEntry.renderTime || lastEntry.loadTime);
}).observe({ entryTypes: ['largest-contentful-paint'] });
FID (First Input Delay)
- 定义:首次输入延迟
- 目标:< 100ms
- 测量:用户首次交互的延迟
// 测量FID
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
entries.forEach(entry => {
console.log('FID:', entry.processingStart - entry.startTime);
});
}).observe({ entryTypes: ['first-input'] });
CLS (Cumulative Layout Shift)
- 定义:累积布局偏移
- 目标:< 0.1
- 测量:布局不稳定的累积分数
// 测量CLS
let clsValue = 0;
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
if (!entry.hadRecentInput) {
clsValue += entry.value;
}
}
console.log('CLS:', clsValue);
}).observe({ entryTypes: ['layout-shift'] });
3. 第三方性能监控工具
常用工具
- Google Analytics:页面性能分析
- New Relic:应用性能监控
- Datadog:全栈性能监控
- Sentry:错误和性能监控
- Lighthouse:性能评分和优化建议
性能优化实践
1. 减少延迟的策略
网络优化
// 1. 使用HTTP/2
// HTTP/2支持多路复用,减少连接建立时间
// 2. 启用CDN
// 将静态资源部署到CDN,减少网络距离
// 3. 使用预连接
<link rel="preconnect" href="https://api.example.com">
<link rel="dns-prefetch" href="https://api.example.com">
// 4. 资源预加载
<link rel="preload" href="/critical.css" as="style">
<link rel="preload" href="/critical.js" as="script">
代码优化
// 1. 代码分割
// 使用动态导入减少初始加载时间
const module = await import('./heavy-module.js');
// 2. 懒加载
// 延迟加载非关键资源
const image = document.createElement('img');
image.loading = 'lazy';
image.src = 'image.jpg';
// 3. 防抖和节流
// 减少不必要的请求
function debounce(func, wait) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
// 4. 请求合并
// 将多个请求合并为一个
async function batchRequests(urls) {
const promises = urls.map(url => fetch(url));
return Promise.all(promises);
}
2. 监控和告警
设置性能预算
// 性能预算配置
const performanceBudget = {
// API延迟预算
api: {
p50: 100, // P50不超过100ms
p95: 500, // P95不超过500ms
p99: 2000 // P99不超过2000ms
},
// 页面加载预算
pageLoad: {
p50: 1000, // P50不超过1s
p95: 3000, // P95不超过3s
p99: 5000 // P99不超过5s
}
};
// 检查性能预算
function checkPerformanceBudget(metrics, budget) {
const violations = [];
Object.keys(budget).forEach(key => {
const metric = metrics[key];
const limits = budget[key];
Object.keys(limits).forEach(percentile => {
if (metric[percentile] > limits[percentile]) {
violations.push({
metric: key,
percentile,
actual: metric[percentile],
limit: limits[percentile]
});
}
});
});
return violations;
}
总结
关键要点
- 延迟(Latency)是客户端性能的核心指标,直接影响用户体验
- 百分位数(P50/P95/P99)帮助我们全面了解性能分布
- P50反映典型用户体验,P95适合SLA定义,P99用于问题排查
- P99要看路径:慢1%可能就是正在付费/打Boss/下单的那一刻
- 关键路径(P0)的P99必须高度排查:即使只有1%的慢请求,也要优先处理
- 需要结合多个指标才能全面评估性能
- 持续监控和优化是提升性能的关键
核心原则:P99的重要性取决于路径
最重要的洞察:
慢1% = 可能就是正在付费 / 打Boss / 下单的那一刻
这意味着:
- P0路径的P99:必须严格监控,超过阈值立即告警和处理
- P1路径的P99:重点关注,及时优化
- P2/P3路径的P99:可以适当放宽,按计划优化
P0中的P0要高度排查:
- 关键业务路径的慢请求影响最大
- 需要实时监控和快速响应
- 即使只有1%的慢请求,也要优先处理
- 需要量化业务影响(用户数、收入损失等)
最佳实践
- 路径优先级分类:
- 识别P0(关键)、P1(重要)、P2(一般)、P3(辅助)路径
- 为不同优先级路径设定不同的性能目标和告警阈值
- 分层监控策略:
- P0路径:实时监控P50、P95、P99,严格告警
- P1路径:定期监控P95、P99,适度告警
- P2/P3路径:基础监控,宽松告警
- 设定合理的性能目标:
- 根据路径优先级设定不同的P50、P95、P99目标
- P0路径的目标应该更严格
- 建立监控体系:
- 持续收集和分析性能指标
- 按路径优先级分组统计和展示
- 自动识别路径优先级
- 设置告警机制:
- P0路径的P99超过阈值立即告警,通知oncall
- 根据路径优先级设置不同的告警级别
- 量化业务影响(受影响用户数、收入损失等)
- 定期性能评估:
- 定期分析性能趋势,识别优化机会
- 重点关注P0路径的性能变化
- 评估优化措施的业务影响
- 优化极端情况:
- 不仅要优化平均值,更要关注P99等极端情况
- 特别关注P0路径的P99:即使只有1%的慢请求也要优先处理
- 根据业务影响决定优化优先级
性能优化是一个持续的过程
性能优化不是一次性的工作,而是一个持续的过程:
- 测量:收集性能数据
- 分析:识别性能瓶颈
- 优化:实施优化措施
- 验证:验证优化效果
- 迭代:持续改进
通过系统化的方法,我们可以不断提升客户端性能,为用户提供更好的体验。