我来详细讲解如何针对 PHP、MySQL 和 Redis 进行 QPS 优化,从配置到架构层面全方位提升性能。
整体优化思路
高QPS = 低延迟 × 高并发优化的核心:减少每个请求的处理时间,同时提高系统处理并发的能力
一、PHP 配置与优化
1. PHP-FPM 进程管理优化
; php-fpm.confpm = dynamic ; 动态进程管理pm.max_children = 200 ; 根据内存计算:总内存/单个进程内存pm.start_servers = 20pm.min_spare_servers = 10pm.max_spare_servers = 30pm.max_requests = 1000 ; 防止内存泄漏,处理一定请求后重启pm.process_idle_timeout = 10s
计算 max_children:
- 每个 PHP 进程约 30-50MB
- 8GB 内存服务器:
(8000MB × 0.8) / 40 ≈ 160个进程 - 设置
pm.max_children = 160
2. OPCache 优化
; php.iniopcache.enable=1opcache.memory_consumption=256 ; 缓存大小,建议128-512MBopcache.interned_strings_buffer=16opcache.max_accelerated_files=10000opcache.validate_timestamps=0 ; 生产环境设置为0,用脚本手动清除opcache.revalidate_freq=0opcache.save_comments=1opcache.consistency_checks=0opcache.enable_cli=0
3. 脚本执行优化
max_execution_time = 30 ; 脚本执行时间memory_limit = 128M ; 根据实际需求调整request_terminate_timeout = 30 ; FPM执行超时
4. PHP 代码级优化
// 1. 避免重复连接数据库/Redisclass Database {private static $instance = null;public static function getConnection() {if (self::$instance === null) {self::$instance = new PDO(...);}return self::$instance;}}// 2. 批量操作代替循环操作// ❌ 慢:循环插入foreach ($data as $row) {$stmt->execute($row);}// ✅ 快:批量插入$stmt = $pdo->prepare("INSERT INTO table VALUES (?, ?)");foreach ($data as $row) {$stmt->execute($row);}// 或者使用批量SQLINSERT INTO table (a, b) VALUES (1,2), (3,4), (5,6);// 3. 使用连接池(Swoole、Workerman等)
5. 使用 PHP 扩展/框架优化
- Swoole/Workerman:异步非阻塞,QPS 提升 10-100 倍
- PHP 8+:JIT 编译,性能提升 20-30%
- 框架选择:
- Laravel:开发快,但性能较低(1000-2000 QPS)
- Slim/Lumen:轻量级,性能高(5000-10000 QPS)
- 原生 PHP:性能最高,但开发慢
二、MySQL 配置与优化
1. 核心配置优化(my.cnf)
[mysqld]# 连接配置max_connections = 1000 # 最大连接数thread_cache_size = 100 # 线程缓存wait_timeout = 300 # 非交互连接超时interactive_timeout = 300# InnoDB 配置innodb_buffer_pool_size = 6G # 关键!设置为系统内存的 70-80%innodb_buffer_pool_instances = 8 # 多个缓冲池实例innodb_log_file_size = 1G # 日志文件大小innodb_flush_log_at_trx_commit = 2 # 性能与安全的平衡innodb_flush_method = O_DIRECTinnodb_file_per_table = 1# 查询缓存(MySQL 8.0 已移除,5.7 可配置)query_cache_type = 0 # 高并发下建议关闭查询缓存query_cache_size = 0# 临时表tmp_table_size = 64Mmax_heap_table_size = 64M# 慢查询日志slow_query_log = 1long_query_time = 2slow_query_log_file = /var/log/mysql-slow.log
2. 索引优化
-- 1. 添加合适索引ALTER TABLE `users` ADD INDEX idx_email_status (`email`, `status`);ALTER TABLE `orders` ADD INDEX idx_user_created (`user_id`, `created_at`);-- 2. 覆盖索引,避免回表EXPLAIN SELECT id, name FROM users WHERE email = 'test@example.com';-- 如果索引包含 (email, name, id),则直接从索引获取数据-- 3. 避免索引失效的情况-- ❌ 不要在索引列上使用函数SELECT * FROM users WHERE DATE(created_at) = '2024-01-21';-- ✅ 使用范围查询SELECT * FROM users WHERE created_at >= '2024-01-21' AND created_at < '2024-01-22';-- ❌ 避免前导通配符SELECT * FROM users WHERE name LIKE '%张%';-- ✅ 如果必须,考虑全文索引
3. 查询优化
-- 1. 避免 SELECT *,只取需要字段SELECT id, name FROM users WHERE status = 1; -- ✅SELECT * FROM users WHERE status = 1; -- ❌-- 2. 分页优化-- ❌ 传统分页,偏移量大时慢SELECT * FROM orders ORDER BY id LIMIT 1000000, 20;-- ✅ 使用id范围查询SELECT * FROM orders WHERE id > 1000000 ORDER BY id LIMIT 20;-- 3. 批量操作-- ❌INSERT INTO log (msg) VALUES ('a');INSERT INTO log (msg) VALUES ('b');-- ✅INSERT INTO log (msg) VALUES ('a'), ('b');-- 4. 使用 EXPLAIN 分析查询EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'test@example.com';
4. 架构优化
-- 1. 读写分离-- 主库:写操作-- 从库:读操作(可配置多个)-- 2. 分表分库-- 水平分表:按时间或ID范围CREATE TABLE orders_202401 LIKE orders;CREATE TABLE orders_202402 LIKE orders;-- 3. 使用连接池-- 客户端使用连接池,避免频繁创建连接
三、Redis 配置与优化
1. Redis 核心配置(redis.conf)
# 内存设置maxmemory 4gb # 最大内存,建议系统内存的50-70%maxmemory-policy allkeys-lru # 内存淘汰策略maxmemory-samples 5 # LRU采样精度# 持久化策略# 方案A:RDB(性能好,可能丢数据)save 900 1save 300 10save 60 10000rdbcompression yesrdbchecksum yes# 方案B:AOF(数据安全,性能稍差)appendonly yesappendfsync everysec # 性能与安全的平衡no-appendfsync-on-rewrite noauto-aof-rewrite-percentage 100auto-aof-rewrite-min-size 64mb# 网络优化tcp-keepalive 300timeout 0 # 连接不超时tcp-backlog 511# 客户端连接maxclients 10000 # 最大客户端数
2. 数据结构与使用优化
// 1. 使用 Pipeline 减少网络往返$redis = new Redis();$redis->connect('127.0.0.1', 6379);$redis->pipeline(); // 开始管道for ($i = 0; $i < 1000; $i++) {$redis->set("key:$i", "value:$i");}$redis->exec(); // 一次性执行// 2. 批量操作// ❌foreach ($userIds as $id) {$user = $redis->get("user:$id");}// ✅$redis->mget(array_map(fn($id) => "user:$id", $userIds));// 3. 选择合适的数据结构// 计数器:String$redis->incr('page_views');// 去重集合:Set$redis->sadd('online_users', $userId);// 排行榜:Sorted Set$redis->zadd('leaderboard', $score, $userId);// 消息队列:List$redis->lpush('queue', $message);// 4. 避免大Key// ❌ 单个Key存储大量数据$redis->set('big_key', json_encode($hugeArray));// ✅ 分片存储foreach (array_chunk($hugeArray, 1000) as $i => $chunk) {$redis->set("big_key:$i", json_encode($chunk));}
3. 连接池与持久连接
// 使用连接池(推荐使用扩展如 phpredis 或 predis)class RedisPool {private $pool;private $config;public function __construct($size = 10) {$this->pool = new SplQueue();for ($i = 0; $i < $size; $i++) {$redis = new Redis();$redis->connect('127.0.0.1', 6379, 2.5); // 2.5秒超时$redis->select(0);$this->pool->enqueue($redis);}}public function get() {return $this->pool->dequeue();}public function put($redis) {$this->pool->enqueue($redis);}}
四、架构级优化
1. 缓存策略设计
// 1. 多级缓存架构class CacheManager {private $localCache = []; // 本地内存缓存private $redis;private $db;public function getUser($id) {// 第一级:本地缓存if (isset($this->localCache["user:$id"])) {return $this->localCache["user:$id"];}// 第二级:Redis$user = $this->redis->get("user:$id");if ($user) {$this->localCache["user:$id"] = json_decode($user, true);return $this->localCache["user:$id"];}// 第三级:数据库$user = $this->db->query("SELECT * FROM users WHERE id = ?", [$id]);if ($user) {$this->redis->setex("user:$id", 300, json_encode($user));$this->localCache["user:$id"] = $user;}return $user;}}// 2. 缓存穿透解决方案public function getUserWithBloomFilter($id) {// 先查布隆过滤器if (!$this->bloomFilter->exists($id)) {return null; // 肯定不存在}// 正常缓存逻辑return $this->getUser($id);}// 3. 缓存雪崩解决方案public function getProduct($id) {$key = "product:$id";$product = $this->redis->get($key);if (!$product) {// 使用互斥锁,防止大量请求同时击穿缓存$lockKey = "lock:product:$id";if ($this->redis->setnx($lockKey, 1)) {$this->redis->expire($lockKey, 10);// 从数据库获取$product = $this->db->getProduct($id);if ($product) {// 设置随机过期时间,防止同时失效$ttl = 300 + rand(0, 60); // 300-360秒$this->redis->setex($key, $ttl, json_encode($product));}$this->redis->del($lockKey);} else {// 等待其他线程加载缓存usleep(100000); // 100msreturn $this->getProduct($id);}}return json_decode($product, true);}
2. 读写分离与负载均衡
架构示意图:用户请求 → Nginx (负载均衡)↓[PHP服务器集群]↓[Redis集群] ←→ [MySQL主库]↓ ↓[Redis从库] ←→ [MySQL从库集群]
3. 异步处理
// 使用队列处理耗时操作class OrderService {public function createOrder($data) {// 1. 快速写入数据库$orderId = $this->db->createOrder($data);// 2. 异步处理后续逻辑$this->queue->push('order_created', ['order_id' => $orderId,'data' => $data]);return $orderId;}}// Worker处理队列class OrderWorker {public function process($job) {$data = $job->getData();// 发送邮件$this->sendEmail($data);// 更新统计数据$this->updateStatistics($data);// 记录日志$this->logOrder($data);$job->delete();}}
五、监控与调试
1. 监控指标
# 查看MySQL QPSmysql> SHOW GLOBAL STATUS LIKE 'Queries';mysql> SHOW GLOBAL STATUS LIKE 'Uptime';# 计算QPS公式# QPS = (当前Queries - 上次Queries) / 时间间隔# 查看Redis QPSredis-cli info stats | grep instantaneous_ops_per_sec# 查看PHP-FPM状态curl http://localhost/status?full# 监控脚本示例#!/bin/bash# 监控MySQL QPSmysql -e "SHOW GLOBAL STATUS" | grep -E "(Queries|Threads_running|Innodb_rows_read)"
2. 压力测试工具
# 使用 wrk 进行压力测试wrk -t12 -c400 -d30s --latency http://localhost/api/users# 使用 ab (Apache Bench)ab -n 10000 -c 100 http://localhost/api/test# 使用 siegesiege -c 100 -t 1M http://localhost/api/test
六、优化案例:从 1000 QPS 到 50000 QPS
优化前(~1000 QPS):
- PHP-FPM:pm.max_children = 50
- MySQL:默认配置,无索引
- Redis:未使用
- 架构:单机,同步处理
优化步骤:
第一轮:基础优化(达到 3000 QPS)
- PHP-FPM 优化:调整进程数,启用 OPCache
- MySQL 添加必要索引
- 引入 Redis 缓存热点数据
第二轮:架构优化(达到 10000 QPS)
- 实现读写分离
- 使用连接池
- 静态资源 CDN 加速
第三轮:深度优化(达到 30000 QPS)
- 使用 Swoole 替换 PHP-FPM
- MySQL 分库分表
- Redis 集群,Pipeline 批量操作
第四轮:极致优化(达到 50000+ QPS)
- 多级缓存(本地内存 + Redis)
- 异步化处理
- 硬件升级(SSD、更多内存)
七、配置检查清单
PHP 检查项:
- PHP 版本 ≥ 7.4(建议 8.1+)
- OPCache 已启用并配置合理内存
- PHP-FPM 进程数根据内存设置
- 关闭不必要的 PHP 扩展
- 设置合理的执行超时时间
MySQL 检查项:
-
innodb_buffer_pool_size为内存的 70-80% - 索引覆盖了主要查询条件
- 慢查询日志已开启
- 连接数设置合理
- 定期执行
OPTIMIZE TABLE和ANALYZE TABLE
Redis 检查项:
- 内存设置合理,开启淘汰策略
- 持久化策略符合业务需求
- 避免大 Key
- 使用 Pipeline 和批量操作
- 连接池配置
重要提醒
- 测试至上:任何配置修改都要在测试环境验证
- 监控先行:优化前建立基线,优化后对比效果
- 逐步进行:一次只修改一个配置,观察效果
- 因地制宜:根据实际业务场景调整,没有万能配置
- 容量规划:预估业务增长,提前规划扩容
记住:优化是一个持续的过程,而不是一次性的任务。随着业务发展,需要不断监控、分析和调整。
