08-面试题库

5 阅读13分钟

Hyperf 题库

📋 题库分类

  • 基础概念题(15 题)
  • 协程相关题(12 题)
  • 依赖注入与 AOP(10 题)
  • 数据库与缓存(15 题)
  • 性能优化题(10 题)
  • 微服务架构(8 题)
  • 综合应用题(10 题)

总计:80 道题


一、基础概念题(15 题)

Q1: Hyperf 是什么?有什么特点?

答案

Hyperf 是一个基于 Swoole 协程的高性能 PHP 框架。

核心特点

  1. 高性能:基于 Swoole 协程,QPS 可达数万
  2. 全协程化:从框架底层到应用层全面协程化
  3. 常驻内存:Swoole 常驻内存,减少框架加载开销
  4. 依赖注入:强大的 DI 容器和注解支持
  5. 微服务:内置服务注册、发现、治理能力
  6. 组件丰富:提供 70+ 官方组件
  7. 云原生:支持容器化部署、配置中心

适用场景:高并发 API、微服务、WebSocket、实时通信


Q2: Hyperf 和 Laravel 有什么区别?

答案

维度LaravelHyperf
运行方式PHP-FPM(每次请求启动)Swoole 常驻内存
并发模型同步阻塞协程异步非阻塞
性能QPS 数百QPS 数万
启动方式每次请求重新加载启动一次,常驻内存
适用场景通用 Web 应用高并发、微服务
学习曲线平缓需要理解协程

核心差异

  • Laravel 基于 PHP-FPM,每次请求都要重新加载框架
  • Hyperf 基于 Swoole,框架常驻内存,只加载一次
  • Hyperf 使用协程实现异步 IO,性能是 Laravel 的几十倍

Q3: 什么是 Swoole?为什么 Hyperf 要基于 Swoole?

答案

Swoole 是一个 PHP 协程网络通信引擎,使用 C/C++ 编写的 PHP 扩展。

核心功能

  • 协程调度器
  • 异步 IO(网络、文件)
  • 协程客户端(MySQL、Redis、HTTP)
  • 定时器
  • 进程管理

Hyperf 选择 Swoole 的原因

  1. 性能优势:协程和异步 IO,性能远超传统 PHP-FPM
  2. 常驻内存:减少框架加载开销
  3. 并发能力:单进程可处理数万并发连接
  4. 丰富功能:提供网络服务、定时器、进程管理等
  5. 生态完善:Swoole 社区活跃

Q4: 常驻内存有什么优势?有什么需要注意的?

答案

优势

  1. 减少框架加载时间:框架只加载一次
  2. 减少文件 IO:配置文件只读取一次
  3. 支持连接池:数据库、Redis 连接可以复用
  4. 可以缓存数据:在内存中缓存热点数据

需要注意

  1. 修改代码需要重启:代码修改后必须重启服务(或使用热重载)
  2. 内存泄漏:需要注意内存泄漏问题(不要在静态变量中无限累积数据)
  3. 全局变量:全局变量会被所有请求共享,需要使用协程上下文隔离
  4. max_request:配置 Worker 定期重启,防止内存泄漏

Q5: Hyperf 适合什么场景?不适合什么场景?

答案

适合的场景

  • ✅ 高并发 API 服务(QPS > 1000)
  • ✅ 微服务架构
  • ✅ WebSocket 实时通信
  • ✅ 消息队列消费者
  • ✅ 定时任务系统
  • ✅ RPC 服务

不适合的场景

  • ❌ 低并发场景(QPS < 1000)
  • ❌ 简单的内容管理系统(CMS)
  • ❌ 需要共享主机的项目
  • ❌ 团队对协程不熟悉

原因

  • Hyperf 的优势在于高并发,低并发场景用传统框架更简单
  • Hyperf 需要独立服务器,不能用虚拟主机
  • Hyperf 有学习成本,团队需要掌握协程概念

二、协程相关题(12 题)

Q6: 什么是协程?和线程有什么区别?

答案

协程:用户态的轻量级线程,由程序自己控制调度。

主要区别

