Swoole 底层核心面试手册

4 阅读10分钟

Swoole 底层核心学习手册

适用版本: Swoole 5.x
文档类型: 学习必备 + 实战指南
更新时间: 2025-10-26


目录

  1. 协程机制
  2. 网络服务器
  3. 并发控制
  4. 进程管理
  5. 事件驱动模型
  6. 内存管理
  7. 高频学习题

1. 协程机制

1.1 协程原理

核心概念:协程是用户态的轻量级线程,由程序自己控制调度,而不是由操作系统调度。

协程调度机制
// Swoole协程的创建
go(function () {
    echo "协程1开始\n";
    co::sleep(1); // 协程挂起,让出CPU
    echo "协程1恢复\n";
});

go(function () {
    echo "协程2开始\n";
    co::sleep(0.5);
    echo "协程2恢复\n";
});

// 输出顺序:
// 协程1开始
// 协程2开始
// 协程2恢复(0.5秒后)
// 协程1恢复(1秒后)

调度原理

  1. 协程在遇到IO操作时主动让出CPU(yield)
  2. Swoole的EventLoop监听IO事件
  3. IO就绪后恢复协程执行(resume)
协程上下文
use Swoole\Coroutine;

// 每个协程都有独立的上下文
go(function () {
    Coroutine::getContext()->user_id = 1001;
    co::sleep(1);
    echo Coroutine::getContext()->user_id; // 1001
});

go(function () {
    Coroutine::getContext()->user_id = 1002;
    co::sleep(0.5);
    echo Coroutine::getContext()->user_id; // 1002
});

学习重点

  • 协程ID:Coroutine::getCid() 获取当前协程ID
  • 协程数量限制:Coroutine::set(['max_coroutine' => 100000])
  • 协程退出:defer延迟调用、协程结束自动清理

1.2 Channel 通道

用途:协程间通信、协程同步

use Swoole\Coroutine\Channel;

$channel = new Channel(3); // 容量为3的有缓冲通道

// 生产者协程
go(function () use ($channel) {
    for ($i = 0; $i < 5; $i++) {
        $channel->push(['data' => $i]);
        echo "生产: {$i}\n";
    }
});

// 消费者协程
go(function () use ($channel) {
    while (true) {
        $data = $channel->pop();
        if ($data === false) {
            break;
        }
        echo "消费: {$data['data']}\n";
    }
});

Channel API

  • push($data, $timeout) - 推送数据(满了会挂起)
  • pop($timeout) - 弹出数据(空了会挂起)
  • stats() - 获取统计信息
  • close() - 关闭通道

典型应用场景

// 协程池实现
class CoroutinePool
{
    private $channel;
    private $maxConcurrency;
    
    public function __construct(int $maxConcurrency)
    {
        $this->maxConcurrency = $maxConcurrency;
        $this->channel = new Channel($maxConcurrency);
        
        // 初始化令牌
        for ($i = 0; $i < $maxConcurrency; $i++) {
            $this->channel->push(true);
        }
    }
    
    public function submit(callable $task)
    {
        go(function () use ($task) {
            $this->channel->pop(); // 获取令牌
            try {
                $task();
            } finally {
                $this->channel->push(true); // 归还令牌
            }
        });
    }
}

// 使用示例
$pool = new CoroutinePool(10); // 最大并发10
for ($i = 0; $i < 100; $i++) {
    $pool->submit(function () use ($i) {
        // 模拟耗时任务
        co::sleep(0.1);
        echo "任务{$i}完成\n";
    });
}

1.3 WaitGroup

用途:等待一组协程全部完成

use Swoole\Coroutine\WaitGroup;

$wg = new WaitGroup();

// 启动10个协程
for ($i = 0; $i < 10; $i++) {
    $wg->add();
    go(function () use ($wg, $i) {
        co::sleep(rand(1, 3));
        echo "协程{$i}完成\n";
        $wg->done();
    });
}

// 等待所有协程完成
$wg->wait();
echo "所有协程已完成\n";

2. 网络服务器

2.1 TCP Server

基础服务器
use Swoole\Server;

$server = new Server('0.0.0.0', 9501, SWOOLE_PROCESS, SWOOLE_SOCK_TCP);

// 配置参数
$server->set([
    'worker_num' => 4,           // Worker进程数
    'max_request' => 10000,      // Worker最大处理请求数
    'max_conn' => 10000,         // 最大连接数
    'dispatch_mode' => 2,        // 固定模式,保证同一个连接数据总在同一个Worker处理
    'daemonize' => 0,            // 守护进程化
    'heartbeat_check_interval' => 60,  // 心跳检测间隔
    'heartbeat_idle_time' => 600,      // 连接最大闲置时间
    'open_eof_check' => true,    // 开启EOF检测
    'package_eof' => "\r\n",     // 设置EOF
]);

