我来详细讲解如何针对 PHP、MySQL 和 Redis 进行 QPS 优化,从配置到架构层面全方位提升性能。

整体优化思路

  1. QPS = 低延迟 × 高并发
  2. 优化的核心:减少每个请求的处理时间,同时提高系统处理并发的能力

一、PHP 配置与优化

1. PHP-FPM 进程管理优化

  1. ; php-fpm.conf
  2. pm = dynamic ; 动态进程管理
  3. pm.max_children = 200 ; 根据内存计算:总内存/单个进程内存
  4. pm.start_servers = 20
  5. pm.min_spare_servers = 10
  6. pm.max_spare_servers = 30
  7. pm.max_requests = 1000 ; 防止内存泄漏,处理一定请求后重启
  8. pm.process_idle_timeout = 10s

计算 max_children

  • 每个 PHP 进程约 30-50MB
  • 8GB 内存服务器:(8000MB × 0.8) / 40 ≈ 160 个进程
  • 设置 pm.max_children = 160

2. OPCache 优化

  1. ; php.ini
  2. opcache.enable=1
  3. opcache.memory_consumption=256 ; 缓存大小,建议128-512MB
  4. opcache.interned_strings_buffer=16
  5. opcache.max_accelerated_files=10000
  6. opcache.validate_timestamps=0 ; 生产环境设置为0,用脚本手动清除
  7. opcache.revalidate_freq=0
  8. opcache.save_comments=1
  9. opcache.consistency_checks=0
  10. opcache.enable_cli=0

3. 脚本执行优化

  1. max_execution_time = 30 ; 脚本执行时间
  2. memory_limit = 128M ; 根据实际需求调整
  3. request_terminate_timeout = 30 ; FPM执行超时

4. PHP 代码级优化

  1. // 1. 避免重复连接数据库/Redis
  2. class Database {
  3. private static $instance = null;
  4. public static function getConnection() {
  5. if (self::$instance === null) {
  6. self::$instance = new PDO(...);
  7. }
  8. return self::$instance;
  9. }
  10. }
  11. // 2. 批量操作代替循环操作
  12. // ❌ 慢:循环插入
  13. foreach ($data as $row) {
  14. $stmt->execute($row);
  15. }
  16. // ✅ 快:批量插入
  17. $stmt = $pdo->prepare("INSERT INTO table VALUES (?, ?)");
  18. foreach ($data as $row) {
  19. $stmt->execute($row);
  20. }
  21. // 或者使用批量SQL
  22. INSERT INTO table (a, b) VALUES (1,2), (3,4), (5,6);
  23. // 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)

  1. [mysqld]
  2. # 连接配置
  3. max_connections = 1000 # 最大连接数
  4. thread_cache_size = 100 # 线程缓存
  5. wait_timeout = 300 # 非交互连接超时
  6. interactive_timeout = 300
  7. # InnoDB 配置
  8. innodb_buffer_pool_size = 6G # 关键!设置为系统内存的 70-80%
  9. innodb_buffer_pool_instances = 8 # 多个缓冲池实例
  10. innodb_log_file_size = 1G # 日志文件大小
  11. innodb_flush_log_at_trx_commit = 2 # 性能与安全的平衡
  12. innodb_flush_method = O_DIRECT
  13. innodb_file_per_table = 1
  14. # 查询缓存(MySQL 8.0 已移除,5.7 可配置)
  15. query_cache_type = 0 # 高并发下建议关闭查询缓存
  16. query_cache_size = 0
  17. # 临时表
  18. tmp_table_size = 64M
  19. max_heap_table_size = 64M
  20. # 慢查询日志
  21. slow_query_log = 1
  22. long_query_time = 2
  23. slow_query_log_file = /var/log/mysql-slow.log

