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 缓存穿透、击穿、雪崩优化
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
分析:
- 串行查询 5 个接口(用户、订单、商品、库存、物流)
- 未使用缓存
- 查询了不必要的字段
优化:
- 使用协程并发查询 → 耗时降到 50ms
- 热点数据使用缓存 → 耗时降到 20ms
- 只查询需要的字段 → 耗时降到 15ms
结果:响应时间从 200ms 降到 15ms,提升 13 倍
案例二:秒杀系统优化
问题:秒杀时 QPS 只有 500,大量请求超时
优化:
- Redis 库存预热,使用 Lua 脚本原子扣减
- 使用异步队列创建订单
- 使用令牌桶限流
结果:QPS 从 500 提升到 50,000,提升 100 倍
案例三:报表生成优化
问题:导出 100 万条数据时内存溢出
优化:
- 使用 chunk 分批查询,每次 1000 条
- 使用生成器,边查询边输出
结果:内存占用从 500MB 降到 10MB,导出时间从 5 分钟降到 1 分钟
11. 要点
必须掌握
- 协程并发的使用场景
- 缓存的优化策略
- 数据库查询优化
- 异步队列的使用
- 至少 1 个性能优化案例
加分项
- 性能分析工具的使用
- 服务器配置优化
- 性能监控体系
- 能量化优化成果
高频题
1. 如何优化 Hyperf 应用的性能?
答:主要从以下几个方面:
- 协程并发:并发执行独立的 IO 操作
- 缓存优化:使用多级缓存,减少数据库查询
- 数据库优化:添加索引、避免 N+1 查询、使用连接池
- 异步队列:耗时操作异步执行
- 代码优化:减少对象创建、使用生成器
- 服务器优化:OpCache、Nginx 配置
2. 你做过哪些性能优化?
答:(参考案例一、二、三,结合自己的项目经验)
3. 如何分析性能瓶颈?
答:
- 使用压测工具(ab、wrk)测试 QPS 和响应时间
- 使用 Xdebug Profiler 分析慢请求
- 查看慢查询日志
- 使用 Prometheus 监控性能指标
- 分析日志,找出耗时操作
总结:性能优化是一个持续的过程,需要不断测试、分析、优化。最重要的是要有数据支撑,而不是盲目优化。
下一步:阅读 08-题库.md 巩固知识点