07-Web性能优化完全指南

3 阅读19分钟

Web性能优化完全指南

目录

  1. 前端性能优化
  2. 数据库性能优化
  3. 缓存性能优化
  4. 高并发优化
  5. 服务端性能优化
  6. 监控与分析
  7. 实战案例

一、前端性能优化

1.1 CDN加速

什么是CDN?

CDN (Content Delivery Network) 内容分发网络,通过在全球各地部署边缘节点,使用户就近访问资源,减少网络延迟。

CDN优化策略
1. 静态资源CDN化

优化前

<!-- 所有资源从源站加载 -->
<link rel="stylesheet" href="/static/css/style.css">
<script src="/static/js/app.js"></script>
<img src="/static/images/logo.png">

优化后

<!-- 静态资源使用CDN -->
<link rel="stylesheet" href="https://cdn.example.com/static/css/style.css?v=1.0.0">
<script src="https://cdn.example.com/static/js/app.js?v=1.0.0"></script>
<img src="https://cdn.example.com/static/images/logo.png">
2. 第三方库使用公共CDN
<!-- 使用公共CDN加速常用库 -->
<script src="https://cdn.jsdelivr.net/npm/vue@3.3.4/dist/vue.global.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
3. CDN配置最佳实践

Nginx配置示例

# CDN源站配置
server {
    listen 80;
    server_name cdn-origin.example.com;
    
    # 开启Gzip压缩
    gzip on;
    gzip_vary on;
    gzip_min_length 1k;
    gzip_comp_level 6;
    gzip_types text/plain text/css text/xml text/javascript 
               application/json application/javascript application/xml+rss;
    
    # 静态资源缓存
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|pdf|txt)$ {
        root /var/www/static;
        expires 30d;
        add_header Cache-Control "public, immutable";
        add_header Access-Control-Allow-Origin *;
    }
    
    # 防盗链
    location ~* \.(jpg|jpeg|png|gif)$ {
        valid_referers none blocked *.example.com;
        if ($invalid_referer) {
            return 403;
        }
    }
}
CDN选择建议
CDN服务商特点适用场景
阿里云CDN国内节点多,速度快国内业务为主
腾讯云CDN价格适中,稳定性好国内中小型项目
Cloudflare全球节点,免费套餐国际业务
AWS CloudFront全球覆盖,高可用大型企业项目
性能提升效果
  • 页面加载时间: 减少 40%-60%
  • 带宽成本: 降低 60%-80%
  • 并发能力: 提升 10-100倍

1.2 静态资源优化

图片优化
1. 图片格式选择
格式特点适用场景
WebP体积小,质量高现代浏览器(减少30%-50%体积)
JPEG有损压缩,适合照片照片、复杂图像
PNG无损压缩,支持透明Logo、图标、需要透明背景
SVG矢量图,体积小图标、简单图形
2. 图片压缩
// 前端图片压缩(上传前)
function compressImage(file, quality = 0.8) {
    return new Promise((resolve) => {
        const reader = new FileReader();
        reader.readAsDataURL(file);
        reader.onload = (e) => {
            const img = new Image();
            img.src = e.target.result;
            img.onload = () => {
                const canvas = document.createElement('canvas');
                const ctx = canvas.getContext('2d');
                
                // 等比例缩放
                let width = img.width;
                let height = img.height;
                if (width > 1920) {
                    height = (1920 / width) * height;
                    width = 1920;
                }
                
                canvas.width = width;
                canvas.height = height;
                ctx.drawImage(img, 0, 0, width, height);
                
                canvas.toBlob(resolve, 'image/jpeg', quality);
            };
        };
    });
}

// 使用示例
document.getElementById('upload').addEventListener('change', async (e) => {
    const file = e.target.files[0];
    const compressed = await compressImage(file, 0.8);
    console.log(`原始大小: ${(file.size / 1024).toFixed(2)}KB`);
    console.log(`压缩后: ${(compressed.size / 1024).toFixed(2)}KB`);
});
3. 响应式图片
<!-- 使用 srcset 提供不同尺寸 -->
<img src="image-800.jpg"
     srcset="image-400.jpg 400w,
             image-800.jpg 800w,
             image-1200.jpg 1200w"
     sizes="(max-width: 600px) 400px,
            (max-width: 1200px) 800px,
            1200px"
     alt="响应式图片">

<!-- 使用 picture 元素 -->
<picture>
    <source media="(min-width: 1200px)" srcset="large.webp" type="image/webp">
    <source media="(min-width: 768px)" srcset="medium.webp" type="image/webp">
    <source srcset="small.webp" type="image/webp">
    <img src="fallback.jpg" alt="图片">
</picture>
4. 图片懒加载
// 原生懒加载(现代浏览器支持)
<img src="image.jpg" loading="lazy" alt="懒加载图片">

// 自定义懒加载实现
class LazyLoad {
    constructor() {
        this.observer = new IntersectionObserver(
            (entries) => this.handleIntersection(entries),
            { rootMargin: '50px' }
        );
        this.images = document.querySelectorAll('img[data-src]');
        this.images.forEach(img => this.observer.observe(img));
    }
    
    handleIntersection(entries) {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                const img = entry.target;
                img.src = img.dataset.src;
                img.removeAttribute('data-src');
                this.observer.unobserve(img);
            }
        });
    }
}

// 使用
new LazyLoad();
<!-- HTML结构 -->
<img data-src="real-image.jpg" src="placeholder.jpg" alt="懒加载">
CSS/JS资源优化
1. 代码压缩与合并
// webpack配置示例
module.exports = {
    mode: 'production',
    optimization: {
        minimize: true,
        minimizer: [
            new TerserPlugin({
                terserOptions: {
                    compress: {
                        drop_console: true, // 删除console
                        drop_debugger: true, // 删除debugger
                    }
                }
            }),
            new CssMinimizerPlugin()
        ],
        // 代码分割
        splitChunks: {
            chunks: 'all',
            cacheGroups: {
                vendor: {
                    test: /[\\/]node_modules[\\/]/,
                    name: 'vendors',
                    priority: 10
                },
                common: {
                    minChunks: 2,
                    priority: 5,
                    reuseExistingChunk: true
                }
            }
        }
    }
};
2. Tree Shaking(摇树优化)
// 使用ES6模块,避免全量引入
// ❌ 错误做法
import _ from 'lodash';

// ✅ 正确做法
import debounce from 'lodash/debounce';
import throttle from 'lodash/throttle';

// package.json配置
{
    "sideEffects": false  // 表示所有代码都无副作用,可以安全删除
}
3. 关键CSS内联
<!DOCTYPE html>
<html>
<head>
    <!-- 关键CSS内联,加快首屏渲染 -->
    <style>
        /* 首屏关键样式 */
        body { margin: 0; font-family: Arial; }
        .header { background: #333; color: #fff; padding: 20px; }
        .hero { min-height: 500px; background: url('hero-bg.jpg'); }
    </style>
    
    <!-- 非关键CSS异步加载 -->
    <link rel="preload" href="styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
    <noscript><link rel="stylesheet" href="styles.css"></noscript>
</head>
<body>
    <!-- 页面内容 -->
</body>
</html>

1.3 浏览器缓存

缓存策略
1. 强缓存 (Cache-Control)
# Nginx配置
location ~* \.(jpg|jpeg|png|gif|ico)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
}

location ~* \.(css|js)$ {
    expires 30d;
    add_header Cache-Control "public, max-age=2592000";
}

location ~* \.(html|htm)$ {
    expires -1;
    add_header Cache-Control "no-cache, no-store, must-revalidate";
}
2. 协商缓存 (ETag / Last-Modified)
// PHP设置ETag
$file = '/path/to/file.jpg';
$etag = md5_file($file);
$lastModified = filemtime($file);