2. 索引优化

  1. -- 1. 添加合适索引
  2. ALTER TABLE `users` ADD INDEX idx_email_status (`email`, `status`);
  3. ALTER TABLE `orders` ADD INDEX idx_user_created (`user_id`, `created_at`);
  4. -- 2. 覆盖索引,避免回表
  5. EXPLAIN SELECT id, name FROM users WHERE email = 'test@example.com';
  6. -- 如果索引包含 (email, name, id),则直接从索引获取数据
  7. -- 3. 避免索引失效的情况
  8. -- 不要在索引列上使用函数
  9. SELECT * FROM users WHERE DATE(created_at) = '2024-01-21';
  10. -- 使用范围查询
  11. SELECT * FROM users WHERE created_at >= '2024-01-21' AND created_at < '2024-01-22';
  12. -- 避免前导通配符
  13. SELECT * FROM users WHERE name LIKE '%张%';
  14. -- 如果必须,考虑全文索引

3. 查询优化

  1. -- 1. 避免 SELECT *,只取需要字段
  2. SELECT id, name FROM users WHERE status = 1; --
  3. SELECT * FROM users WHERE status = 1; --
  4. -- 2. 分页优化
  5. -- 传统分页,偏移量大时慢
  6. SELECT * FROM orders ORDER BY id LIMIT 1000000, 20;
  7. -- 使用id范围查询
  8. SELECT * FROM orders WHERE id > 1000000 ORDER BY id LIMIT 20;
  9. -- 3. 批量操作
  10. --
  11. INSERT INTO log (msg) VALUES ('a');
  12. INSERT INTO log (msg) VALUES ('b');
  13. --
  14. INSERT INTO log (msg) VALUES ('a'), ('b');
  15. -- 4. 使用 EXPLAIN 分析查询
  16. EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'test@example.com';

4. 架构优化

  1. -- 1. 读写分离
  2. -- 主库:写操作
  3. -- 从库:读操作(可配置多个)
  4. -- 2. 分表分库
  5. -- 水平分表:按时间或ID范围
  6. CREATE TABLE orders_202401 LIKE orders;
  7. CREATE TABLE orders_202402 LIKE orders;
  8. -- 3. 使用连接池
  9. -- 客户端使用连接池,避免频繁创建连接

三、Redis 配置与优化

1. Redis 核心配置(redis.conf)

  1. # 内存设置
  2. maxmemory 4gb # 最大内存,建议系统内存的50-70%
  3. maxmemory-policy allkeys-lru # 内存淘汰策略
  4. maxmemory-samples 5 # LRU采样精度
  5. # 持久化策略
  6. # 方案A:RDB(性能好,可能丢数据)
  7. save 900 1
  8. save 300 10
  9. save 60 10000
  10. rdbcompression yes
  11. rdbchecksum yes
  12. # 方案B:AOF(数据安全,性能稍差)
  13. appendonly yes
  14. appendfsync everysec # 性能与安全的平衡
  15. no-appendfsync-on-rewrite no
  16. auto-aof-rewrite-percentage 100
  17. auto-aof-rewrite-min-size 64mb
  18. # 网络优化
  19. tcp-keepalive 300
  20. timeout 0 # 连接不超时
  21. tcp-backlog 511
  22. # 客户端连接
  23. maxclients 10000 # 最大客户端数

2. 数据结构与使用优化

  1. // 1. 使用 Pipeline 减少网络往返
  2. $redis = new Redis();
  3. $redis->connect('127.0.0.1', 6379);
  4. $redis->pipeline(); // 开始管道
  5. for ($i = 0; $i < 1000; $i++) {
  6. $redis->set("key:$i", "value:$i");
  7. }
  8. $redis->exec(); // 一次性执行
  9. // 2. 批量操作
  10. // ❌
  11. foreach ($userIds as $id) {
  12. $user = $redis->get("user:$id");
  13. }
  14. // ✅
  15. $redis->mget(array_map(fn($id) => "user:$id", $userIds));
  16. // 3. 选择合适的数据结构
  17. // 计数器:String
  18. $redis->incr('page_views');
  19. // 去重集合:Set
  20. $redis->sadd('online_users', $userId);
  21. // 排行榜:Sorted Set
  22. $redis->zadd('leaderboard', $score, $userId);
  23. // 消息队列:List
  24. $redis->lpush('queue', $message);
  25. // 4. 避免大Key
  26. // ❌ 单个Key存储大量数据
  27. $redis->set('big_key', json_encode($hugeArray));
  28. // ✅ 分片存储
  29. foreach (array_chunk($hugeArray, 1000) as $i => $chunk) {
  30. $redis->set("big_key:$i", json_encode($chunk));
  31. }

