性能优化最佳实践
本文档提供统一企业数字平台的性能优化建议,帮助您获得最佳的系统性能。
性能指标
关键指标定义
| 指标 | 目标值 | 说明 |
|---|---|---|
| TTFB | < 200ms | 首字节时间 |
| FCP | < 1.5s | 首次内容绘制 |
| LCP | < 2.5s | 最大内容绘制 |
| TTI | < 3.5s | 可交互时间 |
| API P99 | < 500ms | API 99分位响应时间 |
监控工具
// 前端性能监控
import { monitor } from '@enterprise-platform/monitor';
monitor.init({
appId: 'your-app-id',
reportUrl: '/api/monitor/report',
// 性能指标采集
performance: {
enabled: true,
sampleRate: 0.1, // 10% 采样
},
// 错误监控
error: {
enabled: true,
ignoreErrors: [/Script error/],
},
// 用户行为
behavior: {
enabled: true,
maxBreadcrumbs: 20,
}
});
前端优化
代码分割
// 路由级别代码分割
const routes = [
{
path: '/dashboard',
component: () => import('./pages/Dashboard'),
},
{
path: '/iot',
component: () => import('./pages/IoT'),
},
{
path: '/crm',
component: () => import('./pages/CRM'),
},
];
// 组件级别代码分割
const HeavyChart = lazy(() => import('./components/HeavyChart'));
function Dashboard() {
return (
<Suspense fallback={<ChartSkeleton />}>
<HeavyChart data={data} />
</Suspense>
);
}
资源优化
图片优化
<!-- 响应式图片 -->
<picture>
<source srcset="image.webp" type="image/webp">
<source srcset="image.jpg" type="image/jpeg">
<img src="image.jpg" alt="描述" loading="lazy">
</picture>
<!-- 使用 CDN 图片处理 -->
<img
src="https://cdn.example.com/image.jpg?w=400&q=80&f=webp"
alt="描述"
loading="lazy"
decoding="async"
>
字体优化
/* 字体预加载 */
@font-face {
font-family: 'CustomFont';
src: url('/fonts/custom.woff2') format('woff2');
font-display: swap; /* 避免 FOIT */
}
<!-- 预加载关键字体 -->
<link rel="preload" href="/fonts/custom.woff2" as="font" type="font/woff2" crossorigin>
缓存策略
// Service Worker 缓存策略
const CACHE_NAME = 'app-cache-v1';
// 缓存优先(静态资源)
self.addEventListener('fetch', (event) => {
if (event.request.url.includes('/static/')) {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request).then((fetchResponse) => {
const responseClone = fetchResponse.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, responseClone);
});
return fetchResponse;
});
})
);
}
});
// 网络优先(API 请求)
self.addEventListener('fetch', (event) => {
if (event.request.url.includes('/api/')) {
event.respondWith(
fetch(event.request)
.then((response) => {
const responseClone = response.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, responseClone);
});
return response;
})
.catch(() => caches.match(event.request))
);
}
});
虚拟列表
处理大量数据时使用虚拟滚动:
import { useVirtualList } from '@enterprise-platform/hooks';
function DeviceList({ devices }) {
const { list, containerProps, wrapperProps } = useVirtualList(devices, {
itemHeight: 60,
overscan: 5,
});
return (
<div {...containerProps} style={{ height: '500px', overflow: 'auto' }}>
<div {...wrapperProps}>
{list.map(({ data, index }) => (
<DeviceItem key={data.id} device={data} />
))}
</div>
</div>
);
}
状态管理优化
// 避免不必要的重渲染
import { memo, useMemo, useCallback } from 'react';
// 使用 memo 包装组件
const DeviceCard = memo(({ device, onSelect }) => {
return (
<div onClick={() => onSelect(device.id)}>
{device.name}
</div>
);
});
// 使用 useMemo 缓存计算结果
function DeviceStats({ devices }) {
const stats = useMemo(() => {
return {
total: devices.length,
online: devices.filter(d => d.status === 'online').length,
offline: devices.filter(d => d.status === 'offline').length,
};
}, [devices]);
return <StatsDisplay stats={stats} />;
}
// 使用 useCallback 缓存回调函数
function DeviceManager() {
const [selectedId, setSelectedId] = useState(null);
const handleSelect = useCallback((id) => {
setSelectedId(id);
}, []);
return <DeviceList onSelect={handleSelect} />;
}
后端优化
API 响应优化
字段筛选
// 只返回需要的字段
GET /api/devices?fields=id,name,status
// 后端实现
async function getDevices(query) {
const { fields } = query;
const select = fields ? fields.split(',') : undefined;
return await Device.find({})
.select(select)
.lean(); // 返回普通对象,性能更好
}
响应压缩
// Express 中间件
import compression from 'compression';
app.use(compression({
level: 6,
threshold: 1024, // 大于 1KB 才压缩
filter: (req, res) => {
if (req.headers['x-no-compression']) {
return false;
}
return compression.filter(req, res);
}
}));
响应缓存
// HTTP 缓存头
app.get('/api/products', async (req, res) => {
const products = await getProducts();
res.set({
'Cache-Control': 'public, max-age=300', // 5分钟
'ETag': generateETag(products),
});
res.json(products);
});
// 条件请求处理
app.get('/api/products', async (req, res) => {
const etag = req.headers['if-none-match'];
const currentEtag = await getProductsEtag();
if (etag === currentEtag) {
return res.status(304).end();
}
const products = await getProducts();
res.set('ETag', currentEtag);
res.json(products);
});
缓存层设计
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ 客户端 │ -> │ CDN │ -> │ Redis │ -> │ 数据库 │
│ 缓存 │ │ 缓存 │ │ 缓存 │ │ │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
1分钟 5分钟 10分钟 持久化
Redis 缓存实现
import Redis from 'ioredis';
const redis = new Redis({
host: 'localhost',
port: 6379,
keyPrefix: 'app:',
});
// 缓存装饰器
function Cacheable(options: { ttl: number; key: string }) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
const cacheKey = `${options.key}:${JSON.stringify(args)}`;
// 尝试从缓存获取
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
// 执行原方法
const result = await originalMethod.apply(this, args);
// 写入缓存
await redis.setex(cacheKey, options.ttl, JSON.stringify(result));
return result;
};
return descriptor;
};
}
// 使用示例
class DeviceService {
@Cacheable({ ttl: 60, key: 'device' })
async getDevice(id: string) {
return await Device.findById(id);
}
}
缓存更新策略
// 写穿透(Write-Through)
async function updateDevice(id: string, data: any) {
// 更新数据库
const device = await Device.findByIdAndUpdate(id, data, { new: true });
// 更新缓存
await redis.setex(`device:${id}`, 60, JSON.stringify(device));
return device;
}
// 缓存失效(Cache Invalidation)
async function updateDevice(id: string, data: any) {
// 更新数据库
const device = await Device.findByIdAndUpdate(id, data, { new: true });
// 删除缓存
await redis.del(`device:${id}`);
// 删除相关列表缓存
await redis.del('device:list:*');
return device;
}
异步处理
// 消息队列处理耗时任务
import { Queue, Worker } from 'bullmq';
// 创建队列
const reportQueue = new Queue('report-generation');
// API 接口 - 快速响应
app.post('/api/reports', async (req, res) => {
const job = await reportQueue.add('generate', {
type: req.body.type,
params: req.body.params,
userId: req.user.id,
});
res.json({
jobId: job.id,
status: 'processing',
checkUrl: `/api/reports/status/${job.id}`,
});
});
// Worker - 后台处理
const worker = new Worker('report-generation', async (job) => {
const { type, params, userId } = job.data;
// 更新进度
await job.updateProgress(10);
// 生成报表
const report = await generateReport(type, params);
await job.updateProgress(80);
// 保存结果
await saveReport(report, userId);
await job.updateProgress(100);
// 通知用户
await notifyUser(userId, 'report_ready', { reportId: report.id });
return { reportId: report.id };
});
数据库优化
索引优化
-- 分析慢查询
EXPLAIN ANALYZE SELECT * FROM devices WHERE status = 'online' AND product_id = 'prod_001';
-- 创建复合索引
CREATE INDEX idx_devices_status_product ON devices(status, product_id);
-- 创建部分索引(只索引在线设备)
CREATE INDEX idx_devices_online ON devices(product_id) WHERE status = 'online';
-- 创建覆盖索引
CREATE INDEX idx_devices_list ON devices(status, product_id) INCLUDE (name, last_online_time);
查询优化
// 避免 N+1 查询
// ❌ 错误示例
const devices = await Device.find({});
for (const device of devices) {
device.product = await Product.findById(device.productId);
}
// ✅ 正确示例 - 使用 populate
const devices = await Device.find({}).populate('productId');
// ✅ 正确示例 - 使用 aggregation
const devices = await Device.aggregate([
{ $match: { status: 'online' } },
{
$lookup: {
from: 'products',
localField: 'productId',
foreignField: '_id',
as: 'product'
}
},
{ $unwind: '$product' }
]);
分页优化
// ❌ 深分页性能差
const devices = await Device.find({})
.skip(10000)
.limit(20);
// ✅ 使用游标分页
const devices = await Device.find({
_id: { $gt: lastId } // 基于上一页最后一条记录
})
.sort({ _id: 1 })
.limit(20);
// ✅ 使用 keyset 分页
const devices = await Device.find({
$or: [
{ createdAt: { $lt: lastCreatedAt } },
{ createdAt: lastCreatedAt, _id: { $lt: lastId } }
]
})
.sort({ createdAt: -1, _id: -1 })
.limit(20);
读写分离
// 配置读写分离
const mongoose = require('mongoose');
// 主库(写)
const masterConnection = mongoose.createConnection('mongodb://master:27017/app');
// 从库(读)
const slaveConnection = mongoose.createConnection('mongodb://slave:27017/app', {
readPreference: 'secondaryPreferred'
});
// 模型绑定
const DeviceWrite = masterConnection.model('Device', deviceSchema);
const DeviceRead = slaveConnection.model('Device', deviceSchema);
// 使用
async function createDevice(data) {
return await DeviceWrite.create(data);
}
async function getDevices(query) {
return await DeviceRead.find(query);
}
IoT 数据优化
时序数据存储
-- 使用 TimescaleDB 存储时序数据
CREATE TABLE device_telemetry (
time TIMESTAMPTZ NOT NULL,
device_id TEXT NOT NULL,
temperature DOUBLE PRECISION,
humidity DOUBLE PRECISION,
power BOOLEAN
);
-- 转换为超表
SELECT create_hypertable('device_telemetry', 'time');
-- 创建索引
CREATE INDEX idx_telemetry_device_time ON device_telemetry (device_id, time DESC);
-- 设置数据保留策略(90天)
SELECT add_retention_policy('device_telemetry', INTERVAL '90 days');
-- 设置压缩策略(7天后压缩)
ALTER TABLE device_telemetry SET (
timescaledb.compress,
timescaledb.compress_segmentby = 'device_id'
);
SELECT add_compression_policy('device_telemetry', INTERVAL '7 days');
数据聚合
-- 创建连续聚合(每小时统计)
CREATE MATERIALIZED VIEW device_telemetry_hourly
WITH (timescaledb.continuous) AS
SELECT
time_bucket('1 hour', time) AS bucket,
device_id,
AVG(temperature) AS avg_temperature,
MAX(temperature) AS max_temperature,
MIN(temperature) AS min_temperature,
AVG(humidity) AS avg_humidity
FROM device_telemetry
GROUP BY bucket, device_id;
-- 设置刷新策略
SELECT add_continuous_aggregate_policy('device_telemetry_hourly',
start_offset => INTERVAL '3 hours',
end_offset => INTERVAL '1 hour',
schedule_interval => INTERVAL '1 hour'
);
批量写入
// 批量写入优化
class TelemetryBuffer {
private buffer: TelemetryData[] = [];
private readonly batchSize = 1000;
private readonly flushInterval = 5000;
constructor() {
setInterval(() => this.flush(), this.flushInterval);
}
add(data: TelemetryData) {
this.buffer.push(data);
if (this.buffer.length >= this.batchSize) {
this.flush();
}
}
async flush() {
if (this.buffer.length === 0) return;
const batch = this.buffer.splice(0, this.batchSize);
// 批量插入
await db.query(`
INSERT INTO device_telemetry (time, device_id, temperature, humidity, power)
SELECT * FROM unnest($1::timestamptz[], $2::text[], $3::float[], $4::float[], $5::boolean[])
`, [
batch.map(d => d.time),
batch.map(d => d.deviceId),
batch.map(d => d.temperature),
batch.map(d => d.humidity),
batch.map(d => d.power),
]);
}
}
性能测试
负载测试
# k6 负载测试脚本
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
stages: [
{ duration: '2m', target: 100 }, // 2分钟内增加到100用户
{ duration: '5m', target: 100 }, // 保持100用户5分钟
{ duration: '2m', target: 200 }, // 2分钟内增加到200用户
{ duration: '5m', target: 200 }, // 保持200用户5分钟
{ duration: '2m', target: 0 }, // 2分钟内降到0
],
thresholds: {
http_req_duration: ['p(95)<500'], // 95%请求在500ms内
http_req_failed: ['rate<0.01'], // 错误率小于1%
},
};
export default function () {
const res = http.get('https://api.example.com/v1/devices');
check(res, {
'status is 200': (r) => r.status === 200,
'response time < 500ms': (r) => r.timings.duration < 500,
});
sleep(1);
}