07-性能优化

8 阅读6分钟

Hyperf 性能优化

1. 性能优化概述

1.1 优化目标

  • 提高 QPS:每秒处理更多请求
  • 降低响应时间:更快返回结果
  • 减少资源占用:降低 CPU、内存使用
  • 提高并发能力:支持更多并发连接

1.2 性能分析工具

压测工具

Apache Bench (ab)

ab -n 10000 -c 100 http://localhost:9501/api/test
# -n: 总请求数
# -c: 并发数

wrk

wrk -t4 -c100 -d30s http://localhost:9501/api/test
# -t: 线程数
# -c: 并发连接数
# -d: 持续时间
性能分析工具

Xdebug Profiler

; php.ini
xdebug.mode=profile
xdebug.output_dir=/tmp/xdebug

Swoole Tracker

  • 实时监控性能
  • 分析慢请求
  • 内存泄漏检测

2. 协程并发优化

2.1 识别可并发的操作

串行执行示例

<?php
public function getUserDetail(int $userId)
{
    $user = Db::table('users')->find($userId);              // 10ms
    $orders = Db::table('orders')->where('user_id', $userId)->get();  // 20ms
    $profile = Redis::get("profile:{$userId}");             // 5ms
    $balance = $this->paymentService->getBalance($userId);  // 15ms
    
    // 总耗时:10 + 20 + 5 + 15 = 50ms
    return compact('user', 'orders', 'profile', 'balance');
}

并发执行优化

<?php
use Hyperf\Utils\Coroutine;

public function getUserDetail(int $userId)
{
    [$user, $orders, $profile, $balance] = Coroutine::parallel([
        fn() => Db::table('users')->find($userId),
        fn() => Db::table('orders')->where('user_id', $userId)->get(),
        fn() => Redis::get("profile:{$userId}"),
        fn() => $this->paymentService->getBalance($userId),
    ]);
    
    // 总耗时:max(10, 20, 5, 15) = 20ms(提升 2.5 倍)
    return compact('user', 'orders', 'profile', 'balance');
}

2.2 并发批量操作

场景:批量查询多个用户信息

<?php
// 串行查询(慢)
$userIds = [1, 2, 3, 4, 5];
$users = [];

foreach ($userIds as $userId) {
    $users[] = $this->getUserById($userId);  // 每次 10ms
}
// 总耗时:10ms × 5 = 50ms

// 并发查询(快)
$users = Coroutine::parallel(array_map(function ($userId) {
    return fn() => $this->getUserById($userId);
}, $userIds));
// 总耗时:10ms(所有请求并发执行)

3. 缓存优化

3.1 缓存层级

请求 → 本地缓存(进程内存,最快)
      ↓ 未命中
      → Redis 缓存(网络IO,较快)
      ↓ 未命中
      → 数据库(磁盘IO,较慢)

3.2 本地缓存 + Redis 缓存

<?php
use Psr\SimpleCache\CacheInterface;
use Hyperf\Redis\Redis;
use Hyperf\Di\Annotation\Inject;

class ProductService
{
    #[Inject]
    private CacheInterface $localCache;  // 本地缓存
    
    #[Inject]
    private Redis $redis;  // Redis 缓存
    
    public function getProduct(int $id)
    {
        $key = "product:{$id}";
        
        // 1. 本地缓存(无网络开销,1ms)
        $product = $this->localCache->get($key);
        if ($product) {
            return $product;
        }
        
        // 2. Redis 缓存(网络开销,3ms)
        $product = $this->redis->get($key);
        if ($product) {
            $product = json_decode($product, true);
            $this->localCache->set($key, $product, 60);  // 缓存 1 分钟
            return $product;
        }
        
        // 3. 数据库(磁盘IO,10ms)
        $product = Product::find($id);
        if ($product) {
            $this->redis->setex($key, 3600, json_encode($product));
            $this->localCache->set($key, $product, 60);
        }
        
        return $product;
    }
}

性能对比

首次查询:10ms(数据库)
第二次查询:3ms(Redis)
第三次查询:1ms(本地缓存)- 提升 10 倍

3.3 缓存预热

问题:服务刚启动时,缓存是空的,大量请求打到数据库。

解决方案:启动时预热缓存

<?php
namespace App\Listener;

use Hyperf\Event\Annotation\Listener;
use Hyperf\Event\Contract\ListenerInterface;
use Hyperf\Framework\Event\BootApplication;

#[Listener]
class CacheWarmUpListener implements ListenerInterface
{
    public function listen(): array
    {
        return [
            BootApplication::class,
        ];
    }
    
    public function process(object $event): void
    {
        echo "开始预热缓存...\n";
        
        // 预热热门商品
        $hotProducts = Product::where('is_hot', 1)->limit(100)->get();
        
        foreach ($hotProducts as $product) {
            $key = "product:{$product->id}";
            $this->redis->setex($key, 3600, json_encode($product));
        }
        
        echo "缓存预热完成\n";
    }
}