特性线程协程
调度者操作系统内核用户程序
切换开销较大(几 KB)很小(几百字节)
内存占用几百 KB几 KB
并发数量几百个几万个
创建速度较慢极快

协程的优势

  1. 轻量级,可以创建大量协程
  2. 切换开销小,性能高
  3. 同步写法,异步执行
  4. 遇到 IO 自动挂起,让出 CPU

示例

// 协程自动调度
Coroutine::create(function () {
    $result = Db::query(...);  // IO 操作,自动挂起
    // IO 完成后恢复执行
});

Q7: 协程是如何调度的?

答案

协程调度流程

1. 事件循环(Event Loop)不断检查事件
2. 当协程遇到 IO 操作时,主动挂起(yield)
3. 调度器切换到其他可运行的协程
4. 当 IO 操作完成时,触发事件
5. 调度器恢复(resume)该协程

挂起时机

  • 执行 Coroutine::sleep()
  • 网络 IO(MySQL、Redis、HTTP 请求)
  • 文件 IO(启用 Hook 时)
  • 调用 Coroutine::yield() 主动挂起

恢复时机

  • 定时器触发
  • IO 操作完成
  • 调用 Coroutine::resume()

执行示例

Coroutine::create(function () {
    echo "1. 开始\n";
    Coroutine::sleep(1);  // 挂起,切换到其他协程
    echo "3. 恢复\n";
});

Coroutine::create(function () {
    echo "2. 其他协程\n";
});

// 输出:1. 开始 → 2. 其他协程 → 3. 恢复

Q8: 什么是协程上下文?为什么需要它?

答案

协程上下文:每个协程独立的数据存储空间,用于隔离不同协程的数据。

为什么需要

在传统 PHP-FPM 中,每个请求都是独立的进程,全局变量天然隔离。但在 Swoole 中,多个请求在同一个进程中并发执行,共享全局变量,会导致数据混乱。

问题示例

// 全局变量会被所有协程共享
$userId = 0;

function handleRequest($id) {
    global $userId;
    $userId = $id;              // 请求 A: userId = 100
    Coroutine::sleep(0.1);      // 挂起,切换到请求 B
    echo "User: $userId\n";     // 可能输出 200(被请求 B 修改)
}

解决方案

use Hyperf\Context\Context;

function handleRequest($id) {
    Context::set('user_id', $id);
    Coroutine::sleep(0.1);
    echo "User: " . Context::get('user_id') . "\n";  // 正确
}

Q9: 如何在 Hyperf 中实现并发请求?

答案

使用 Coroutine::parallel() 实现并发执行。

串行执行(慢)

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
    
    // 总耗时:10 + 20 + 5 = 35ms
    return compact('user', 'orders', 'profile');
}

并行执行(快)

use Hyperf\Utils\Coroutine;

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

注意事项

  • 只有独立的、无依赖关系的操作才适合并发
  • 并发操作会创建多个协程

Q10: 什么是协程安全?如何保证?

答案

协程安全:在协程环境下,多个协程共享同一个进程的内存,需要确保数据不会互相干扰。

不安全的代码

class UserService
{
    private $currentUser;  // 实例变量,所有协程共享
    
    public function handle(int $userId)
    {
        $this->currentUser = Db::table('users')->find($userId);
        
        // 这里可能协程切换,currentUser 被其他协程修改
        Coroutine::sleep(0.1);
        
        return $this->currentUser;  // 可能返回错误的用户
    }
}

保证协程安全的方法

  1. 使用协程上下文
use Hyperf\Context\Context;

public function handle(int $userId)
{
    $user = Db::table('users')->find($userId);
    Context::set('current_user', $user);
    
    Coroutine::sleep(0.1);
    
    return Context::get('current_user');  // 正确
}
  1. 使用局部变量
public function handle(int $userId)
{
    $user = Db::table('users')->find($userId);  // 局部变量
    Coroutine::sleep(0.1);
    return $user;  // 正确
}
  1. 避免使用全局变量和静态变量

三、依赖注入与 AOP(10 题)

Q11: 什么是依赖注入?有什么优势?

答案

