Web性能优化完全指南
目录
一、前端性能优化
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);
}
})
);
})
);
});
缓存策略总结
| 资源类型 | 缓存策略 | 过期时间 |
|---|---|---|
| HTML | no-cache | 实时 |
| CSS/JS | public, max-age | 30天(带版本号) |
| 图片 | public, immutable | 1年 |
| 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