3.4 缓存穿透、击穿、雪崩优化

05-数据库与缓存.md

4. 数据库优化

4.1 连接池配置

<?php
// config/autoload/databases.php
return [
    'default' => [
        'pool' => [
            'min_connections' => 10,   // 启动时创建 10 个连接
            'max_connections' => 32,   // 根据 CPU 核心数调整
            'wait_timeout' => 3.0,     // 等待超时
        ],
    ],
];

优化建议

  • max_connections = CPU 核心数 × 2
  • 监控连接池使用情况,动态调整

4.2 索引优化

慢查询示例

-- 未建索引,全表扫描 10 万行(200ms)
SELECT * FROM users WHERE email = 'test@example.com';

优化

<?php
// 添加索引
Schema::table('users', function (Blueprint $table) {
    $table->index('email');
});

// 查询只扫描 1 行(2ms) - 提升 100 倍

4.3 查询优化

只查询需要的字段
<?php
// ❌ 查询所有字段(慢)
$users = User::where('status', 1)->get();

// ✅ 只查询需要的字段(快)
$users = User::where('status', 1)->select('id', 'name', 'email')->get();
避免 N+1 查询
<?php
// ❌ N+1 查询(1 + 100 次查询)
$users = User::all();
foreach ($users as $user) {
    echo $user->orders->count();
}

// ✅ 预加载(2 次查询)
$users = User::with('orders')->get();
foreach ($users as $user) {
    echo $user->orders->count();
}
批量操作
<?php
// ❌ 循环插入(1000 次 SQL)
foreach ($data as $item) {
    User::create($item);
}

// ✅ 批量插入(1 次 SQL)
User::insert($data);

4.4 读写分离

<?php
// config/autoload/databases.php
return [
    'default' => [
        'read' => [
            'host' => ['192.168.1.100', '192.168.1.101'],  // 多个从库
        ],
        'write' => [
            'host' => ['192.168.1.1'],  // 主库
        ],
    ],
];

使用:

<?php
// 自动从从库读取
$users = User::all();

// 强制从主库读取
$user = User::onWriteConnection()->find(1);

5. 异步队列优化

5.1 识别耗时操作

适合放入队列的操作

  • ✅ 发送邮件、短信
  • ✅ 数据统计和分析
  • ✅ 图片处理
  • ✅ 调用第三方 API
  • ✅ 日志写入

不适合放入队列的操作

  • ❌ 用户登录(需要立即返回)
  • ❌ 订单支付(需要同步结果)

5.2 异步化示例

优化前

<?php
public function register(array $data)
{
    $user = User::create($data);
    
    // 发送欢迎邮件(耗时 2 秒)
    $this->sendWelcomeEmail($user);
    
    // 总响应时间:2 秒
    return $user;
}

优化后

<?php
public function register(array $data)
{
    $user = User::create($data);
    
    // 推送到队列,异步发送
    $this->driver->push(new SendWelcomeEmailJob(['user_id' => $user->id]));
    
    // 立即返回,响应时间:10ms
    return $user;
}

6. 进程和 Worker 配置

6.1 Worker 进程数配置

config/autoload/server.php

<?php
return [
    'settings' => [
        'worker_num' => swoole_cpu_num() * 2,  // CPU 核心数 × 2
        'max_request' => 10000,  // Worker 处理 10000 个请求后重启
        'task_worker_num' => 4,  // 异步任务进程数
    ],
];

优化建议

  • CPU 密集型:worker_num = CPU 核心数
  • IO 密集型:worker_num = CPU 核心数 × 2

6.2 max_request 防止内存泄漏

<?php
'max_request' => 10000,  // Worker 处理 10000 个请求后自动重启

7. 代码优化

7.1 减少不必要的对象创建

优化前

<?php
public function handle()
{
    foreach ($data as $item) {
        $service = new UserService();  // 每次都创建对象
        $service->process($item);
    }
}

优化后

<?php
public function handle()
{
    $service = new UserService();  // 只创建一次
    
    foreach ($data as $item) {
        $service->process($item);
    }
}

7.2 使用生成器处理大量数据

优化前

<?php
// 一次性加载 100 万条数据到内存(几百 MB)
public function export()
{
    $users = User::all();
    
    foreach ($users as $user) {
        // 导出数据
    }
}

优化后

<?php
// 使用生成器,内存占用始终只有几 MB
public function export()
{
    User::chunk(1000, function ($users) {
        foreach ($users as $user) {
            // 导出数据
        }
    });
}

7.3 减少日志输出

优化前

<?php
public function process($data)
{
    $this->logger->debug('开始处理', $data);  // 每个请求都写日志
    
    // 业务逻辑
    
    $this->logger->debug('处理完成');
}

优化后

<?php
public function process($data)
{
    // 生产环境只记录错误日志
    if (env('APP_ENV') === 'production') {
        // 只在出错时记录
    } else {
        $this->logger->debug('开始处理', $data);
    }
    
    // 业务逻辑
}