依赖注入:将类所依赖的对象从外部注入,而不是在类内部创建。

优势

  1. 降低耦合度:依赖接口而非具体实现
  2. 便于测试:可以注入 Mock 对象
  3. 便于替换:切换实现无需修改代码
  4. 自动管理:容器自动创建和注入依赖

示例对比

// 传统方式(高耦合)
class UserController
{
    private $userService;
    
    public function __construct()
    {
        $this->userService = new UserService();  // 在类内部创建
    }
}

// 依赖注入(低耦合)
class UserController
{
    public function __construct(
        private UserServiceInterface $userService  // 从外部注入
    ) {
    }
}

Q12: Hyperf 有哪些依赖注入方式?

答案

三种方式

  1. 构造函数注入
class UserController
{
    public function __construct(
        private UserService $userService
    ) {
    }
}
  1. 属性注入(推荐):
use Hyperf\Di\Annotation\Inject;

class UserController
{
    #[Inject]
    private UserService $userService;
}
  1. 从容器获取
use Hyperf\Context\ApplicationContext;

$userService = ApplicationContext::getContainer()->get(UserService::class);

推荐使用属性注入:代码更简洁,减少构造函数参数。


Q13: 什么是 AOP?有什么应用场景?

答案

AOP(Aspect Oriented Programming):面向切面编程,允许在不修改原有代码的情况下,对方法进行增强。

应用场景

  1. 日志记录:记录方法调用、参数、返回值
  2. 性能监控:统计方法执行时间
  3. 权限校验:检查用户权限
  4. 缓存:自动缓存方法结果
  5. 事务管理:自动开启和提交事务
  6. 限流:防止接口被频繁调用
  7. 异常处理:统一处理异常

示例

#[Aspect]
class LogAspect extends AbstractAspect
{
    public array $classes = [UserService::class];
    
    public function process(ProceedingJoinPoint $proceedingJoinPoint)
    {
        echo "方法调用前\n";
        
        $result = $proceedingJoinPoint->process();  // 执行原方法
        
        echo "方法调用后\n";
        
        return $result;
    }
}

Q14: AOP 为什么有时候不生效?

答案

常见原因

  1. 对象不是从容器获取的
$service = new UserService();  // ❌ AOP 不生效

正确做法:

$service = ApplicationContext::getContainer()->get(UserService::class);
  1. 方法不是 public
private function getList() { }  // ❌ AOP 不生效
  1. 类被标记为 final
final class UserService { }  // ❌ AOP 不生效
  1. 切点配置不正确:检查 $classes$annotations 配置

  2. 代理类未生成:清除缓存重新生成

php bin/hyperf.php di:init-proxy

四、数据库与缓存(15 题)

Q15: 什么是连接池?为什么需要它?

答案

连接池:预先创建并维护一定数量的连接,需要时从池中获取,用完后归还。

为什么需要

传统 PHP-FPM

每次请求:创建连接 → 查询 → 关闭连接
开销:TCP 三次握手 + MySQL 认证 + TCP 四次挥手

Hyperf 连接池

启动时:创建 10 个连接,放入池中
请求时:从池中获取 → 查询 → 归还到池中
优势:复用连接,减少创建和销毁开销

配置示例

'pool' => [
    'min_connections' => 10,   // 最小连接数
    'max_connections' => 32,   // 最大连接数
    'wait_timeout' => 3.0,     // 等待超时
],

性能提升

  • 创建连接:50ms
  • 从连接池获取:1ms
  • 提升 50 倍

Q16: 什么是 N+1 查询?如何解决?

答案

N+1 查询:查询主表 1 次,然后循环查询关联表 N 次,导致大量数据库查询。

问题代码

// 查询所有用户(1 次查询)
$users = User::all();

// 循环查询每个用户的订单(N 次查询)
foreach ($users as $user) {
    echo $user->orders->count();
}

// 总查询次数:1 + N(如 1 + 100 = 101 次)

解决方案:使用预加载(Eager Loading)

// 只查询 2 次(1 次用户 + 1 次订单)
$users = User::with('orders')->get();

