性能 优化 缓存 数据库

性能优化最佳实践

平台性能优化指南,包括前端优化、后端优化、数据库优化等

性能优化最佳实践

本文档提供统一企业数字平台的性能优化建议,帮助您获得最佳的系统性能。

性能指标

关键指标定义

指标目标值说明
TTFB< 200ms首字节时间
FCP< 1.5s首次内容绘制
LCP< 2.5s最大内容绘制
TTI< 3.5s可交互时间
API P99< 500msAPI 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);
}

下一步