8. 服务器优化

8.1 OpCache 配置

php.ini

[opcache]
opcache.enable=1
opcache.enable_cli=1
opcache.memory_consumption=256  ; 内存大小(MB)
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.validate_timestamps=0  ; 生产环境禁用时间戳验证

8.2 Nginx 配置

http {
    # 开启 Gzip 压缩
    gzip on;
    gzip_comp_level 5;
    gzip_types text/plain text/css application/json application/javascript;
    
    # 连接池
    upstream hyperf {
        server 127.0.0.1:9501 weight=1;
        server 127.0.0.1:9502 weight=1;
        keepalive 32;  # 保持 32 个长连接
    }
    
    server {
        listen 80;
        
        location / {
            proxy_pass http://hyperf;
            proxy_http_version 1.1;
            proxy_set_header Connection "";  # 保持长连接
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
}

8.3 系统参数优化

# /etc/sysctl.conf

# 增加文件描述符限制
fs.file-max = 1048576

# 增加 TCP 连接数
net.ipv4.tcp_max_syn_backlog = 8192
net.core.somaxconn = 8192

# 快速回收 TIME_WAIT 连接
net.ipv4.tcp_tw_reuse = 1

# 应用配置
sysctl -p

9. 性能监控

9.1 Prometheus + Grafana

安装 Prometheus 组件
composer require hyperf/metric
配置

config/autoload/metric.php

<?php
return [
    'default' => 'prometheus',
    'use_standalone_process' => true,
    'enable_default_metric' => true,
    'metric' => [
        'prometheus' => [
            'driver' => Hyperf\Metric\Adapter\Prometheus\MetricFactory::class,
            'mode' => Hyperf\Metric\Adapter\Prometheus\Constants::SCRAPE_MODE,
            'namespace' => 'app',
        ],
    ],
];
自定义指标
<?php
use Hyperf\Metric\Contract\MetricFactoryInterface;
use Hyperf\Di\Annotation\Inject;

class OrderService
{
    #[Inject]
    private MetricFactoryInterface $metricFactory;
    
    public function createOrder()
    {
        $counter = $this->metricFactory->makeCounter('order_created', ['type']);
        $counter->with('online')->add(1);
        
        $histogram = $this->metricFactory->makeHistogram('order_amount', ['type']);
        $histogram->with('online')->put($amount);
    }
}

9.2 日志分析

使用 ELK(Elasticsearch + Logstash + Kibana)分析日志,发现性能瓶颈。

10. 性能优化案例

案例一:接口响应时间优化

问题:订单详情接口响应时间 200ms

分析

  1. 串行查询 5 个接口(用户、订单、商品、库存、物流)
  2. 未使用缓存
  3. 查询了不必要的字段

优化

  1. 使用协程并发查询 → 耗时降到 50ms
  2. 热点数据使用缓存 → 耗时降到 20ms
  3. 只查询需要的字段 → 耗时降到 15ms

结果:响应时间从 200ms 降到 15ms,提升 13 倍

案例二:秒杀系统优化

问题:秒杀时 QPS 只有 500,大量请求超时

优化

  1. Redis 库存预热,使用 Lua 脚本原子扣减
  2. 使用异步队列创建订单
  3. 使用令牌桶限流

结果:QPS 从 500 提升到 50,000,提升 100 倍

案例三:报表生成优化

问题:导出 100 万条数据时内存溢出

优化

  1. 使用 chunk 分批查询,每次 1000 条
  2. 使用生成器,边查询边输出

结果:内存占用从 500MB 降到 10MB,导出时间从 5 分钟降到 1 分钟

11. 要点

必须掌握

  • 协程并发的使用场景
  • 缓存的优化策略
  • 数据库查询优化
  • 异步队列的使用
  • 至少 1 个性能优化案例

加分项

  • 性能分析工具的使用
  • 服务器配置优化
  • 性能监控体系
  • 能量化优化成果

高频题

1. 如何优化 Hyperf 应用的性能?

答:主要从以下几个方面:

  1. 协程并发:并发执行独立的 IO 操作
  2. 缓存优化:使用多级缓存,减少数据库查询
  3. 数据库优化:添加索引、避免 N+1 查询、使用连接池
  4. 异步队列:耗时操作异步执行
  5. 代码优化:减少对象创建、使用生成器
  6. 服务器优化:OpCache、Nginx 配置

2. 你做过哪些性能优化?

答:(参考案例一、二、三,结合自己的项目经验)

3. 如何分析性能瓶颈?

答:

  1. 使用压测工具(ab、wrk)测试 QPS 和响应时间
  2. 使用 Xdebug Profiler 分析慢请求
  3. 查看慢查询日志
  4. 使用 Prometheus 监控性能指标
  5. 分析日志,找出耗时操作

总结:性能优化是一个持续的过程,需要不断测试、分析、优化。最重要的是要有数据支撑,而不是盲目优化。

下一步:阅读 08-题库.md 巩固知识点