foreach ($users as $user) {
    echo $user->orders->count();  // 直接从内存读取
}

性能提升

  • 优化前:101 次查询,耗时 1010ms
  • 优化后:2 次查询,耗时 20ms
  • 提升 50 倍

Q17: Hyperf 的缓存注解有哪几种?

答案

三种注解

  1. @Cacheable:生成缓存
    • 首次调用时执行方法,并缓存结果
    • 后续调用直接返回缓存
#[Cacheable(prefix: 'user', ttl: 3600, value: '#{id}')]
public function getUserById(int $id)
{
    return Db::table('users')->find($id);
}
  1. @CachePut:更新缓存
    • 每次都执行方法
    • 并更新缓存
#[CachePut(prefix: 'user', ttl: 3600, value: '#{user.id}')]
public function updateUser(array $user)
{
    Db::table('users')->where('id', $user['id'])->update($user);
    return $user;
}
  1. @CacheEvict:删除缓存
#[CacheEvict(prefix: 'user', value: '#{id}')]
public function deleteUser(int $id)
{
    Db::table('users')->where('id', $id)->delete();
}

// 删除所有缓存
#[CacheEvict(prefix: 'user', all: true)]
public function deleteAllCache() {}

Q18: 什么是缓存穿透、缓存击穿、缓存雪崩?如何解决?

答案

1. 缓存穿透

定义:查询不存在的数据,缓存和数据库都没有,每次都打到数据库。

场景:用户恶意查询不存在的 ID(如 -1)

解决方案:缓存空值

if ($data === null) {
    $redis->setex($key, 60, 'null');  // 缓存空值(短时间)
}
2. 缓存击穿

定义:热点 key 过期,瞬间大量请求打到数据库。

场景:热门商品的缓存过期,1 万个请求同时查询数据库

解决方案:分布式锁 + 双重检查

$locked = $redis->set($lockKey, $lockValue, ['NX', 'EX' => 10]);

if ($locked) {
    // 只有一个请求查询数据库
    $data = Db::query(...);
    $redis->setex($key, 3600, $data);
}
3. 缓存雪崩

定义:大量 key 同时过期,导致数据库压力骤增。

场景:1000 个商品的缓存同时在凌晨 2 点过期

解决方案:过期时间加随机值

$ttl = 3600 + mt_rand(0, 600);  // 加 0-10 分钟的随机值
$redis->setex($key, $ttl, $data);

Q19: 分布式锁如何实现?

答案

使用 Redis 的 SET NX EX 命令实现分布式锁。

获取锁

$lockKey = "lock:user:{$userId}";
$lockValue = uniqid();  // 唯一值,用于释放锁时验证

// NX:只在 key 不存在时设置
// EX:设置过期时间(防止死锁)
$locked = $redis->set($lockKey, $lockValue, ['NX', 'EX' => 10]);

if (!$locked) {
    throw new \Exception('获取锁失败');
}

释放锁(使用 Lua 脚本保证原子性):

$script = <<<LUA
    if redis.call('get', KEYS[1]) == ARGV[1] then
        return redis.call('del', KEYS[1])
    else
        return 0
    end
LUA;

$redis->eval($script, [$lockKey, $lockValue], 1);

为什么需要 Lua 脚本

  • 确保"检查 + 删除"是原子操作
  • 防止删除其他请求的锁

五、性能优化题(10 题)

Q20: Hyperf 如何实现高性能?

答案

核心机制

  1. Swoole 常驻内存

    • 框架只加载一次,常驻内存
    • 减少框架加载和初始化开销
  2. 协程异步 IO

    • 遇到 IO 操作时挂起协程,让出 CPU
    • 单进程可处理数万并发请求
  3. 连接池

    • 复用数据库、Redis 连接
    • 减少连接建立和销毁开销
  4. 事件驱动

    • 基于 Reactor 模型
    • 高效处理并发事件
  5. 协程并发

    • 可以并发执行多个 IO 操作
    • 大幅减少总耗时

性能对比

Laravel (PHP-FPM):  QPS 500-800
ThinkPHP (PHP-FPM): QPS 600-900
Hyperf (Swoole):    QPS 15,000-30,000