// 检查客户端缓存
$ifNoneMatch = $_SERVER['HTTP_IF_NONE_MATCH'] ?? '';
$ifModifiedSince = $_SERVER['HTTP_IF_MODIFIED_SINCE'] ?? '';

if ($ifNoneMatch === $etag || strtotime($ifModifiedSince) === $lastModified) {
    header('HTTP/1.1 304 Not Modified');
    exit;
}

// 设置响应头
header('ETag: ' . $etag);
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $lastModified) . ' GMT');
header('Cache-Control: max-age=3600');

// 输出文件
readfile($file);
3. Service Worker缓存
// service-worker.js
const CACHE_NAME = 'my-site-cache-v1';
const urlsToCache = [
    '/',
    '/css/style.css',
    '/js/app.js',
    '/images/logo.png'
];

// 安装
self.addEventListener('install', event => {
    event.waitUntil(
        caches.open(CACHE_NAME)
            .then(cache => cache.addAll(urlsToCache))
    );
});

// 拦截请求
self.addEventListener('fetch', event => {
    event.respondWith(
        caches.match(event.request)
            .then(response => {
                // 缓存命中,返回缓存
                if (response) {
                    return response;
                }
                // 缓存未命中,发起网络请求
                return fetch(event.request).then(response => {
                    // 不缓存非GET请求
                    if (!response || response.status !== 200 || response.type !== 'basic') {
                        return response;
                    }
                    // 克隆响应并缓存
                    const responseToCache = response.clone();
                    caches.open(CACHE_NAME)
                        .then(cache => cache.put(event.request, responseToCache));
                    return response;
                });
            })
    );
});

// 清理旧缓存
self.addEventListener('activate', event => {
    event.waitUntil(
        caches.keys().then(cacheNames => {
            return Promise.all(
                cacheNames.map(cacheName => {
                    if (cacheName !== CACHE_NAME) {
                        return caches.delete(cacheName);
                    }
                })
            );
        })
    );
});
缓存策略总结
资源类型缓存策略过期时间
HTMLno-cache实时
CSS/JSpublic, max-age30天(带版本号)
图片public, immutable1年
API数据no-store无缓存

1.4 代码优化

JavaScript优化
1. 防抖与节流
// 防抖:延迟执行,多次触发只执行最后一次
function debounce(func, wait) {
    let timeout;
    return function executedFunction(...args) {
        const later = () => {
            clearTimeout(timeout);
            func(...args);
        };
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
    };
}

// 使用场景:搜索框输入
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', debounce((e) => {
    console.log('搜索:', e.target.value);
    // 发起API请求
}, 500));

// 节流:固定时间内只执行一次
function throttle(func, limit) {
    let inThrottle;
    return function(...args) {
        if (!inThrottle) {
            func.apply(this, args);
            inThrottle = true;
            setTimeout(() => inThrottle = false, limit);
        }
    };
}

// 使用场景:滚动事件
window.addEventListener('scroll', throttle(() => {
    console.log('滚动位置:', window.scrollY);
}, 200));
2. 虚拟列表(长列表优化)
// 虚拟滚动实现
class VirtualList {
    constructor(container, data, options) {
        this.container = container;
        this.data = data;
        this.itemHeight = options.itemHeight || 50;
        this.bufferSize = options.bufferSize || 5;
        
        this.viewportHeight = container.clientHeight;
        this.visibleCount = Math.ceil(this.viewportHeight / this.itemHeight);
        this.totalHeight = data.length * this.itemHeight;
        
        this.init();
    }
    
    init() {
        // 创建容器
        this.listContent = document.createElement('div');
        this.listContent.style.height = `${this.totalHeight}px`;
        this.listContent.style.position = 'relative';
        
        this.container.appendChild(this.listContent);
        this.container.addEventListener('scroll', () => this.render());
        
        this.render();
    }
    
    render() {
        const scrollTop = this.container.scrollTop;
        const startIndex = Math.max(0, Math.floor(scrollTop / this.itemHeight) - this.bufferSize);
        const endIndex = Math.min(
            this.data.length,
            startIndex + this.visibleCount + this.bufferSize * 2
        );
        
        // 清空现有内容
        this.listContent.innerHTML = '';
        
        // 渲染可见项
        for (let i = startIndex; i < endIndex; i++) {
            const item = document.createElement('div');
            item.style.position = 'absolute';
            item.style.top = `${i * this.itemHeight}px`;
            item.style.height = `${this.itemHeight}px`;
            item.textContent = this.data[i];
            this.listContent.appendChild(item);
        }
    }
}

// 使用
const container = document.getElementById('list-container');
const data = Array.from({ length: 10000 }, (_, i) => `Item ${i + 1}`);
new VirtualList(container, data, { itemHeight: 50 });
3. Web Worker(后台线程)
// main.js - 主线程
const worker = new Worker('worker.js');

// 发送大量数据处理任务
worker.postMessage({
    type: 'processData',
    data: largeDataArray
});

// 接收处理结果
worker.addEventListener('message', (e) => {
    console.log('处理结果:', e.data);
});

// worker.js - 工作线程
self.addEventListener('message', (e) => {
    if (e.data.type === 'processData') {
        const result = processLargeData(e.data.data);
        self.postMessage(result);
    }
});

function processLargeData(data) {
    // 执行耗时计算
    return data.map(item => {
        // 复杂运算
        return Math.sqrt(item) * Math.PI;
    });
}
CSS优化
1. 避免重排重绘
// ❌ 多次重排
for (let i = 0; i < 1000; i++) {
    const element = document.createElement('div');
    element.style.width = '100px';
    element.style.height = '100px';
    document.body.appendChild(element); // 每次都触发重排
}

// ✅ 批量操作
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
    const element = document.createElement('div');
    element.style.cssText = 'width: 100px; height: 100px;'; // 一次设置多个样式
    fragment.appendChild(element);
}
document.body.appendChild(fragment); // 只触发一次重排
2. 使用CSS动画代替JS动画
/* CSS动画(GPU加速) */
.animate {
    transition: transform 0.3s ease;
}

.animate:hover {
    transform: translateX(100px); /* 使用transform而非left */
}

/* 3D变换触发GPU加速 */
.gpu-accelerated {
    transform: translateZ(0);
    /* 或 */
    will-change: transform;
}

二、数据库性能优化

2.1 MySQL索引优化

索引基础
索引类型对比
索引类型特点使用场景
主键索引 (PRIMARY KEY)唯一且非空,聚簇索引ID字段
唯一索引 (UNIQUE)唯一但可空用户名、邮箱
普通索引 (INDEX)最常用查询条件字段
全文索引 (FULLTEXT)文本搜索文章内容搜索
组合索引多列索引多条件查询
索引设计原则
1. 选择合适的列创建索引
-- ✅ 经常出现在WHERE、ORDER BY、GROUP BY的列
CREATE INDEX idx_user_email ON users(email);
CREATE INDEX idx_order_user_status ON orders(user_id, status);

-- ❌ 不适合创建索引的情况
-- 1. 数据重复度高的列(如性别)
-- 2. 频繁更新的列
-- 3. 很少在查询中使用的列
2. 组合索引最左前缀原则
-- 创建组合索引
CREATE INDEX idx_abc ON table_name(a, b, c);

-- ✅ 可以使用索引的查询
SELECT * FROM table_name WHERE a = 1;
SELECT * FROM table_name WHERE a = 1 AND b = 2;
SELECT * FROM table_name WHERE a = 1 AND b = 2 AND c = 3;

-- ❌ 无法使用索引的查询
SELECT * FROM table_name WHERE b = 2;
SELECT * FROM table_name WHERE c = 3;
SELECT * FROM table_name WHERE b = 2 AND c = 3;
3. 避免索引失效
-- ❌ 索引失效的情况