// 连接建立
$server->on('connect', function (Server $server, int $fd) {
    echo "客户端{$fd}连接\n";
});

// 接收数据
$server->on('receive', function (Server $server, int $fd, int $reactorId, string $data) {
    echo "收到客户端{$fd}数据: {$data}\n";
    $server->send($fd, "Server: " . $data);
});

// 连接关闭
$server->on('close', function (Server $server, int $fd) {
    echo "客户端{$fd}关闭\n";
});

$server->start();
长连接心跳检测
$server = new Server('0.0.0.0', 9501);

$server->set([
    'heartbeat_check_interval' => 5,   // 每5秒检测一次
    'heartbeat_idle_time' => 10,       // 10秒内无数据传输则关闭连接
]);

// 自定义心跳包
$server->on('receive', function (Server $server, int $fd, int $reactorId, string $data) {
    if ($data === 'PING') {
        $server->send($fd, 'PONG');
        return;
    }
    // 处理业务数据
});
TCP粘包处理

方案1:固定包头+包体

$server->set([
    'open_length_check' => true,
    'package_length_type' => 'N',        // 包头长度类型(4字节网络字节序)
    'package_length_offset' => 0,        // 长度字段偏移
    'package_body_offset' => 4,          // 包体起始位置
    'package_max_length' => 2000000,     // 最大包长度
]);

// 客户端发送
$data = "Hello Swoole";
$package = pack('N', strlen($data)) . $data;

方案2:EOF分包

$server->set([
    'open_eof_check' => true,
    'package_eof' => "\r\n\r\n",
    'package_max_length' => 1024 * 1024 * 2,
]);

方案3:HTTP协议

$server->set([
    'open_http_protocol' => true,
    'open_websocket_protocol' => false,
]);

2.2 UDP Server

use Swoole\Server;

$server = new Server('0.0.0.0', 9502, SWOOLE_PROCESS, SWOOLE_SOCK_UDP);

$server->on('packet', function (Server $server, string $data, array $clientInfo) {
    echo "收到UDP数据: {$data}\n";
    echo "来自: {$clientInfo['address']}:{$clientInfo['port']}\n";
    
    // 回复客户端
    $server->sendto($clientInfo['address'], $clientInfo['port'], "收到: " . $data);
});

$server->start();

UDP vs TCP对比

特性TCPUDP
连接面向连接无连接
可靠性可靠传输不可靠
顺序有序无序
速度较慢
应用场景HTTP、FTP、邮件视频流、游戏、DNS

2.3 WebSocket Server

use Swoole\WebSocket\Server;
use Swoole\WebSocket\Frame;

$server = new Server('0.0.0.0', 9503);

// 握手
$server->on('open', function (Server $server, Swoole\Http\Request $request) {
    echo "客户端{$request->fd}握手成功\n";
});

// 接收消息
$server->on('message', function (Server $server, Frame $frame) {
    echo "收到客户端{$frame->fd}消息: {$frame->data}\n";
    
    // 广播给所有客户端
    foreach ($server->connections as $fd) {
        if ($server->isEstablished($fd)) {
            $server->push($fd, $frame->data);
        }
    }
});

// 关闭
$server->on('close', function (Server $server, int $fd) {
    echo "客户端{$fd}关闭\n";
});

$server->start();

实战:聊天室实现

class ChatRoom
{
    private $server;
    private $rooms = []; // 房间列表
    
    public function __construct()
    {
        $this->server = new Server('0.0.0.0', 9503);
        $this->server->on('open', [$this, 'onOpen']);
        $this->server->on('message', [$this, 'onMessage']);
        $this->server->on('close', [$this, 'onClose']);
    }
    
    public function onOpen(Server $server, $request)
    {
        // 默认加入大厅
        $this->joinRoom($request->fd, 'lobby');
    }
    
    public function onMessage(Server $server, Frame $frame)
    {
        $data = json_decode($frame->data, true);
        
        switch ($data['type']) {
            case 'join':
                $this->joinRoom($frame->fd, $data['room']);
                break;
            case 'message':
                $this->broadcast($frame->fd, $data['message']);
                break;
        }
    }
    
    private function joinRoom(int $fd, string $room)
    {
        if (!isset($this->rooms[$room])) {
            $this->rooms[$room] = [];
        }
        $this->rooms[$room][$fd] = true;
        
        // 通知房间内其他人
        $this->broadcast($fd, "用户{$fd}加入房间", $room);
    }
    
    private function broadcast(int $fromFd, string $message, string $room = null)
    {
        // 获取房间成员
        if ($room) {
            $members = $this->rooms[$room] ?? [];
        } else {
            // 找到发送者所在房间
            $members = [];
            foreach ($this->rooms as $r => $m) {
                if (isset($m[$fromFd])) {
                    $members = $m;
                    break;
                }
            }
        }
        
        foreach ($members as $fd => $_) {
            if ($this->server->isEstablished($fd)) {
                $this->server->push($fd, json_encode([
                    'from' => $fromFd,
                    'message' => $message,
                    'time' => date('Y-m-d H:i:s')
                ]));
            }
        }
    }
    