Q21: 如何优化 Hyperf 应用的性能?

答案

优化策略(从多个层面):

1. 协程并发

[$user, $orders] = Coroutine::parallel([
    fn() => $this->getUser($id),
    fn() => $this->getOrders($id),
]);

2. 缓存优化

#[Cacheable(prefix: 'user', ttl: 3600)]
public function getUserById(int $id)
{
    return Db::table('users')->find($id);
}

3. 数据库优化

  • 添加索引
  • 避免 N+1 查询
  • 使用批量操作
  • 只查询需要的字段

4. 连接池配置

'pool' => [
    'min_connections' => 10,
    'max_connections' => 32,
],

5. 异步队列

// 将耗时操作放入队列异步执行
$this->driver->push(new EmailJob([...]));

6. 减少日志输出:生产环境只记录必要日志

7. 使用 OpCache:开启 PHP OpCache,减少代码解析开销


Q22: 连接池如何配置?参数含义是什么?

答案

配置示例

'pool' => [
    'min_connections' => 10,        // 最小连接数(预热)
    'max_connections' => 32,        // 最大连接数
    'connect_timeout' => 10.0,      // 连接超时时间
    'wait_timeout' => 3.0,          // 等待超时时间
    'heartbeat' => -1,              // 心跳检测间隔
    'max_idle_time' => 60,          // 最大空闲时间
],

参数说明

参数含义推荐值
min_connections启动时创建的连接数10
max_connections最大连接数,超过则等待CPU 核心数 × 2
connect_timeout连接超时时间(秒)10.0
wait_timeout等待可用连接的超时时间(秒)3.0
max_idle_time连接最大空闲时间(秒)60

优化建议

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

六、组件相关题(8 题)

Q23: 异步队列有什么用?如何使用?

答案

作用:将耗时任务异步执行,避免阻塞主流程。

应用场景

  • 发送邮件、短信
  • 数据统计和分析
  • 图片处理
  • 导出大文件
  • 调用第三方 API

使用步骤

  1. 创建任务
namespace App\Job;

use Hyperf\AsyncQueue\Job;

class EmailJob extends Job
{
    public function __construct(public array $params) {}
    
    public function handle()
    {
        // 发送邮件逻辑
        echo "发送邮件到: {$this->params['to']}\n";
    }
}
  1. 推送任务
use Hyperf\AsyncQueue\Driver\DriverFactory;

$driver = $this->driverFactory->get('default');
$driver->push(new EmailJob([
    'to' => 'user@example.com',
    'subject' => '欢迎注册',
]), 0);  // 延迟 0 秒

性能提升

  • 优化前:注册接口响应时间 2 秒(包含发送邮件)
  • 优化后:注册接口响应时间 10ms(邮件异步发送)

Q24: 如何配置定时任务?

答案

使用 @Crontab 注解配置定时任务。

示例

namespace App\Crontab;

use Hyperf\Crontab\Annotation\Crontab;

#[Crontab(
    name: 'StatisticsTask',
    rule: '0 2 * * *',        // 每天凌晨 2callback: 'execute',
    memo: '统计每日数据'
)]
class StatisticsTask
{
    public function execute()
    {
        // 统计逻辑
        $count = Db::table('orders')
            ->whereDate('created_at', date('Y-m-d'))
            ->count();
        
        echo "今日订单数:{$count}\n";
    }
}

Cron 表达式

* * * * *
│ │ │ │ │
│ │ │ │ └─ 星期 (0-7)
│ │ │ └─── 月份 (1-12)
│ │ └───── 日期 (1-31)
│ └─────── 小时 (0-23)
└───────── 分钟 (0-59)

常用示例

  • 0 2 * * *:每天凌晨 2 点
  • */5 * * * *:每 5 分钟
  • 0 */2 * * *:每 2 小时
  • 0 0 * * 0:每周日凌晨
  • 0 0 1 * *:每月 1 号凌晨

七、综合应用题(10 题)

Q25: 设计一个高并发秒杀系统,如何使用 Hyperf 实现?

