Swoole 底层核心学习手册
适用版本: Swoole 5.x
文档类型: 学习必备 + 实战指南
更新时间: 2025-10-26
目录
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秒后)
调度原理:
- 协程在遇到IO操作时主动让出CPU(yield)
- Swoole的EventLoop监听IO事件
- 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对比:
| 特性 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接 | 无连接 |
| 可靠性 | 可靠传输 | 不可靠 |
| 顺序 | 有序 | 无序 |
| 速度 | 较慢 | 快 |
| 应用场景 | 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";
应用场景:
- 统计计数器
$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');
});
- 会话存储
$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();
- 连接映射
$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-FPM | Swoole协程 |
|---|---|---|
| 并发模型 | 多进程 | 协程 |
| 内存占用 | 高(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:
- 连接池: 复用MySQL/Redis连接
- 协程池: 限制并发协程数量
- Table: 使用共享内存代替Redis
- 异步Task: 耗时操作投递到Task进程
- 静态文件: 使用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:
- 及时释放变量
$server->on('request', function ($request, $response) {
$data = file_get_contents('large_file.txt');
// 处理数据
unset($data); // 及时释放
});
- 使用对象池
// 复用对象,避免频繁创建销毁
$objectPool = new Channel(100);
- 定期重启Worker
$server->set([
'max_request' => 10000, // 每个Worker处理10000请求后重启
]);
- 避免全局变量累积
// ❌ 错误:全局数组不断增长
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(哨兵) │
└────────────────┘
关键点:
- 多实例部署: 至少3个Swoole实例
- 健康检查: Nginx定期检测后端存活
- 数据库高可用: MySQL主从、Redis哨兵
- 优雅重启: 滚动更新,避免服务中断
- 监控告警: 监控QPS、内存、错误率
Q10: Swoole适合哪些业务场景?
A:
✅ 适合场景:
- 即时通讯: WebSocket聊天室、客服系统
- 游戏服务器: 实时对战、在线游戏
- 物联网: MQTT消息推送、设备管理
- API网关: 高并发接口聚合
- 微服务: RPC服务、服务治理
- 实时推送: 消息通知、数据同步
❌ 不适合场景:
- CPU密集型: 图像处理、视频转码(建议异步队列)
- 遗留项目: 大量同步阻塞代码难以改造
- 简单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. 总结
核心要点
- 协程: 理解协程调度、上下文隔离
- 网络编程: 掌握TCP/UDP/WebSocket/HTTP服务器
- 并发控制: 熟练使用Lock、Channel、Atomic
- 进程管理: 理解多进程模型、进程间通信
- 性能优化: 连接池、对象池、内存表
学习路径
- 入门: 创建HTTP服务器、协程基础
- 进阶: 连接池、任务调度、WebSocket
- 高级: 性能调优、架构设计、分布式
推荐资源
- 官方文档: wiki.swoole.com
- GitHub: github.com/swoole/swoo…
- Swoole社区: wenda.swoole.com
学习建议:
- 重点掌握协程原理和应用场景
- 了解Swoole与传统PHP-FPM的区别
- 准备实际项目案例(如即时通讯、高并发API)
- 关注性能优化和架构设计