-- 1. 使用函数
SELECT * FROM users WHERE YEAR(created_at) = 2024;
-- ✅ 改进
SELECT * FROM users WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01';

-- 2. 类型转换
SELECT * FROM users WHERE phone = 13800138000; -- phone是varchar
-- ✅ 改进
SELECT * FROM users WHERE phone = '13800138000';

-- 3. 使用 OR(某些情况)
SELECT * FROM users WHERE name = 'Alice' OR age = 25;
-- ✅ 改进:使用UNION
SELECT * FROM users WHERE name = 'Alice'
UNION
SELECT * FROM users WHERE age = 25;

-- 4. LIKE以通配符开头
SELECT * FROM users WHERE name LIKE '%Alice';
-- ✅ 改进
SELECT * FROM users WHERE name LIKE 'Alice%';

-- 5. NOT、!=、<>
SELECT * FROM users WHERE status != 1;
-- ✅ 改进:使用IN
SELECT * FROM users WHERE status IN (0, 2, 3);
索引优化实战
案例1:电商订单表优化
-- 订单表结构
CREATE TABLE `orders` (
    `id` bigint PRIMARY KEY AUTO_INCREMENT,
    `order_no` varchar(32) NOT NULL,
    `user_id` bigint NOT NULL,
    `status` tinyint NOT NULL DEFAULT 0,
    `total_amount` decimal(10,2) NOT NULL,
    `created_at` datetime NOT NULL,
    `updated_at` datetime NOT NULL,
    INDEX idx_user_id (user_id),
    INDEX idx_status (status),
    INDEX idx_created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 慢查询问题
SELECT * FROM orders 
WHERE user_id = 123 
  AND status IN (1, 2, 3) 
  AND created_at >= '2024-01-01'
ORDER BY created_at DESC
LIMIT 20;

-- 查看执行计划
EXPLAIN SELECT * FROM orders 
WHERE user_id = 123 AND status IN (1, 2, 3) AND created_at >= '2024-01-01'
ORDER BY created_at DESC LIMIT 20;

-- 优化:创建组合索引
CREATE INDEX idx_user_status_created ON orders(user_id, status, created_at);

-- 删除旧索引(避免冗余)
DROP INDEX idx_user_id ON orders;
DROP INDEX idx_status ON orders;
DROP INDEX idx_created_at ON orders;
案例2:覆盖索引优化
-- 慢查询
SELECT order_no, total_amount, created_at 
FROM orders 
WHERE user_id = 123 
ORDER BY created_at DESC 
LIMIT 10;

-- 优化:使用覆盖索引(索引包含所有查询字段)
CREATE INDEX idx_user_created_cover ON orders(
    user_id, 
    created_at, 
    order_no, 
    total_amount
);

-- 此时查询只需访问索引,无需回表
索引监控与维护
-- 1. 查看索引使用情况
SELECT 
    t.TABLE_SCHEMA,
    t.TABLE_NAME,
    s.INDEX_NAME,
    s.COLUMN_NAME,
    t.TABLE_ROWS,
    s.CARDINALITY,
    s.SEQ_IN_INDEX
FROM 
    information_schema.STATISTICS s
JOIN 
    information_schema.TABLES t 
    ON s.TABLE_SCHEMA = t.TABLE_SCHEMA 
    AND s.TABLE_NAME = t.TABLE_NAME
WHERE 
    t.TABLE_SCHEMA = 'your_database'
ORDER BY 
    t.TABLE_NAME, s.INDEX_NAME, s.SEQ_IN_INDEX;

-- 2. 查找未使用的索引(MySQL 5.7+)
SELECT 
    object_schema,
    object_name,
    index_name
FROM 
    performance_schema.table_io_waits_summary_by_index_usage
WHERE 
    index_name IS NOT NULL
    AND count_star = 0
    AND object_schema = 'your_database'
    AND index_name != 'PRIMARY'
ORDER BY 
    object_schema, object_name;

-- 3. 分析表(更新索引统计信息)
ANALYZE TABLE orders;

-- 4. 优化表(整理碎片)
OPTIMIZE TABLE orders;

2.2 SQL查询优化

查询优化技巧
1. SELECT字段优化
-- ❌ 避免使用SELECT *
SELECT * FROM users WHERE id = 123;

-- ✅ 只查询需要的字段
SELECT id, name, email FROM users WHERE id = 123;

-- 性能提升原因:
-- 1. 减少数据传输量
-- 2. 可能使用覆盖索引,避免回表
-- 3. 减少内存占用
2. 分页优化
-- ❌ 深度分页性能差
SELECT * FROM orders ORDER BY id LIMIT 100000, 20;

-- ✅ 使用主键或索引进行分页
SELECT * FROM orders WHERE id > 100000 ORDER BY id LIMIT 20;

-- ✅ 延迟关联(先查ID,再关联)
SELECT o.* FROM orders o
INNER JOIN (
    SELECT id FROM orders ORDER BY created_at DESC LIMIT 100000, 20
) tmp ON o.id = tmp.id;
3. JOIN优化
-- ❌ 子查询性能差
SELECT * FROM orders 
WHERE user_id IN (
    SELECT id FROM users WHERE status = 1
);

-- ✅ 使用JOIN替代子查询
SELECT o.* FROM orders o
INNER JOIN users u ON o.user_id = u.id
WHERE u.status = 1;

-- JOIN优化要点:
-- 1. 确保关联字段有索引
-- 2. 小表驱动大表
-- 3. 避免使用LEFT JOIN(如果不需要NULL值)
4. COUNT优化
-- ❌ 慢查询
SELECT COUNT(*) FROM orders WHERE status = 1;

-- ✅ 使用近似值(大表)
SELECT table_rows FROM information_schema.tables 
WHERE table_schema = 'your_db' AND table_name = 'orders';

-- ✅ 使用计数表(实时性要求不高)
CREATE TABLE order_count (
    status tinyint PRIMARY KEY,
    count int NOT NULL DEFAULT 0
);

-- 插入订单时维护计数
INSERT INTO orders (...) VALUES (...);
UPDATE order_count SET count = count + 1 WHERE status = 1;
5. EXISTS vs IN
-- 大表 EXISTS 更快
SELECT * FROM orders o
WHERE EXISTS (
    SELECT 1 FROM users u WHERE u.id = o.user_id AND u.status = 1
);

-- 小表 IN 更快
SELECT * FROM orders 
WHERE user_id IN (
    SELECT id FROM users WHERE status = 1 LIMIT 100
);
查询性能分析
EXPLAIN详解
EXPLAIN SELECT * FROM orders WHERE user_id = 123;
字段说明优化目标
type访问类型system > const > eq_ref > ref > range > index > ALL
possible_keys可能使用的索引-
key实际使用的索引确保使用了合适的索引
rows扫描行数越少越好
Extra额外信息Using index (覆盖索引), Using filesort (避免)
-- 查看详细执行信息
EXPLAIN FORMAT=JSON SELECT * FROM orders WHERE user_id = 123;

-- 查看实际执行情况(MySQL 8.0+)
EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 123;

2.3 数据库架构优化

读写分离
// ThinkPHP 8.0 读写分离配置
// config/database.php
return [
    'default' => 'mysql',
    'connections' => [
        'mysql' => [
            'type' => 'mysql',
            'hostname' => '192.168.1.10', // 主库
            'database' => 'shop',
            'username' => 'root',
            'password' => 'password',
            'charset' => 'utf8mb4',
            'deploy' => 1, // 开启主从
            'rw_separate' => true, // 读写分离
            'master_num' => 1, // 主服务器数量
            'slave_no' => '', // 从库序号
            'fields_strict' => true,
            // 主库配置
            'hostname' => ['192.168.1.10'],
            // 从库配置
            'slave_hostname' => ['192.168.1.20', '192.168.1.21'],
        ]
    ]
];

// 使用示例
// 写操作自动使用主库
Db::name('orders')->insert($data);

// 读操作自动使用从库
Db::name('orders')->where('id', 123)->find();

// 强制使用主库读取
Db::name('orders')->master()->where('id', 123)->find();
分库分表
垂直分库
-- 原始单库结构
database: shop
  - users (用户表)
  - orders (订单表)
  - products (商品表)
  - logs (日志表)

-- 垂直分库后
database: shop_user
  - users
  
database: shop_order
  - orders
  
database: shop_product
  - products
  
database: shop_log
  - logs
水平分表(按用户ID)
// 分表策略
class ShardingService
{
    // 分表数量
    const TABLE_COUNT = 16;
    
    /**
     * 根据用户ID获取表名
     */
    public static function getTableName($userId, $baseTable = 'orders')
    {
        $suffix = $userId % self::TABLE_COUNT;
        return sprintf('%s_%02d', $baseTable, $suffix);
    }
    
    /**
     * 插入订单
     */
    public static function insertOrder($data)
    {
        $table = self::getTableName($data['user_id']);
        return Db::name($table)->insert($data);
    }
    
    /**
     * 查询用户订单
     */
    public static function getUserOrders($userId)
    {
        $table = self::getTableName($userId);
        return Db::name($table)->where('user_id', $userId)->select();
    }
    
    /**
     * 跨表查询(需要遍历所有表)
     */
    public static function queryAllOrders($conditions)
    {
        $result = [];
        for ($i = 0; $i < self::TABLE_COUNT; $i++) {
            $table = sprintf('orders_%02d', $i);
            $data = Db::name($table)->where($conditions)->select();
            $result = array_merge($result, $data->toArray());
        }
        return $result;
    }
}

// 创建分表
for ($i = 0; $i < 16; $i++) {
    $sql = "
    CREATE TABLE orders_" . sprintf('%02d', $i) . " LIKE orders;
    ";
    Db::execute($sql);
}
数据归档
-- 历史数据归档策略

-- 1. 创建归档表
CREATE TABLE orders_archive LIKE orders;

-- 2. 定期归档(每月执行)
INSERT INTO orders_archive
SELECT * FROM orders 
WHERE created_at < DATE_SUB(NOW(), INTERVAL 6 MONTH);

-- 3. 删除已归档数据
DELETE FROM orders 
WHERE created_at < DATE_SUB(NOW(), INTERVAL 6 MONTH);

-- 4. 优化原表
OPTIMIZE TABLE orders;

三、缓存性能优化

3.1 Redis缓存策略

缓存模式
1. Cache-Aside(旁路缓存)
// 最常用的缓存模式
class ProductService
{
    /**
     * 获取商品信息
     */
    public static function getProduct($productId)
    {
        $cacheKey = 'product:' . $productId;
        
        // 1. 先查缓存
        $product = Redis::get($cacheKey);
        if ($product !== false) {
            return json_decode($product, true);
        }
        
        // 2. 缓存未命中,查数据库
        $product = Db::name('products')->where('id', $productId)->find();
        if (!$product) {
            return null;
        }
        
        // 3. 写入缓存
        Redis::setex($cacheKey, 3600, json_encode($product));
        
        return $product;
    }
    
    /**
     * 更新商品信息
     */
    public static function updateProduct($productId, $data)
    {
        // 1. 更新数据库
        $result = Db::name('products')->where('id', $productId)->update($data);
        
        // 2. 删除缓存(而非更新)
        Redis::del('product:' . $productId);
        
        return $result;
    }
}
2. Read-Through(读穿透)
// 由缓存层负责数据加载
class CacheService
{
    public static function get($key, $loader, $ttl = 3600)
    {
        $value = Redis::get($key);
        if ($value !== false) {
            return json_decode($value, true);
        }
        
        // 缓存未命中,调用加载器
        $value = $loader();
        if ($value !== null) {
            Redis::setex($key, $ttl, json_encode($value));
        }
        
        return $value;
    }
}

// 使用
$product = CacheService::get(
    'product:123',
    function() {
        return Db::name('products')->where('id', 123)->find();
    },
    3600
);
3. Write-Through(写穿透)
// 写操作同时更新缓存和数据库
class ProductService
{
    public static function updateProduct($productId, $data)
    {
        // 开启事务
        Db::startTrans();
        try {
            // 1. 更新数据库
            Db::name('products')->where('id', $productId)->update($data);
            
            // 2. 获取最新数据
            $product = Db::name('products')->where('id', $productId)->find();
            
            // 3. 更新缓存
            $cacheKey = 'product:' . $productId;
            Redis::setex($cacheKey, 3600, json_encode($product));
            
            Db::commit();
            return true;
        } catch (\Exception $e) {
            Db::rollback();
            return false;
        }
    }
}
缓存数据结构选择
// 1. String - 简单键值
Redis::set('user:123:name', 'Alice');
Redis::expire('user:123:name', 3600);

// 2. Hash - 对象存储
Redis::hMSet('user:123', [
    'name' => 'Alice',
    'email' => 'alice@example.com',
    'age' => 25
]);
Redis::expire('user:123', 3600);

// 获取单个字段
$name = Redis::hGet('user:123', 'name');

// 获取所有字段
$user = Redis::hGetAll('user:123');

// 3. List - 队列、时间线
Redis::lPush('user:123:messages', json_encode($message));
Redis::lTrim('user:123:messages', 0, 99); // 只保留最新100条

// 4. Set - 去重集合
Redis::sAdd('user:123:tags', 'PHP', 'MySQL', 'Redis');
$tags = Redis::sMembers('user:123:tags');

// 5. Sorted Set - 排行榜
Redis::zAdd('leaderboard', 1000, 'user:123');
Redis::zAdd('leaderboard', 1500, 'user:456');

// 获取排行
$top10 = Redis::zRevRange('leaderboard', 0, 9, true);
缓存Key设计规范
// 良好的Key命名规范
class CacheKey
{
    // 用户相关
    const USER_INFO = 'user:%d';                    // user:123
    const USER_SESSION = 'session:%s';              // session:abc123
    
    // 商品相关
    const PRODUCT_INFO = 'product:%d';              // product:456
    const PRODUCT_LIST = 'product:list:%d:%d';      // product:list:1:20 (分类:页码)
    
    // 订单相关
    const ORDER_INFO = 'order:%s';                  // order:202401010001
    const USER_ORDERS = 'user:%d:orders:%d';        // user:123:orders:1 (用户:页码)
    
    // 计数器
    const PRODUCT_VIEW = 'stat:product:%d:view';    // stat:product:456:view
    const DAILY_SALES = 'stat:sales:%s';            // stat:sales:20240101
    
    // 锁
    const LOCK_ORDER = 'lock:order:%s';             // lock:order:202401010001
    
    /**
     * 生成缓存Key
     */
    public static function make($template, ...$args)
    {
        return sprintf($template, ...$args);
    }
    
    /**
     * 生成带TTL的Key(用于自动过期)
     */
    public static function makeWithTTL($template, $ttl, ...$args)
    {
        $key = sprintf($template, ...$args);
        $timestamp = time() + $ttl;
        return $key . ':ttl:' . $timestamp;
    }
}

// 使用示例
$key = CacheKey::make(CacheKey::PRODUCT_INFO, 123);
Redis::setex($key, 3600, json_encode($product));

3.2 热点数据缓存

热点数据识别
// 统计访问频率,识别热点数据
class HotDataService
{
    /**
     * 记录访问
     */
    public static function recordAccess($type, $id)
    {
        $key = "hotdata:{$type}:" . date('YmdH'); // 按小时统计
        Redis::zIncrBy($key, 1, $id);
        Redis::expire($key, 3600 * 25); // 保留25小时
    }
    
    /**
     * 获取热点数据Top N
     */
    public static function getHotData($type, $limit = 100)
    {
        $key = "hotdata:{$type}:" . date('YmdH');
        return Redis::zRevRange($key, 0, $limit - 1, true);
    }
    
    /**
     * 预热热点数据
     */
    public static function preloadHotData($type = 'product')
    {
        $hotIds = self::getHotData($type, 100);
        
        foreach ($hotIds as $id => $score) {
            $cacheKey = "{$type}:{$id}";
            
            // 检查缓存是否存在
            if (!Redis::exists($cacheKey)) {
                // 从数据库加载
                $data = Db::name($type.'s')->where('id', $id)->find();
                if ($data) {
                    // 热点数据设置更长的过期时间
                    Redis::setex($cacheKey, 7200, json_encode($data));
                }
            }
        }
    }
}

// 商品详情页记录访问
class ProductController
{
    public function detail($id)
    {
        // 记录访问
        HotDataService::recordAccess('product', $id);
        
        // 获取商品信息(优先从缓存)
        $product = ProductService::getProduct($id);
        
        return view('detail', ['product' => $product]);
    }
}

// 定时任务预热(每小时执行)
php think cron:preheat
多级缓存架构
// 本地缓存 + Redis缓存 + 数据库
class MultiLevelCacheService
{
    // 本地缓存(进程内存)
    private static $localCache = [];
    private static $localCacheTTL = [];
    
    /**
     * 获取数据(三级缓存)
     */
    public static function get($key, $loader, $ttl = 3600)
    {
        // 1. 查本地缓存
        if (isset(self::$localCache[$key])) {
            if (time() < self::$localCacheTTL[$key]) {
                return self::$localCache[$key];
            }
            // 本地缓存过期,清除
            unset(self::$localCache[$key]);
            unset(self::$localCacheTTL[$key]);
        }
        
        // 2. 查Redis缓存
        $value = Redis::get($key);
        if ($value !== false) {
            $data = json_decode($value, true);
            // 写入本地缓存(较短的TTL)
            self::setLocal($key, $data, min(60, $ttl));
            return $data;
        }
        
        // 3. 查数据库
        $data = $loader();
        if ($data !== null) {
            // 写入Redis
            Redis::setex($key, $ttl, json_encode($data));
            // 写入本地缓存
            self::setLocal($key, $data, min(60, $ttl));
        }
        
        return $data;
    }
    
    /**
     * 设置本地缓存
     */
    private static function setLocal($key, $value, $ttl)
    {
        self::$localCache[$key] = $value;
        self::$localCacheTTL[$key] = time() + $ttl;
        
        // 限制本地缓存大小
        if (count(self::$localCache) > 1000) {
            // 清理最早的100个
            $keys = array_slice(array_keys(self::$localCache), 0, 100);
            foreach ($keys as $k) {
                unset(self::$localCache[$k]);
                unset(self::$localCacheTTL[$k]);
            }
        }
    }
    
    /**
     * 删除缓存
     */
    public static function delete($key)
    {
        // 删除本地缓存
        unset(self::$localCache[$key]);
        unset(self::$localCacheTTL[$key]);
        
        // 删除Redis缓存
        Redis::del($key);
    }
}

3.3 缓存雪崩、穿透、击穿

缓存雪崩

问题:大量缓存同时过期,请求全部打到数据库

解决方案

// 1. 过期时间加随机值
class CacheService
{
    public static function set($key, $value, $ttl = 3600)
    {
        // 添加随机偏移(-5% ~ +5%)
        $randomOffset = mt_rand(-$ttl * 0.05, $ttl * 0.05);
        $finalTTL = $ttl + $randomOffset;
        
        return Redis::setex($key, $finalTTL, json_encode($value));
    }
}

// 2. 缓存预热
class CacheWarmup
{
    /**
     * 预热关键数据
     */
    public static function warmup()
    {
        // 预热热门商品
        $hotProducts = Db::name('products')
            ->where('is_hot', 1)
            ->limit(100)
            ->select();
        
        foreach ($hotProducts as $product) {
            $key = CacheKey::make(CacheKey::PRODUCT_INFO, $product['id']);
            CacheService::set($key, $product, 7200);
        }
        
        // 预热分类数据
        $categories = Db::name('categories')->select();
        CacheService::set('categories:all', $categories, 86400);
    }
}

// 3. 使用Redis集群(高可用)
// config/redis.php
return [
    'default' => [
        'type' => 'cluster',
        'hosts' => [
            ['host' => '192.168.1.10', 'port' => 6379],
            ['host' => '192.168.1.11', 'port' => 6379],
            ['host' => '192.168.1.12', 'port' => 6379],
        ],
    ]
];
缓存穿透

问题:查询不存在的数据,缓存无法命中,每次都查数据库

解决方案

// 1. 布隆过滤器
class BloomFilter
{
    private $redis;
    private $key = 'bloomfilter:products';
    private $hashFunctions = 3;
    private $size = 10000000; // 1000万bit
    
    public function __construct()
    {
        $this->redis = Redis::connection();
    }
    
    /**
     * 添加元素
     */
    public function add($value)
    {
        for ($i = 0; $i < $this->hashFunctions; $i++) {
            $hash = $this->hash($value, $i);
            $this->redis->setBit($this->key, $hash, 1);
        }
    }
    
    /**
     * 检查元素是否可能存在
     */
    public function mightExist($value)
    {
        for ($i = 0; $i < $this->hashFunctions; $i++) {
            $hash = $this->hash($value, $i);
            if (!$this->redis->getBit($this->key, $hash)) {
                return false; // 一定不存在
            }
        }
        return true; // 可能存在
    }
    
    private function hash($value, $seed)
    {
        return crc32($value . $seed) % $this->size;
    }
}

// 使用布隆过滤器
class ProductService
{
    public static function getProduct($productId)
    {
        $bloomFilter = new BloomFilter();
        
        // 先用布隆过滤器判断
        if (!$bloomFilter->mightExist($productId)) {
            return null; // 一定不存在,直接返回
        }
        
        // 可能存在,继续查询
        $cacheKey = 'product:' . $productId;
        $product = Redis::get($cacheKey);
        
        if ($product === false) {
            $product = Db::name('products')->where('id', $productId)->find();
            if ($product) {
                Redis::setex($cacheKey, 3600, json_encode($product));
            }
        } else {
            $product = json_decode($product, true);
        }
        
        return $product;
    }
}

// 2. 缓存空对象
class ProductService
{
    public static function getProduct($productId)
    {
        $cacheKey = 'product:' . $productId;
        $product = Redis::get($cacheKey);
        
        if ($product !== false) {
            $product = json_decode($product, true);
            // 空对象标识
            if ($product === ['_empty' => true]) {
                return null;
            }
            return $product;
        }
        
        // 查数据库
        $product = Db::name('products')->where('id', $productId)->find();
        
        if ($product) {
            Redis::setex($cacheKey, 3600, json_encode($product));
        } else {
            // 缓存空对象,较短的TTL
            Redis::setex($cacheKey, 60, json_encode(['_empty' => true]));
        }
        
        return $product;
    }
}
缓存击穿

问题:热点数据过期瞬间,大量请求同时查询数据库

解决方案

// 1. 互斥锁(只有一个请求去加载数据)
class ProductService
{
    public static function getProduct($productId)
    {
        $cacheKey = 'product:' . $productId;
        $lockKey = 'lock:product:' . $productId;
        
        // 查缓存
        $product = Redis::get($cacheKey);
        if ($product !== false) {
            return json_decode($product, true);
        }
        
        // 尝试获取锁
        $lockToken = uniqid();
        $locked = Redis::set($lockKey, $lockToken, ['NX', 'EX' => 10]);
        
        if ($locked) {
            try {
                // 获取锁成功,查数据库
                $product = Db::name('products')->where('id', $productId)->find();
                if ($product) {
                    Redis::setex($cacheKey, 3600, json_encode($product));
                }
                return $product;
            } finally {
                // 释放锁(使用Lua脚本保证原子性)
                $script = "
                    if redis.call('get', KEYS[1]) == ARGV[1] then
                        return redis.call('del', KEYS[1])
                    else
                        return 0
                    end
                ";
                Redis::eval($script, [$lockKey, $lockToken], 1);
            }
        } else {
            // 未获取到锁,等待后重试
            usleep(50000); // 等待50ms
            return self::getProduct($productId);
        }
    }
}

// 2. 逻辑过期(热点数据永不过期)
class ProductService
{
    public static function getProduct($productId)
    {
        $cacheKey = 'product:' . $productId;
        $product = Redis::hGetAll($cacheKey);
        
        if (empty($product)) {
            // 缓存不存在,加载数据
            return self::loadAndCache($productId);
        }
        
        // 检查逻辑过期时间
        $expireTime = $product['_expire_time'] ?? 0;
        unset($product['_expire_time']);
        
        if (time() > $expireTime) {
            // 逻辑已过期,异步更新
            $lockKey = 'lock:product:' . $productId;
            $locked = Redis::set($lockKey, 1, ['NX', 'EX' => 10]);
            
            if ($locked) {
                // 获取锁成功,异步刷新缓存
                self::asyncRefreshCache($productId);
            }
        }
        
        // 返回旧数据(避免缓存击穿)
        return $product;
    }
    
    private static function loadAndCache($productId)
    {
        $product = Db::name('products')->where('id', $productId)->find();
        if ($product) {
            $product['_expire_time'] = time() + 3600; // 逻辑过期时间
            Redis::hMSet('product:' . $productId, $product);
        }
        return $product;
    }
    
    private static function asyncRefreshCache($productId)
    {
        // 使用消息队列异步刷新
        Queue::push('RefreshCache', ['type' => 'product', 'id' => $productId]);
    }
}

四、高并发优化

4.1 流量削峰

秒杀系统设计
// 秒杀服务
class SeckillService
{
    /**
     * 秒杀商品预热
     */
    public static function warmup($seckillId)
    {
        $seckill = Db::name('seckill')->where('id', $seckillId)->find();
        if (!$seckill) {
            return false;
        }
        
        $cacheKey = "seckill:{$seckillId}";
        $stockKey = "seckill:{$seckillId}:stock";
        
        // 缓存秒杀信息
        Redis::hMSet($cacheKey, [
            'id' => $seckill['id'],
            'product_id' => $seckill['product_id'],
            'price' => $seckill['price'],
            'start_time' => $seckill['start_time'],
            'end_time' => $seckill['end_time'],
        ]);
        
        // 初始化库存(使用Redis计数器)
        Redis::set($stockKey, $seckill['stock']);
        
        return true;
    }
    
    /**
     * 秒杀抢购
     */
    public static function rush($seckillId, $userId)
    {
        $cacheKey = "seckill:{$seckillId}";
        $stockKey = "seckill:{$seckillId}:stock";
        $userKey = "seckill:{$seckillId}:user:{$userId}";
        
        // 1. 检查是否已抢购
        if (Redis::exists($userKey)) {
            return ['code' => -1, 'msg' => '已经抢购过了'];
        }
        
        // 2. 检查时间
        $seckill = Redis::hGetAll($cacheKey);
        $now = time();
        if ($now < strtotime($seckill['start_time'])) {
            return ['code' => -1, 'msg' => '秒杀未开始'];
        }
        if ($now > strtotime($seckill['end_time'])) {
            return ['code' => -1, 'msg' => '秒杀已结束'];
        }
        
        // 3. 扣减库存(Lua脚本保证原子性)
        $script = "
            local stock = redis.call('get', KEYS[1])
            if tonumber(stock) > 0 then
                redis.call('decr', KEYS[1])
                return 1
            else
                return 0
            end
        ";
        
        $result = Redis::eval($script, [$stockKey], 1);
        
        if ($result == 0) {
            return ['code' => -1, 'msg' => '库存不足'];
        }
        
        // 4. 记录用户抢购
        Redis::setex($userKey, 3600, 1);
        
        // 5. 创建订单(异步)
        Queue::push('CreateSeckillOrder', [
            'seckill_id' => $seckillId,
            'user_id' => $userId,
            'product_id' => $seckill['product_id'],
            'price' => $seckill['price'],
        ]);
        
        return ['code' => 0, 'msg' => '抢购成功'];
    }
}

// 控制器(使用令牌桶限流)
class SeckillController
{
    /**
     * 秒杀接口
     */
    public function rush()
    {
        $seckillId = input('seckill_id');
        $userId = session('user_id');
        
        // 1. 限流(令牌桶)
        if (!$this->rateLimiter($userId)) {
            return json(['code' => -1, 'msg' => '请求过于频繁']);
        }
        
        // 2. 秒杀
        $result = SeckillService::rush($seckillId, $userId);
        
        return json($result);
    }
    
    /**
     * 令牌桶限流
     */
    private function rateLimiter($userId, $capacity = 10, $rate = 1)
    {
        $key = "ratelimit:user:{$userId}";
        $now = microtime(true);
        
        // Lua脚本实现令牌桶
        $script = "
            local key = KEYS[1]
            local capacity = tonumber(ARGV[1])
            local rate = tonumber(ARGV[2])
            local now = tonumber(ARGV[3])
            local requested = 1
            
            local bucket = redis.call('hmget', key, 'tokens', 'last_time')
            local tokens = tonumber(bucket[1])
            local last_time = tonumber(bucket[2])
            
            if tokens == nil then
                tokens = capacity
                last_time = now
            end
            
            local delta = math.max(0, now - last_time)
            tokens = math.min(capacity, tokens + delta * rate)
            
            local allowed = tokens >= requested
            if allowed then
                tokens = tokens - requested
            end
            
            redis.call('hmset', key, 'tokens', tokens, 'last_time', now)
            redis.call('expire', key, capacity / rate + 1)
            
            return allowed and 1 or 0
        ";
        
        $allowed = Redis::eval($script, [$key, $capacity, $rate, $now], 1);
        
        return $allowed == 1;
    }
}
Nginx限流
# 限流配置
http {
    # 限制请求速率(每秒10个请求)
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
    
    # 限制连接数
    limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
    
    server {
        listen 80;
        server_name api.example.com;
        
        location /api/seckill {
            # 应用限流规则
            limit_req zone=api_limit burst=20 nodelay;
            limit_conn conn_limit 10;
            
            # 限流返回状态码
            limit_req_status 429;
            limit_conn_status 429;
            
            proxy_pass http://backend;
        }
    }
}

4.2 消息队列

Redis队列(轻量级)
// 队列服务
class QueueService
{
    /**
     * 推送任务
     */
    public static function push($queue, $data)
    {
        $key = "queue:{$queue}";
        $job = [
            'id' => uniqid(),
            'data' => $data,
            'attempts' => 0,
            'created_at' => time(),
        ];
        
        Redis::rPush($key, json_encode($job));
        
        return $job['id'];
    }
    
    /**
     * 消费任务
     */
    public static function consume($queue, $handler, $timeout = 0)
    {
        $key = "queue:{$queue}";
        
        while (true) {
            // 阻塞弹出任务
            $result = Redis::blPop($key, $timeout);
            
            if (!$result) {
                break;
            }
            
            $job = json_decode($result[1], true);
            
            try {
                // 执行任务
                call_user_func($handler, $job['data']);
            } catch (\Exception $e) {
                // 失败重试
                $job['attempts']++;
                if ($job['attempts'] < 3) {
                    self::push($queue, $job['data']);
                } else {
                    // 记录失败任务
                    self::pushFailed($queue, $job, $e->getMessage());
                }
            }
        }
    }
    
    /**
     * 记录失败任务
     */
    private static function pushFailed($queue, $job, $error)
    {
        $key = "queue:{$queue}:failed";
        $failed = [
            'job' => $job,
            'error' => $error,
            'failed_at' => date('Y-m-d H:i:s'),
        ];
        
        Redis::rPush($key, json_encode($failed));
    }
}

// 定义任务处理器
class OrderQueueHandler
{
    public static function handle($data)
    {
        $orderId = $data['order_id'];
        
        // 处理订单
        OrderService::processOrder($orderId);
    }
}

// 推送任务
QueueService::push('orders', ['order_id' => 12345]);

// 消费任务(命令行脚本)
QueueService::consume('orders', [OrderQueueHandler::class, 'handle']);
RabbitMQ(生产级)
// 需要安装 php-amqplib
// composer require php-amqplib/php-amqplib

use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;

// 生产者
class RabbitMQProducer
{
    private $connection;
    private $channel;
    
    public function __construct()
    {
        $this->connection = new AMQPStreamConnection(
            'localhost', 5672, 'guest', 'guest'
        );
        $this->channel = $this->connection->channel();
    }
    
    /**
     * 发送消息
     */
    public function publish($queue, $data, $exchange = '', $routingKey = '')
    {
        // 声明队列
        $this->channel->queue_declare(
            $queue,     // queue name
            false,      // passive
            true,       // durable
            false,      // exclusive
            false       // auto_delete
        );
        
        // 创建消息
        $message = new AMQPMessage(
            json_encode($data),
            ['delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT]
        );
        
        // 发送消息
        $this->channel->basic_publish(
            $message,
            $exchange,
            $routingKey ?: $queue
        );
    }
    
    public function __destruct()
    {
        $this->channel->close();
        $this->connection->close();
    }
}

// 消费者
class RabbitMQConsumer
{
    private $connection;
    private $channel;
    
    public function __construct()
    {
        $this->connection = new AMQPStreamConnection(
            'localhost', 5672, 'guest', 'guest'
        );
        $this->channel = $this->connection->channel();
    }
    
    /**
     * 消费消息
     */
    public function consume($queue, $callback)
    {
        // 声明队列
        $this->channel->queue_declare(
            $queue, false, true, false, false
        );
        
        // 设置预取数量
        $this->channel->basic_qos(null, 1, null);
        
        // 消费消息
        $this->channel->basic_consume(
            $queue,
            '',
            false,
            false,
            false,
            false,
            function($msg) use ($callback) {
                try {
                    $data = json_decode($msg->body, true);
                    call_user_func($callback, $data);
                    
                    // 确认消息
                    $msg->ack();
                } catch (\Exception $e) {
                    // 拒绝消息,重新入队
                    $msg->nack(true);
                }
            }
        );
        
        // 等待消息
        while ($this->channel->is_consuming()) {
            $this->channel->wait();
        }
    }
    
    public function __destruct()
    {
        $this->channel->close();
        $this->connection->close();
    }
}

// 使用示例
// 发送消息
$producer = new RabbitMQProducer();
$producer->publish('order_queue', [
    'order_id' => 12345,
    'user_id' => 678,
    'amount' => 99.99,
]);

// 消费消息
$consumer = new RabbitMQConsumer();
$consumer->consume('order_queue', function($data) {
    echo "处理订单: {$data['order_id']}\n";
    OrderService::processOrder($data['order_id']);
});

4.3 负载均衡

Nginx负载均衡
# 上游服务器配置
upstream backend {
    # 负载均衡策略
    
    # 1. 轮询(默认)
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
    
    # 2. 加权轮询
    # server 192.168.1.10:8080 weight=3;
    # server 192.168.1.11:8080 weight=2;
    # server 192.168.1.12:8080 weight=1;
    
    # 3. IP哈希(同一IP固定到同一服务器)
    # ip_hash;
    
    # 4. 最少连接
    # least_conn;
    
    # 健康检查
    server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
    server 192.168.1.12:8080 max_fails=3 fail_timeout=30s;
    
    # 备份服务器
    server 192.168.1.20:8080 backup;
    
    # 长连接
    keepalive 32;
}

server {
    listen 80;
    server_name www.example.com;
    
    location / {
        proxy_pass http://backend;
        
        # 代理设置
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        # 超时设置
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
        
        # 缓冲设置
        proxy_buffering on;
        proxy_buffer_size 4k;
        proxy_buffers 8 4k;
        
        # 长连接
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}

五、服务端性能优化

5.1 代码层面优化

PHP性能优化
1. OPcache配置
; php.ini
[opcache]
; 开启OPcache
opcache.enable=1
opcache.enable_cli=1

; 内存配置
opcache.memory_consumption=256      ; 内存大小(MB)
opcache.interned_strings_buffer=16  ; 字符串缓存(MB)
opcache.max_accelerated_files=10000 ; 最大缓存文件数

; 验证配置
opcache.revalidate_freq=60          ; 检查更新频率(秒)
opcache.validate_timestamps=1        ; 开发环境设为1,生产环境可设为0

; 优化配置
opcache.fast_shutdown=1
opcache.enable_file_override=1
opcache.optimization_level=0x7FFEBFFF
2. 避免重复计算
// ❌ 低效代码
for ($i = 0; $i < count($array); $i++) {  // 每次循环都调用count()
    echo $array[$i];
}

// ✅ 优化代码
$count = count($array);
for ($i = 0; $i < $count; $i++) {
    echo $array[$i];
}

// ✅ 更好的方式
foreach ($array as $value) {
    echo $value;
}
3. 使用生成器减少内存
// ❌ 大数组占用大量内存
function getUsers()
{
    $users = [];
    for ($i = 1; $i <= 100000; $i++) {
        $users[] = Db::name('users')->where('id', $i)->find();
    }
    return $users;
}

$users = getUsers(); // 占用大量内存

// ✅ 使用生成器
function getUsersGenerator()
{
    for ($i = 1; $i <= 100000; $i++) {
        yield Db::name('users')->where('id', $i)->find();
    }
}

foreach (getUsersGenerator() as $user) {
    // 逐个处理,内存占用小
    processUser($user);
}
4. 批量操作
// ❌ 逐条插入
foreach ($data as $item) {
    Db::name('users')->insert($item);
}

// ✅ 批量插入
Db::name('users')->insertAll($data);

// ❌ 逐条查询
$users = [];
foreach ($userIds as $id) {
    $users[] = Db::name('users')->where('id', $id)->find();
}

// ✅ 批量查询
$users = Db::name('users')->whereIn('id', $userIds)->select();

5.2 架构层面优化

微服务架构
// 服务拆分示例

// 用户服务
class UserService
{
    public static function getUserInfo($userId)
    {
        // RPC调用用户服务
        return self::rpcCall('user-service', 'getUserInfo', ['user_id' => $userId]);
    }
}

// 订单服务
class OrderService
{
    public static function createOrder($data)
    {
        // RPC调用订单服务
        return self::rpcCall('order-service', 'createOrder', $data);
    }
}

// 商品服务
class ProductService
{
    public static function getProduct($productId)
    {
        // RPC调用商品服务
        return self::rpcCall('product-service', 'getProduct', ['product_id' => $productId]);
    }
}

// RPC客户端(基于HTTP)
class RpcClient
{
    public static function call($service, $method, $params)
    {
        $serviceUrl = config('services.' . $service);
        
        $response = file_get_contents($serviceUrl . '/rpc', false, stream_context_create([
            'http' => [
                'method' => 'POST',
                'header' => 'Content-Type: application/json',
                'content' => json_encode([
                    'method' => $method,
                    'params' => $params,
                    'id' => uniqid(),
                ]),
                'timeout' => 5,
            ]
        ]));
        
        $result = json_decode($response, true);
        
        if (isset($result['error'])) {
            throw new \Exception($result['error']['message']);
        }
        
        return $result['result'];
    }
}
异步处理
// 使用Swoole实现异步

use Swoole\Coroutine;
use Swoole\Runtime;

// 开启协程
Runtime::enableCoroutine();

// 异步并发请求
Coroutine\run(function() {
    $wg = new Coroutine\WaitGroup();
    
    // 并发查询用户、订单、商品
    $user = null;
    $orders = null;
    $products = null;
    
    // 协程1:查询用户
    $wg->add();
    Coroutine::create(function() use (&$user, $wg) {
        $user = Db::name('users')->where('id', 123)->find();
        $wg->done();
    });
    
    // 协程2:查询订单
    $wg->add();
    Coroutine::create(function() use (&$orders, $wg) {
        $orders = Db::name('orders')->where('user_id', 123)->select();
        $wg->done();
    });
    
    // 协程3:查询商品
    $wg->add();
    Coroutine::create(function() use (&$products, $wg) {
        $products = Db::name('products')->limit(10)->select();
        $wg->done();
    });
    
    // 等待所有协程完成
    $wg->wait();
    
    // 组合数据
    return [
        'user' => $user,
        'orders' => $orders,
        'products' => $products,
    ];
});

六、监控与分析

性能监控

应用性能监控(APM)
// 自定义性能监控
class PerformanceMonitor
{
    private static $timers = [];
    
    /**
     * 开始计时
     */
    public static function start($name)
    {
        self::$timers[$name] = [
            'start' => microtime(true),
            'memory_start' => memory_get_usage(),
        ];
    }
    
    /**
     * 结束计时
     */
    public static function end($name)
    {
        if (!isset(self::$timers[$name])) {
            return;
        }
        
        $timer = self::$timers[$name];
        $duration = microtime(true) - $timer['start'];
        $memory = memory_get_usage() - $timer['memory_start'];
        
        // 记录日志
        Log::info("Performance: {$name}", [
            'duration' => round($duration * 1000, 2) . 'ms',
            'memory' => round($memory / 1024, 2) . 'KB',
        ]);
        
        // 慢查询告警
        if ($duration > 1) {
            self::alert($name, $duration);
        }
        
        unset(self::$timers[$name]);
    }
    
    /**
     * 告警
     */
    private static function alert($name, $duration)
    {
        // 发送告警(邮件、短信、钉钉等)
        $message = "慢查询告警: {$name} 耗时 " . round($duration, 3) . "秒";
        // ... 发送告警
    }
}

// 使用示例
PerformanceMonitor::start('query_orders');
$orders = Db::name('orders')->where('user_id', 123)->select();
PerformanceMonitor::end('query_orders');
MySQL慢查询监控
-- 开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;  -- 超过1秒的查询
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';

-- 分析慢查询日志
-- mysqldumpslow -s t -t 10 /var/log/mysql/slow.log

-- 查看慢查询统计
SELECT * FROM mysql.slow_log ORDER BY query_time DESC LIMIT 10;
Redis监控
# 实时监控Redis命令
redis-cli monitor

# 查看慢查询日志
redis-cli slowlog get 10

# 查看内存使用
redis-cli info memory

# 查看连接数
redis-cli info clients

# 查看命中率
redis-cli info stats

七、实战案例

案例1:电商首页优化

优化前问题

  • 首页加载时间5秒
  • 数据库查询30+次
  • 未使用缓存

优化方案

class IndexController
{
    /**
     * 首页优化后
     */
    public function index()
    {
        $cacheKey = 'homepage:data';
        
        // 1. 使用页面缓存(5分钟)
        $data = Redis::get($cacheKey);
        if ($data !== false) {
            return view('index', json_decode($data, true));
        }
        
        // 2. 并发查询多个数据源
        $data = $this->getConcurrentData();
        
        // 3. 缓存结果
        Redis::setex($cacheKey, 300, json_encode($data));
        
        return view('index', $data);
    }
    
    /**
     * 并发查询数据
     */
    private function getConcurrentData()
    {
        // 使用协程并发查询
        $banners = $this->getCachedData('banners', function() {
            return Db::name('banners')->where('status', 1)->limit(5)->select();
        });
        
        $hotProducts = $this->getCachedData('hot_products', function() {
            return Db::name('products')->where('is_hot', 1)->limit(10)->select();
        });
        
        $categories = $this->getCachedData('categories', function() {
            return Db::name('categories')->where('level', 1)->select();
        });
        
        return [
            'banners' => $banners,
            'hot_products' => $hotProducts,
            'categories' => $categories,
        ];
    }
    
    /**
     * 获取缓存数据
     */
    private function getCachedData($key, $loader, $ttl = 600)
    {
        $cacheKey = 'homepage:' . $key;
        $data = Redis::get($cacheKey);
        
        if ($data === false) {
            $data = $loader();
            Redis::setex($cacheKey, $ttl, json_encode($data));
        } else {
            $data = json_decode($data, true);
        }
        
        return $data;
    }
}

优化结果

  • 首页加载时间:5秒 → 0.5秒(提升90%)
  • 数据库查询:30次 → 0次(缓存命中)
  • 并发能力:100 QPS → 10000 QPS

案例2:订单列表分页优化

优化前问题

  • 深度分页慢(第1000页需要5秒)
  • 使用 LIMIT 100000, 20

优化方案

class OrderController
{
    /**
     * 订单列表(游标分页)
     */
    public function list()
    {
        $userId = session('user_id');
        $lastId = input('last_id', 0);  // 上一页最后一条记录的ID
        $pageSize = 20;
        
        // 使用主键索引进行分页
        $orders = Db::name('orders')
            ->where('user_id', $userId)
            ->where('id', '>', $lastId)  // 游标条件
            ->order('id', 'asc')
            ->limit($pageSize + 1)  // 多查一条,判断是否有下一页
            ->select();
        
        $hasMore = count($orders) > $pageSize;
        if ($hasMore) {
            array_pop($orders);  // 移除多查的一条
        }
        
        return json([
            'code' => 0,
            'data' => $orders,
            'has_more' => $hasMore,
            'last_id' => $hasMore ? end($orders)['id'] : 0,
        ]);
    }
}

优化结果

  • 查询时间:5秒 → 0.01秒
  • 无论翻到第几页,性能都稳定

八、性能优化检查清单

前端优化

  • 静态资源使用CDN
  • 图片压缩和懒加载
  • CSS/JS压缩和合并
  • 开启Gzip压缩
  • 使用浏览器缓存
  • 减少HTTP请求
  • 使用异步加载
  • 代码分割

数据库优化

  • 为常用查询字段添加索引
  • 避免SELECT *
  • 使用EXPLAIN分析查询
  • 批量操作代替单条操作
  • 读写分离
  • 分库分表(大表)
  • 定期归档历史数据
  • 优化JOIN查询

缓存优化

  • 热点数据使用Redis缓存
  • 合理设置缓存过期时间
  • 防止缓存雪崩、穿透、击穿
  • 使用多级缓存
  • 定期预热缓存
  • 监控缓存命中率

代码优化

  • 开启OPcache
  • 避免重复计算
  • 使用生成器处理大数据
  • 批量操作
  • 异步处理耗时任务
  • 使用连接池
  • 代码规范和最佳实践

架构优化

  • 负载均衡
  • 消息队列削峰
  • 服务拆分(微服务)
  • 限流和熔断
  • 监控和告警
  • 自动扩缩容

九、参考资源

工具推荐

  • 性能测试: Apache Bench (ab), JMeter, Locust
  • 监控工具: Prometheus, Grafana, Zabbix
  • APM工具: Skywalking, Pinpoint, Elastic APM
  • 压测工具: wrk, siege, Gatling
  • 分析工具: Xdebug, Blackfire, New Relic

文档链接


更新日期

最后更新:2025-10-30