Files
vibe_coding/.cursor/rules/016-swoole.mdc
2026-03-05 21:27:11 +08:00

233 lines
6.0 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
description: "Swoole 协程与高并发编程规范 — 协程安全/连接池/Worker配置/内存管理"
globs:
- "Case-Database-Backend/app/**/*.php"
- "Case-Database-Backend/config/**/*.php"
- "Case-Database-Backend/bin/hyperf.php"
alwaysApply: false
---
# ⚡ Swoole Coroutine & High-Concurrency Standards
## 核心原则
Swoole 是常驻内存的协程服务器,与传统 PHP-FPM 有本质区别。
**每个请求共享 Worker 进程内存**,必须避免全局状态污染。
## 协程安全规则
### 绝对禁止
```php
// ❌ 全局变量存储请求数据(会被其他协程覆盖)
global $currentUser;
// ❌ 静态属性存储请求级数据
class UserContext {
public static ?User $user = null; // WRONG: shared across coroutines
}
// ❌ 阻塞 I/O 函数(会阻塞整个 Worker
file_get_contents('https://api.example.com'); // use Guzzle coroutine client
sleep(5); // use Coroutine::sleep()
flock($fp, LOCK_EX); // use Redis distributed lock
// ❌ 非协程安全的全局单例
$pdo = new PDO(...); // use connection pool instead
```
### 正确做法
```php
// ✅ 使用 Hyperf Context 传递请求数据
use Hyperf\Context\Context;
// 在中间件中设置
Context::set('current_user', $user);
// 在任意位置读取(协程安全)
$user = Context::get('current_user');
// ✅ 协程安全的延时
use Swoole\Coroutine;
Coroutine::sleep(0.1);
// ✅ 使用协程 HTTP 客户端
use Hyperf\Guzzle\ClientFactory;
$client = $this->clientFactory->create();
$response = $client->get('https://api.example.com');
```
## 连接池配置
### MySQL 连接池
```php
// config/autoload/databases.php
'pool' => [
// 公式: min = Worker数, max = Worker数 * 2 ~ 4
'min_connections' => (int) env('DB_POOL_MIN', 5),
'max_connections' => (int) env('DB_POOL_MAX', 50),
'connect_timeout' => 10.0, // seconds
'wait_timeout' => 3.0, // wait for available connection
'heartbeat' => -1, // disable heartbeat
'max_idle_time' => 60, // recycle idle connections
],
```
### Redis 连接池
```php
// config/autoload/redis.php
return [
'default' => [
'host' => env('REDIS_HOST', 'localhost'),
'port' => (int) env('REDIS_PORT', 6379),
'auth' => env('REDIS_AUTH', null),
'db' => (int) env('REDIS_DB', 0),
'pool' => [
'min_connections' => 5,
'max_connections' => 30,
'connect_timeout' => 10.0,
'wait_timeout' => 3.0,
'heartbeat' => -1,
'max_idle_time' => 60,
],
],
];
```
### 连接池容量计算
```
单 Worker 最大协程数: max_coroutine (default: 100000)
实际并发协程数 ≈ QPS * avg_response_time
DB Pool Max = ceil(concurrent_coroutines * db_query_ratio)
Redis Pool Max = ceil(concurrent_coroutines * redis_query_ratio)
示例 (百万级):
- 4 Worker, 每 Worker 1000 并发协程
- DB 查询占比 60% → DB Pool Max = 1000 * 0.6 = 600 (per worker)
- 实际受 MySQL max_connections 限制,需配合读写分离
```
## Worker 进程配置
```php
// config/autoload/server.php
'settings' => [
'worker_num' => (int) env('SWOOLE_WORKER_NUM', swoole_cpu_num() * 2),
'task_worker_num' => (int) env('SWOOLE_TASK_WORKER_NUM', 4),
'max_request' => 10000, // restart worker after N requests (prevent memory leak)
'max_coroutine' => 100000, // max coroutines per worker
'enable_coroutine' => true,
'open_tcp_nodelay' => true,
'socket_buffer_size' => 3 * 1024 * 1024,
'buffer_output_size' => 3 * 1024 * 1024,
'package_max_length' => 5 * 1024 * 1024,
'heartbeat_check_interval' => 60,
'heartbeat_idle_time' => 120,
],
```
## 协程并发控制
```php
use Hyperf\Coroutine\Parallel;
// ✅ 并行执行多个无依赖的 I/O 操作
$parallel = new Parallel(10); // max concurrency = 10
$parallel->add(function () {
return $this->orderService->getStatistics($orderId);
});
$parallel->add(function () {
return $this->paymentService->getPayments($orderId);
});
$parallel->add(function () {
return $this->deliveryService->getDeliveries($orderId);
});
[$stats, $payments, $deliveries] = $parallel->wait();
```
```php
use Hyperf\Coroutine\WaitGroup;
// ✅ WaitGroup: wait for all coroutines to complete
$wg = new WaitGroup();
$results = [];
$wg->add(1);
go(function () use ($wg, &$results) {
$results['users'] = $this->userService->getOnlineCount();
$wg->done();
});
$wg->add(1);
go(function () use ($wg, &$results) {
$results['orders'] = $this->orderService->getTodayCount();
$wg->done();
});
$wg->wait(5.0); // timeout 5s
```
## 内存管理
- `max_request = 10000`: Worker 处理 N 个请求后自动重启,防止内存泄漏
- 避免在全局/静态变量中累积数据
- 大数组处理完后手动 `unset()`
- 使用 `memory_get_usage()` 监控内存
- 对象生命周期与请求绑定,不跨请求持有引用
```php
// ✅ 大数据分批处理
ProductionOrder::query()
->where('status', 'pending')
->chunk(500, function ($orders) {
foreach ($orders as $order) {
$this->processOrder($order);
}
// chunk 结束后自动释放内存
});
```
## 定时任务
```php
use Hyperf\Crontab\Annotation\Crontab;
#[Crontab(
name: 'CheckPaymentTimeout',
rule: '*/5 * * * *',
memo: 'Check payment timeout orders',
singleton: true, // prevent duplicate execution
onOneServer: true, // only run on one server in cluster
mutexPool: 'default',
)]
class CheckPaymentTimeoutTask
{
public function execute(): void
{
// batch processing with chunk
}
}
```
## 性能监控
```php
// Swoole Server status
$server = $this->container->get(ServerInterface::class);
$stats = $server->stats();
// Returns: start_time, connection_num, request_count, worker_request_count, coroutine_num
// Redis monitor
$redis = $this->container->get(RedisFactory::class)->get('default');
$info = $redis->info();
```