答案

系统设计

1. 库存预热

// 启动时将库存加载到 Redis
$redis->set("stock:{$productId}", 1000);

2. 秒杀接口

public function seckill(int $productId, int $userId)
{
    // 使用 Lua 脚本原子扣减库存
    $script = <<<LUA
        if redis.call('get', KEYS[1]) > '0' then
            return redis.call('decr', KEYS[1])
        else
            return -1
        end
    LUA;
    
    $result = $redis->eval($script, ["stock:{$productId}"], 1);
    
    if ($result < 0) {
        return ['code' => 1, 'msg' => '商品已售罄'];
    }
    
    // 异步创建订单(使用队列)
    $this->driver->push(new CreateOrderJob([
        'product_id' => $productId,
        'user_id' => $userId,
    ]));
    
    return ['code' => 0, 'msg' => '抢购成功'];
}

3. 创建订单(异步):

class CreateOrderJob extends Job
{
    public function handle()
    {
        Db::transaction(function () {
            // 创建订单
            $orderId = Db::table('orders')->insertGetId([...]);
            
            // 扣减数据库库存(双重保险)
            Db::table('products')
                ->where('id', $this->params['product_id'])
                ->decrement('stock');
        });
    }
}

4. 限流

#[RateLimit(create: 10, capacity: 100)]  // 令牌桶限流

优化点

  • Redis 原子操作,防止超卖
  • 异步创建订单,快速响应
  • 令牌桶限流,防止恶意刷单
  • Lua 脚本保证原子性

性能

  • 支持 10 万+并发
  • 响应时间 < 10ms

Q26: 如何保证 Hyperf 服务的稳定性?

答案

从多个层面保证稳定性

1. 代码层面

  • 使用 try-catch 捕获异常
  • 避免内存泄漏(不使用静态变量缓存)
  • 使用协程上下文隔离数据
  • 使用连接池,避免连接耗尽

2. 配置层面

'settings' => [
    'worker_num' => swoole_cpu_num() * 2,
    'max_request' => 10000,  // 定期重启 Worker
],

3. 架构层面

  • 使用限流防止过载
  • 服务降级,保证核心功能
  • 使用缓存,减轻数据库压力
  • 使用熔断器,防止雪崩

4. 监控层面

  • 使用 Prometheus 监控性能指标
  • 配置告警,及时发现问题
  • 记录详细日志,便于排查

5. 运维层面

  • 使用 Supervisor 守护进程
  • 使用 Nginx 负载均衡
  • 多机房部署,容灾备份
  • 定期压测,评估系统容量

八、高频题汇总(必背)

Q27: Hyperf 和 ThinkPHP 有什么区别?

答案

维度ThinkPHPHyperf
运行方式PHP-FPMSwoole 常驻内存
性能QPS 数百QPS 数万
学习曲线容易需要理解协程
适用场景传统 Web 应用高并发、微服务

相似点

  • 都是 PHP 框架
  • API 设计相似(路由、数据库、缓存)

核心差异

  • ThinkPHP 基于 PHP-FPM,传统同步模型
  • Hyperf 基于 Swoole,协程异步模型

Q28: 协程和异步回调有什么区别?

答案

异步回调(Node.js 风格):

// 回调地狱
queryDatabase(sql1, function(result1) {
    queryDatabase(sql2, function(result2) {
        queryDatabase(sql3, function(result3) {
            // 使用 result1, result2, result3
        });
    });
});

协程(同步写法,异步执行):

$result1 = queryDatabase($sql1);
$result2 = queryDatabase($sql2);
$result3 = queryDatabase($sql3);
// 使用 result1, result2, result3

协程的优势

  • 代码更简洁,易于理解
  • 避免回调地狱
  • 同样是异步执行,性能相当

Q29: 你在项目中遇到过哪些 Hyperf 相关的问题?

参考答案

问题 1:内存泄漏

现象:服务运行一段时间后,内存占用持续增长。

原因:在静态变量中缓存数据。

解决:

  • 改用 Redis 缓存,设置过期时间
  • 配置 max_request,定期重启 Worker