3. 连接池与持久连接

  1. // 使用连接池(推荐使用扩展如 phpredis 或 predis)
  2. class RedisPool {
  3. private $pool;
  4. private $config;
  5. public function __construct($size = 10) {
  6. $this->pool = new SplQueue();
  7. for ($i = 0; $i < $size; $i++) {
  8. $redis = new Redis();
  9. $redis->connect('127.0.0.1', 6379, 2.5); // 2.5秒超时
  10. $redis->select(0);
  11. $this->pool->enqueue($redis);
  12. }
  13. }
  14. public function get() {
  15. return $this->pool->dequeue();
  16. }
  17. public function put($redis) {
  18. $this->pool->enqueue($redis);
  19. }
  20. }

四、架构级优化

1. 缓存策略设计

  1. // 1. 多级缓存架构
  2. class CacheManager {
  3. private $localCache = []; // 本地内存缓存
  4. private $redis;
  5. private $db;
  6. public function getUser($id) {
  7. // 第一级:本地缓存
  8. if (isset($this->localCache["user:$id"])) {
  9. return $this->localCache["user:$id"];
  10. }
  11. // 第二级:Redis
  12. $user = $this->redis->get("user:$id");
  13. if ($user) {
  14. $this->localCache["user:$id"] = json_decode($user, true);
  15. return $this->localCache["user:$id"];
  16. }
  17. // 第三级:数据库
  18. $user = $this->db->query("SELECT * FROM users WHERE id = ?", [$id]);
  19. if ($user) {
  20. $this->redis->setex("user:$id", 300, json_encode($user));
  21. $this->localCache["user:$id"] = $user;
  22. }
  23. return $user;
  24. }
  25. }
  26. // 2. 缓存穿透解决方案
  27. public function getUserWithBloomFilter($id) {
  28. // 先查布隆过滤器
  29. if (!$this->bloomFilter->exists($id)) {
  30. return null; // 肯定不存在
  31. }
  32. // 正常缓存逻辑
  33. return $this->getUser($id);
  34. }
  35. // 3. 缓存雪崩解决方案
  36. public function getProduct($id) {
  37. $key = "product:$id";
  38. $product = $this->redis->get($key);
  39. if (!$product) {
  40. // 使用互斥锁,防止大量请求同时击穿缓存
  41. $lockKey = "lock:product:$id";
  42. if ($this->redis->setnx($lockKey, 1)) {
  43. $this->redis->expire($lockKey, 10);
  44. // 从数据库获取
  45. $product = $this->db->getProduct($id);
  46. if ($product) {
  47. // 设置随机过期时间,防止同时失效
  48. $ttl = 300 + rand(0, 60); // 300-360秒
  49. $this->redis->setex($key, $ttl, json_encode($product));
  50. }
  51. $this->redis->del($lockKey);
  52. } else {
  53. // 等待其他线程加载缓存
  54. usleep(100000); // 100ms
  55. return $this->getProduct($id);
  56. }
  57. }
  58. return json_decode($product, true);
  59. }

2. 读写分离与负载均衡

  1. 架构示意图:
  2. 用户请求 Nginx (负载均衡)
  3. [PHP服务器集群]
  4. [Redis集群] ←→ [MySQL主库]
  5. [Redis从库] ←→ [MySQL从库集群]