    public function start()
    {
        $this->server->start();
    }
}

$chat = new ChatRoom();
$chat->start();

2.4 HTTP Server

use Swoole\Http\Server;
use Swoole\Http\Request;
use Swoole\Http\Response;

$server = new Server('0.0.0.0', 9504);

$server->on('request', function (Request $request, Response $response) {
    // 设置响应头
    $response->header('Content-Type', 'application/json');
    $response->header('Access-Control-Allow-Origin', '*');
    
    // 路由处理
    $path = $request->server['request_uri'];
    
    switch ($path) {
        case '/api/user':
            $response->end(json_encode([
                'code' => 0,
                'data' => ['id' => 1, 'name' => 'John']
            ]));
            break;
            
        case '/api/upload':
            // 处理文件上传
            if (isset($request->files['file'])) {
                $file = $request->files['file'];
                move_uploaded_file($file['tmp_name'], '/upload/' . $file['name']);
                $response->end(json_encode(['code' => 0, 'msg' => '上传成功']));
            }
            break;
            
        default:
            $response->status(404);
            $response->end('Not Found');
    }
});

$server->start();

2.5 MQTT 协议支持

use Swoole\Server;

$server = new Server('0.0.0.0', 1883, SWOOLE_PROCESS, SWOOLE_SOCK_TCP);

$server->set([
    'open_mqtt_protocol' => true,  // 启用MQTT协议解析
    'package_max_length' => 2000000,
]);

$server->on('receive', function (Server $server, int $fd, int $reactorId, string $data) {
    // Swoole会自动解析MQTT包
    $mqtt = swoole_mqtt_decode($data);
    
    switch ($mqtt['type']) {
        case MQTT_CONNECT:
            // 处理连接请求
            $connack = swoole_mqtt_encode([
                'type' => MQTT_CONNACK,
                'code' => 0, // 连接成功
            ]);
            $server->send($fd, $connack);
            break;
            
        case MQTT_PUBLISH:
            // 处理发布消息
            $topic = $mqtt['topic'];
            $payload = $mqtt['payload'];
            echo "收到发布: Topic={$topic}, Payload={$payload}\n";
            
            // 转发给订阅者
            // ...
            break;
            
        case MQTT_SUBSCRIBE:
            // 处理订阅
            $topics = $mqtt['topics'];
            // ...
            break;
    }
});

$server->start();

3. 并发控制

3.1 协程锁 (Lock)

use Swoole\Coroutine;
use Swoole\Lock;

// 创建互斥锁
$lock = new Lock(SWOOLE_MUTEX);

$counter = 0;

// 启动100个协程同时累加
for ($i = 0; $i < 100; $i++) {
    go(function () use ($lock, &$counter) {
        for ($j = 0; $j < 100; $j++) {
            $lock->lock(); // 加锁
            $counter++;
            $lock->unlock(); // 解锁
        }
    });
}

Coroutine::sleep(1);
echo "计数器: {$counter}\n"; // 输出: 10000

锁类型对比

锁类型常量特点应用场景
互斥锁SWOOLE_MUTEX独占,不可递归临界区保护
读写锁SWOOLE_RWLOCK读共享,写独占读多写少
自旋锁SWOOLE_SPINLOCK忙等待,不会挂起锁持有时间极短
文件锁SWOOLE_FILELOCK跨进程进程间同步
信号量SWOOLE_SEM允许N个并发资源池

3.2 读写锁 (RWLock)

use Swoole\Lock;

$rwlock = new Lock(SWOOLE_RWLOCK);
$data = ['value' => 0];

// 读协程(可并发)
for ($i = 0; $i < 5; $i++) {
    go(function () use ($rwlock, &$data, $i) {
        $rwlock->lock_read();
        echo "读协程{$i}: {$data['value']}\n";
        co::sleep(1);
        $rwlock->unlock();
    });
}

// 写协程(独占)
go(function () use ($rwlock, &$data) {
    co::sleep(0.5);
    $rwlock->lock(); // 写锁
    echo "写协程开始\n";
    $data['value'] = 100;
    co::sleep(1);
    echo "写协程结束\n";
    $rwlock->unlock();
});

3.3 信号量 (Semaphore)

use Swoole\Coroutine;

// 创建连接池(最多3个连接)
class ConnectionPool
{
    private $pool = [];
    private $semaphore;
    
    public function __construct(int $size)
    {
        $this->semaphore = new Swoole\Lock(SWOOLE_SEM);
        
        // 初始化连接池
        for ($i = 0; $i < $size; $i++) {
            $this->pool[] = $this->createConnection();
        }
    }
    
    public function get()
    {
        $this->semaphore->lock(); // 获取信号量
        return array_pop($this->pool);
    }
    
