初始化
This commit is contained in:
232
.cursor/rules/016-swoole.mdc
Normal file
232
.cursor/rules/016-swoole.mdc
Normal file
@@ -0,0 +1,232 @@
|
||||
---
|
||||
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();
|
||||
```
|
||||
Reference in New Issue
Block a user