问题 2:协程数据串了

现象:用户 A 看到了用户 B 的数据。

原因:使用了实例变量存储请求相关数据。

解决:使用协程上下文存储数据。

问题 3:AOP 不生效

现象:切面没有执行。

原因:对象是通过 new 创建的。

解决:从容器获取对象。

问题 4:数据库连接超时

现象:偶尔报错 "MySQL server has gone away"。

原因:连接池中的连接长时间未使用,被数据库关闭。

解决:配置心跳检测或 max_idle_time


Q30: 如何进行 Hyperf 应用的压力测试?

答案

使用 ab(Apache Bench)

ab -n 10000 -c 100 http://localhost:9501/api/test

# -n: 总请求数
# -c: 并发数

输出结果解读

Requests per second:    15000 [#/sec]  # QPS
Time per request:       6.67 [ms]      # 平均响应时间

使用 wrk(更强大):

wrk -t4 -c100 -d30s http://localhost:9501/api/test

# -t: 线程数
# -c: 并发连接数
# -d: 持续时间

压测注意事项

  1. 关闭日志输出
  2. 使用生产环境配置
  3. 预热服务(先跑一轮)
  4. 逐步增加并发数
  5. 监控系统资源(CPU、内存)

性能指标

  • QPS:每秒请求数(越高越好)
  • 响应时间:平均响应时间(越低越好)
  • 成功率:成功请求占比(应该 100%)

九、项目经验题(重要!)

Q31: 介绍一个你用 Hyperf 做的项目

参考答案模板

我在 [公司] 使用 Hyperf 开发了一个高并发的电商 API 服务。

【项目背景】
这个项目主要为 Web、App、小程序提供后端接口,
峰值 QPS 达到 15,000,支持 10 万+并发连接。

【技术架构】
- 后端:Hyperf 3.1 + MySQL 8.0 + Redis 7.0
- 部署:Docker + Nginx + Supervisor
- 监控:Prometheus + Grafana

【我的职责】
1. 负责用户、订单模块的开发
2. 进行性能优化,将 QPS 从 500 提升到 15,000
3. 设计秒杀系统,支持 10 万+并发
4. 解决缓存雪崩、数据一致性等问题

【技术亮点】
1. 使用协程并发,接口响应时间从 200ms 降到 50ms
2. 使用多级缓存,缓存命中率 95%
3. 使用异步队列,异步处理耗时任务
4. 使用 Redis + Lua 脚本实现秒杀,无超卖

【项目成果】
- QPS 提升 30 倍
- 响应时间降低 4 倍
- 支持 10 万+并发连接
- 系统稳定运行,无重大故障

Q32: 你做过哪些性能优化?

参考答案

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

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

优化措施:

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

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

案例 2:数据库查询优化

问题:用户列表接口很慢

原因:N+1 查询问题

优化:使用 with 预加载关联数据

结果:查询次数从 101 次降到 2 次,耗时从 1 秒降到 20ms

案例 3:秒杀系统优化

问题:秒杀时 QPS 只有 500

优化措施:

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

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


十、快速复习清单

必须掌握的 20 个知识点

  • 1. Hyperf 的核心特点
  • 2. 与 Laravel/ThinkPHP 的区别
  • 3. 协程的定义和优势
  • 4. 协程调度原理
  • 5. 协程上下文的作用
  • 6. 依赖注入的概念
  • 7. Hyperf 的依赖注入方式
  • 8. AOP 的应用场景
  • 9. 连接池的作用
  • 10. N+1 查询问题
  • 11. 缓存的三种注解
  • 12. 缓存穿透/击穿/雪崩
  • 13. 异步队列的使用
  • 14. 定时任务的配置
  • 15. 协程并发的使用
  • 16. 性能优化的方法
  • 17. 分布式锁的实现
  • 18. 服务注册与发现
  • 19. RPC 调用
  • 20. 项目经验总结

建议

  1. 每天复习 5-10 题
  2. 用自己的话复述答案
  3. 结合实际项目经验
  4. 准备 2-3 个深入话题

祝你成功! 🎉