    public function put($conn)
    {
        $this->pool[] = $conn;
        $this->semaphore->unlock(); // 释放信号量
    }
    
    private function createConnection()
    {
        // 创建数据库连接
        return new PDO('mysql:host=localhost;dbname=test', 'root', '');
    }
}

$pool = new ConnectionPool(3);

// 10个协程竞争3个连接
for ($i = 0; $i < 10; $i++) {
    go(function () use ($pool, $i) {
        $conn = $pool->get();
        echo "协程{$i}获得连接\n";
        co::sleep(1);
        $pool->put($conn);
        echo "协程{$i}归还连接\n";
    });
}

3.4 原子计数器 (Atomic)

use Swoole\Atomic;
use Swoole\Coroutine;

// 创建原子计数器
$atomic = new Atomic(0);

// 1000个协程并发累加
for ($i = 0; $i < 1000; $i++) {
    go(function () use ($atomic) {
        $atomic->add(1);  // 原子加1
    });
}

Coroutine::sleep(1);
echo "计数: " . $atomic->get() . "\n"; // 1000

// 常用操作
$atomic->add(5);      // 加5
$atomic->sub(3);      // 减3
$atomic->cmpset(7, 10); // 如果当前值是7,则设置为10
$atomic->get();       // 获取当前值
$atomic->set(100);    // 设置值

Atomic vs Lock 性能对比

  • Atomic:无锁操作,性能极高,但只支持整数
  • Lock:支持任意临界区,性能较低

4. 进程管理

4.1 多进程模型

use Swoole\Process;

// 创建子进程
$process = new Process(function (Process $worker) {
    echo "子进程ID: " . $worker->pid . "\n";
    echo "父进程ID: " . posix_getppid() . "\n";
    
    // 子进程逻辑
    for ($i = 0; $i < 5; $i++) {
        sleep(1);
        echo "子进程工作中: {$i}\n";
    }
}, false, 1, true); // 不重定向输入输出, 管道类型SOCK_STREAM, 启用协程

$pid = $process->start();
echo "启动子进程: {$pid}\n";

// 等待子进程退出
$ret = Process::wait(true); // 阻塞等待
echo "子进程退出: PID={$ret['pid']}, 退出码={$ret['code']}\n";

4.2 进程池 (Process Pool)

use Swoole\Process\Pool;

$pool = new Pool(4); // 4个Worker进程

// Worker进程启动回调
$pool->on('workerStart', function (Pool $pool, int $workerId) {
    echo "Worker#{$workerId} 启动\n";
    
    // 每个Worker处理任务
    while (true) {
        $data = $pool->pop(); // 从队列获取任务
        if ($data === false) {
            break;
        }
        
        echo "Worker#{$workerId} 处理: {$data}\n";
        sleep(1);
    }
});

// Worker进程停止回调
$pool->on('workerStop', function (Pool $pool, int $workerId) {
    echo "Worker#{$workerId} 停止\n";
});

$pool->start();

// 投递任务
for ($i = 0; $i < 100; $i++) {
    $pool->push("任务{$i}");
}

4.3 进程间通信 (IPC)

方案1:管道 (Pipe)
use Swoole\Process;

$process = new Process(function (Process $worker) {
    // 子进程向父进程发送数据
    $worker->write("Hello from child");
}, false, 2, true); // 管道类型: SOCK_DGRAM

$process->start();

// 父进程读取数据
$data = $process->read();
echo "父进程收到: {$data}\n";

// 父进程向子进程发送数据
$process->write("Hello from parent");

Process::wait(true);
方案2:消息队列 (Queue)
use Swoole\Process;

// 创建消息队列
$key = ftok(__FILE__, 'a');
$queue = msg_get_queue($key);

// 生产者进程
$producer = new Process(function () use ($queue) {
    for ($i = 0; $i < 10; $i++) {
        msg_send($queue, 1, "消息{$i}");
        sleep(1);
    }
});

// 消费者进程
$consumer = new Process(function () use ($queue) {
    while (true) {
        msg_receive($queue, 1, $msgtype, 1024, $message);
        echo "收到: {$message}\n";
    }
});

$producer->start();
$consumer->start();

Process::wait(true);
Process::wait(true);
方案3:共享内存 (Table)
use Swoole\Table;
use Swoole\Process;

// 创建共享内存表
$table = new Table(1024);
$table->column('id', Table::TYPE_INT);
$table->column('name', Table::TYPE_STRING, 64);
$table->column('price', Table::TYPE_FLOAT);
$table->create();

// 父进程写入
$table->set('product_1', ['id' => 1, 'name' => 'iPhone', 'price' => 5999.99]);

// 子进程读取
$process = new Process(function () use ($table) {
    $row = $table->get('product_1');
    var_dump($row);
});

$process->start();
Process::wait(true);