3. 异步处理

  1. // 使用队列处理耗时操作
  2. class OrderService {
  3. public function createOrder($data) {
  4. // 1. 快速写入数据库
  5. $orderId = $this->db->createOrder($data);
  6. // 2. 异步处理后续逻辑
  7. $this->queue->push('order_created', [
  8. 'order_id' => $orderId,
  9. 'data' => $data
  10. ]);
  11. return $orderId;
  12. }
  13. }
  14. // Worker处理队列
  15. class OrderWorker {
  16. public function process($job) {
  17. $data = $job->getData();
  18. // 发送邮件
  19. $this->sendEmail($data);
  20. // 更新统计数据
  21. $this->updateStatistics($data);
  22. // 记录日志
  23. $this->logOrder($data);
  24. $job->delete();
  25. }
  26. }

五、监控与调试

1. 监控指标

  1. # 查看MySQL QPS
  2. mysql> SHOW GLOBAL STATUS LIKE 'Queries';
  3. mysql> SHOW GLOBAL STATUS LIKE 'Uptime';
  4. # 计算QPS公式
  5. # QPS = (当前Queries - 上次Queries) / 时间间隔
  6. # 查看Redis QPS
  7. redis-cli info stats | grep instantaneous_ops_per_sec
  8. # 查看PHP-FPM状态
  9. curl http://localhost/status?full
  10. # 监控脚本示例
  11. #!/bin/bash
  12. # 监控MySQL QPS
  13. mysql -e "SHOW GLOBAL STATUS" | grep -E "(Queries|Threads_running|Innodb_rows_read)"

2. 压力测试工具

  1. # 使用 wrk 进行压力测试
  2. wrk -t12 -c400 -d30s --latency http://localhost/api/users
  3. # 使用 ab (Apache Bench)
  4. ab -n 10000 -c 100 http://localhost/api/test
  5. # 使用 siege
  6. siege -c 100 -t 1M http://localhost/api/test

六、优化案例:从 1000 QPS 到 50000 QPS

优化前(~1000 QPS):

  • PHP-FPM:pm.max_children = 50
  • MySQL:默认配置,无索引
  • Redis:未使用
  • 架构:单机,同步处理

优化步骤:

  1. 第一轮:基础优化(达到 3000 QPS)

    • PHP-FPM 优化:调整进程数,启用 OPCache
    • MySQL 添加必要索引
    • 引入 Redis 缓存热点数据
  2. 第二轮:架构优化(达到 10000 QPS)

    • 实现读写分离
    • 使用连接池
    • 静态资源 CDN 加速
  3. 第三轮:深度优化(达到 30000 QPS)

    • 使用 Swoole 替换 PHP-FPM
    • MySQL 分库分表
    • Redis 集群,Pipeline 批量操作
  4. 第四轮:极致优化(达到 50000+ QPS)

    • 多级缓存(本地内存 + Redis)
    • 异步化处理
    • 硬件升级(SSD、更多内存)

七、配置检查清单

PHP 检查项:

  • PHP 版本 ≥ 7.4(建议 8.1+)
  • OPCache 已启用并配置合理内存
  • PHP-FPM 进程数根据内存设置
  • 关闭不必要的 PHP 扩展
  • 设置合理的执行超时时间

MySQL 检查项:

  • innodb_buffer_pool_size 为内存的 70-80%
  • 索引覆盖了主要查询条件
  • 慢查询日志已开启
  • 连接数设置合理
  • 定期执行 OPTIMIZE TABLEANALYZE TABLE

Redis 检查项:

  • 内存设置合理,开启淘汰策略
  • 持久化策略符合业务需求
  • 避免大 Key
  • 使用 Pipeline 和批量操作
  • 连接池配置

重要提醒

  1. 测试至上:任何配置修改都要在测试环境验证
  2. 监控先行:优化前建立基线,优化后对比效果
  3. 逐步进行:一次只修改一个配置,观察效果
  4. 因地制宜:根据实际业务场景调整,没有万能配置
  5. 容量规划:预估业务增长,提前规划扩容

记住:优化是一个持续的过程,而不是一次性的任务。随着业务发展,需要不断监控、分析和调整。