4.4 Server的进程模型

use Swoole\Server;

$server = new Server('0.0.0.0', 9501);

$server->set([
    'worker_num' => 4,      // Worker进程数(建议设置为CPU核数)
    'task_worker_num' => 2, // Task进程数
    'max_request' => 10000, // Worker最大处理请求数后重启
    'dispatch_mode' => 3,   // 抢占模式
]);

// Worker进程启动
$server->on('workerStart', function (Server $server, int $workerId) {
    if ($workerId >= $server->setting['worker_num']) {
        echo "Task Worker#{$workerId} 启动\n";
    } else {
        echo "Worker#{$workerId} 启动\n";
    }
});

// 接收请求
$server->on('receive', function (Server $server, int $fd, int $reactorId, string $data) {
    // 投递异步任务
    $server->task(['type' => 'email', 'to' => 'user@example.com']);
    
    // 立即返回
    $server->send($fd, "任务已投递");
});

// Task进程处理任务
$server->on('task', function (Server $server, int $taskId, int $srcWorkerId, $data) {
    echo "Task#{$taskId} 处理: " . json_encode($data) . "\n";
    
    // 处理耗时任务(如发送邮件)
    sleep(3);
    
    return "任务完成";
});

// Task完成回调
$server->on('finish', function (Server $server, int $taskId, string $data) {
    echo "Task#{$taskId} 完成: {$data}\n";
});

$server->start();

进程架构

Master进程
  ├─ Manager进程
  │    ├─ Worker进程#1
  │    ├─ Worker进程#2
  │    ├─ Worker进程#3
  │    ├─ Worker进程#4
  │    ├─ Task进程#1
  │    └─ Task进程#2
  └─ Reactor线程

5. 事件驱动模型

5.1 Reactor模型

核心概念:Swoole基于Reactor模型实现IO多路复用

                    ┌─────────────┐
                    │   Client    │
                    └──────┬──────┘
                           │
                    ┌──────▼──────┐
                    │  Reactor    │ (epoll/kqueue)
                    │  监听IO事件  │
                    └──────┬──────┘
                           │
        ┌──────────────────┼──────────────────┐
        │                  │                  │
   ┌────▼────┐       ┌────▼────┐       ┌────▼────┐
   │Worker #1│       │Worker #2│       │Worker #3│
   └─────────┘       └─────────┘       └─────────┘

5.2 异步IO

use Swoole\Coroutine\System;

// 异步文件读取
go(function () {
    $content = System::readFile('/tmp/test.txt');
    echo "文件内容: {$content}\n";
});

// 异步文件写入
go(function () {
    $result = System::writeFile('/tmp/test.txt', 'Hello Swoole');
    echo "写入结果: " . ($result ? '成功' : '失败') . "\n";
});

// 异步执行Shell命令
go(function () {
    $result = System::exec('ls -la');
    echo "命令输出: {$result['output']}\n";
    echo "退出码: {$result['code']}\n";
});

// 异步DNS查询
go(function () {
    $ip = System::gethostbyname('www.google.com');
    echo "IP: {$ip}\n";
});

// 异步sleep
go(function () {
    echo "开始等待\n";
    System::sleep(2); // 不会阻塞其他协程
    echo "等待结束\n";
});

5.3 事件循环

use Swoole\Event;
use Swoole\Timer;

// 添加读事件
$sock = stream_socket_client('tcp://www.baidu.com:80');
stream_set_blocking($sock, false);

Event::add($sock, function ($sock) {
    $data = fread($sock, 8192);
    echo "收到数据: {$data}\n";
    Event::del($sock);
});

// 添加定时器
Timer::tick(1000, function () {
    echo "每秒执行一次\n";
});

Timer::after(5000, function () {
    echo "5秒后执行\n";
});

// 延迟任务
Event::defer(function () {
    echo "当前EventLoop结束后执行\n";
});

// 循环回调
Event::cycle(function () {
    echo "每次EventLoop结束后执行\n";
});

6. 内存管理

6.1 Table - 共享内存表

use Swoole\Table;

// 创建高性能内存表
$table = new Table(1024); // 最大1024行

// 定义列结构
$table->column('id', Table::TYPE_INT);
$table->column('name', Table::TYPE_STRING, 64);
$table->column('price', Table::TYPE_FLOAT);
$table->column('quantity', Table::TYPE_INT);
$table->create();

// 写入数据
$table->set('product_1', [
    'id' => 1,
    'name' => 'iPhone 15',
    'price' => 5999.99,
    'quantity' => 100
]);

// 读取数据
$row = $table->get('product_1');
echo "产品: {$row['name']}, 价格: {$row['price']}\n";

// 更新数据
$table->set('product_1', ['quantity' => 99]);

// 原子操作
$table->incr('product_1', 'quantity', 1);  // +1
$table->decr('product_1', 'quantity', 1);  // -1

// 删除数据
$table->del('product_1');

// 遍历
foreach ($table as $key => $row) {
    echo "Key: {$key}, Data: " . json_encode($row) . "\n";
}

// 统计
echo "行数: " . $table->count() . "\n";

应用场景

  1. 统计计数器
$statsTable = new Table(1024);
$statsTable->column('request_count', Table::TYPE_INT);
$statsTable->column('error_count', Table::TYPE_INT);
$statsTable->create();

// 在Worker进程中统计
$server->on('request', function ($request, $response) use ($statsTable) {
    $statsTable->incr('stats', 'request_count');
});
  1. 会话存储
$sessionTable = new Table(10000);
$sessionTable->column('user_id', Table::TYPE_INT);
$sessionTable->column('username', Table::TYPE_STRING, 64);
$sessionTable->column('login_time', Table::TYPE_INT);
$sessionTable->create();
  1. 连接映射
$connTable = new Table(10000);
$connTable->column('fd', Table::TYPE_INT);
$connTable->column('user_id', Table::TYPE_INT);
$connTable->column('room_id', Table::TYPE_STRING, 32);
$connTable->create();

6.2 内存优化

连接池复用
class RedisPool
{
    private $pool;
    private $config;
    
    public function __construct(array $config, int $size = 10)
    {
        $this->config = $config;
        $this->pool = new \Swoole\Coroutine\Channel($size);
        
        // 预创建连接
        for ($i = 0; $i < $size; $i++) {
            $redis = $this->createConnection();
            $this->pool->push($redis);
        }
    }
    
    private function createConnection()
    {
        $redis = new \Swoole\Coroutine\Redis();
        $redis->connect($this->config['host'], $this->config['port']);
        if (isset($this->config['password'])) {
            $redis->auth($this->config['password']);
        }
        return $redis;
    }
    
    public function get()
    {
        if ($this->pool->isEmpty()) {
            // 池已空,创建临时连接
            return $this->createConnection();
        }
        return $this->pool->pop();
    }
    
    public function put($redis)
    {
        $this->pool->push($redis);
    }
}

// 使用
$pool = new RedisPool(['host' => '127.0.0.1', 'port' => 6379], 10);

go(function () use ($pool) {
    $redis = $pool->get();
    $redis->set('key', 'value');
    $pool->put($redis);
});
对象池
class ObjectPool
{
    private $pool;
    private $class;
    
    public function __construct(string $class, int $size = 100)
    {
        $this->class = $class;
        $this->pool = new \Swoole\Coroutine\Channel($size);
    }
    
    public function get()
    {
        if ($this->pool->isEmpty()) {
            return new $this->class();
        }
        return $this->pool->pop();
    }
    
    public function put($obj)
    {
        // 重置对象状态
        if (method_exists($obj, 'reset')) {
            $obj->reset();
        }
        $this->pool->push($obj);
    }
}

// 使用
class Request
{
    public $data = [];
    
    public function reset()
    {
        $this->data = [];
    }
}

$pool = new ObjectPool(Request::class, 100);

7. 高频学习题

7.1 基础概念

Q1: Swoole的协程和PHP-FPM有什么区别?

A:

  • PHP-FPM: 同步阻塞,每个请求一个进程,IO阻塞时进程挂起,资源浪费
  • Swoole协程: 异步非阻塞,单进程处理多个请求,IO阻塞时自动切换协程,资源利用率高

对比:

维度PHP-FPMSwoole协程
并发模型多进程协程
内存占用高(30-50MB/进程)低(2KB/协程)
并发能力低(100-200)高(10万+)
数据库连接每次创建连接池复用
性能高(5-10倍)

Q2: Swoole如何实现热重载?

A:

// 方案1: 监听文件变化
use Swoole\Timer;

$server->on('workerStart', function ($server, $workerId) {
    // 只在Worker#0中监听
    if ($workerId == 0) {
        Timer::tick(2000, function () use ($server) {
            // 检查文件修改时间
            $files = get_included_files();
            foreach ($files as $file) {
                if (filemtime($file) > $_SERVER['REQUEST_TIME']) {
                    $server->reload(); // 重启Worker
                    break;
                }
            }
        });
    }
});

// 方案2: 使用inotify扩展
$inotify = inotify_init();
$watch = inotify_add_watch($inotify, '/path/to/src', IN_MODIFY);

while (true) {
    $events = inotify_read($inotify);
    if ($events) {
        exec('kill -USR1 ' . file_get_contents('/tmp/server.pid'));
    }
}

Q3: Swoole如何实现优雅退出?

A:

$server->on('workerStop', function (Server $server, int $workerId) {
    // 关闭数据库连接
    $db->close();
    
    // 清理资源
    // ...
});

// 信号处理
Process::signal(SIGTERM, function () use ($server) {
    echo "收到停止信号,开始优雅退出\n";
    
    // 停止接收新连接
    $server->shutdown();
    
    // 等待现有请求处理完毕
    Timer::after(5000, function () {
        exit(0);
    });
});

7.2 性能优化

Q4: Swoole如何优化高并发场景?

A:

  1. 连接池: 复用MySQL/Redis连接
  2. 协程池: 限制并发协程数量
  3. Table: 使用共享内存代替Redis
  4. 异步Task: 耗时操作投递到Task进程
  5. 静态文件: 使用Nginx处理,Swoole只处理动态请求
// 示例:协程池 + 连接池
class HighPerformanceServer
{
    private $pool;      // 协程池
    private $dbPool;    // 数据库连接池
    
    public function __construct()
    {
        $this->pool = new Channel(1000); // 最大1000并发
        $this->dbPool = new MysqlPool(10); // 10个连接
        
        // 初始化协程池令牌
        for ($i = 0; $i < 1000; $i++) {
            $this->pool->push(true);
        }
    }
    
    public function handleRequest($request, $response)
    {
        go(function () use ($request, $response) {
            $this->pool->pop(); // 获取令牌
            
            try {
                $db = $this->dbPool->get();
                $result = $db->query('SELECT * FROM users');
                $this->dbPool->put($db);
                
                $response->end(json_encode($result));
            } finally {
                $this->pool->push(true); // 归还令牌
            }
        });
    }
}

Q5: Swoole如何避免内存泄漏?

A:

  1. 及时释放变量
$server->on('request', function ($request, $response) {
    $data = file_get_contents('large_file.txt');
    // 处理数据
    unset($data); // 及时释放
});
  1. 使用对象池
// 复用对象,避免频繁创建销毁
$objectPool = new Channel(100);
  1. 定期重启Worker
$server->set([
    'max_request' => 10000, // 每个Worker处理10000请求后重启
]);
  1. 避免全局变量累积
// ❌ 错误:全局数组不断增长
global $cache;
$cache[$key] = $value;

// ✅ 正确:使用Table或定期清理
$table->set($key, $value);

7.3 常见陷阱

Q6: 为什么在协程中使用全局变量会出现数据错乱?

A: 协程之间共享全局变量,并发访问时会互相覆盖。

// ❌ 错误示例
global $userId;

go(function () {
    global $userId;
    $userId = 1001;
    co::sleep(1);
    echo $userId; // 可能被其他协程修改
});

go(function () {
    global $userId;
    $userId = 1002;
});

// ✅ 正确:使用协程上下文
go(function () {
    Context::put('user_id', 1001);
    co::sleep(1);
    echo Context::get('user_id'); // 1001
});

Q7: 如何在Swoole中使用MySQL/Redis?

A:

// ❌ 错误:使用同步阻塞的扩展
$pdo = new PDO(...);
$pdo->query(...); // 阻塞整个进程

// ✅ 正确:使用协程客户端
go(function () {
    $db = new Swoole\Coroutine\MySQL();
    $db->connect([
        'host' => '127.0.0.1',
        'user' => 'root',
        'password' => '',
        'database' => 'test',
    ]);
    
    $result = $db->query('SELECT * FROM users'); // 协程自动yield
});

Q8: Swoole如何处理慢查询?

A:

use Swoole\Coroutine;

// 方案1: 超时控制
go(function () {
    $result = null;
    $channel = new Channel(1);
    
    // 查询协程
    go(function () use ($channel) {
        $db = new Coroutine\MySQL();
        $db->connect([...]);
        $result = $db->query('SELECT ...');
        $channel->push($result);
    });
    
    // 等待2秒
    $result = $channel->pop(2.0);
    if ($result === false) {
        echo "查询超时\n";
    }
});

// 方案2: 投递到Task进程
$server->task(['type' => 'slow_query', 'sql' => '...']);

7.4 架构设计

Q9: 如何设计一个高可用的Swoole服务?

A:

                  ┌──────────┐
                  │  Nginx   │ (负载均衡)
                  └────┬─────┘
                       │
          ┌────────────┼────────────┐
          │            │            │
     ┌────▼───┐   ┌───▼────┐  ┌───▼────┐
     │Swoole#1│   │Swoole#2│  │Swoole#3│
     └────┬───┘   └───┬────┘  └───┬────┘
          │           │           │
          └───────────┼───────────┘
                      │
              ┌───────▼────────┐
              │ MySQL(主从)    │
              │ Redis(哨兵)    │
              └────────────────┘

关键点:

  1. 多实例部署: 至少3个Swoole实例
  2. 健康检查: Nginx定期检测后端存活
  3. 数据库高可用: MySQL主从、Redis哨兵
  4. 优雅重启: 滚动更新,避免服务中断
  5. 监控告警: 监控QPS、内存、错误率

Q10: Swoole适合哪些业务场景?

A:

✅ 适合场景:

  1. 即时通讯: WebSocket聊天室、客服系统
  2. 游戏服务器: 实时对战、在线游戏
  3. 物联网: MQTT消息推送、设备管理
  4. API网关: 高并发接口聚合
  5. 微服务: RPC服务、服务治理
  6. 实时推送: 消息通知、数据同步

❌ 不适合场景:

  1. CPU密集型: 图像处理、视频转码(建议异步队列)
  2. 遗留项目: 大量同步阻塞代码难以改造
  3. 简单CRUD: 低并发场景用PHP-FPM更简单

8. 实战案例

8.1 高性能API网关

use Swoole\Http\Server;
use Swoole\Coroutine\Http\Client;

class ApiGateway
{
    private $server;
    private $routes = [];
    
    public function __construct()
    {
        $this->server = new Server('0.0.0.0', 9501);
        
        $this->server->set([
            'worker_num' => swoole_cpu_num() * 2,
            'max_request' => 10000,
        ]);
        
        $this->server->on('request', [$this, 'onRequest']);
    }
    
    public function route(string $path, string $upstream)
    {
        $this->routes[$path] = $upstream;
    }
    
    public function onRequest($request, $response)
    {
        $path = $request->server['request_uri'];
        
        if (!isset($this->routes[$path])) {
            $response->status(404);
            $response->end('Not Found');
            return;
        }
        
        go(function () use ($request, $response, $path) {
            $upstream = $this->routes[$path];
            
            // 转发请求
            $client = new Client($upstream['host'], $upstream['port']);
            $client->setHeaders($request->header ?? []);
            $client->post($path, $request->rawContent());
            
            // 返回响应
            $response->status($client->statusCode);
            $response->end($client->body);
            
            $client->close();
        });
    }
    
    public function start()
    {
        $this->server->start();
    }
}

// 使用
$gateway = new ApiGateway();
$gateway->route('/user', ['host' => '127.0.0.1', 'port' => 8001]);
$gateway->route('/order', ['host' => '127.0.0.1', 'port' => 8002]);
$gateway->start();

8.2 分布式任务调度

use Swoole\Table;
use Swoole\Timer;

class TaskScheduler
{
    private $tasks;
    private $workers;
    
    public function __construct(int $workerNum = 4)
    {
        // 任务表
        $this->tasks = new Table(10000);
        $this->tasks->column('name', Table::TYPE_STRING, 64);
        $this->tasks->column('cron', Table::TYPE_STRING, 64);
        $this->tasks->column('next_run', Table::TYPE_INT);
        $this->tasks->column('status', Table::TYPE_INT);
        $this->tasks->create();
        
        // 启动Worker
        for ($i = 0; $i < $workerNum; $i++) {
            $this->workers[] = $this->createWorker($i);
        }
        
        // 定时调度
        Timer::tick(1000, [$this, 'dispatch']);
    }
    
    public function addTask(string $name, string $cron, callable $callback)
    {
        $this->tasks->set($name, [
            'name' => $name,
            'cron' => $cron,
            'next_run' => $this->parseNext($cron),
            'status' => 0,
        ]);
        
        // 保存回调
        $this->callbacks[$name] = $callback;
    }
    
    public function dispatch()
    {
        $now = time();
        
        foreach ($this->tasks as $name => $task) {
            if ($task['next_run'] <= $now && $task['status'] == 0) {
                // 执行任务
                go(function () use ($name, $task) {
                    $this->tasks->set($name, ['status' => 1]);
                    
                    try {
                        $this->callbacks[$name]();
                    } finally {
                        // 计算下次执行时间
                        $this->tasks->set($name, [
                            'next_run' => $this->parseNext($task['cron']),
                            'status' => 0,
                        ]);
                    }
                });
            }
        }
    }
    
    private function parseNext(string $cron): int
    {
        // 解析cron表达式
        // 简化实现
        return time() + 60;
    }
}

// 使用
$scheduler = new TaskScheduler(4);
$scheduler->addTask('send_email', '*/5 * * * *', function () {
    echo "发送邮件\n";
});
$scheduler->addTask('clean_cache', '0 2 * * *', function () {
    echo "清理缓存\n";
});

9. 总结

核心要点

  1. 协程: 理解协程调度、上下文隔离
  2. 网络编程: 掌握TCP/UDP/WebSocket/HTTP服务器
  3. 并发控制: 熟练使用Lock、Channel、Atomic
  4. 进程管理: 理解多进程模型、进程间通信
  5. 性能优化: 连接池、对象池、内存表

学习路径

  1. 入门: 创建HTTP服务器、协程基础
  2. 进阶: 连接池、任务调度、WebSocket
  3. 高级: 性能调优、架构设计、分布式

推荐资源


学习建议:

  • 重点掌握协程原理和应用场景
  • 了解Swoole与传统PHP-FPM的区别
  • 准备实际项目案例(如即时通讯、高并发API)
  • 关注性能优化和架构设计