Масштабирование и работа под высокой нагрузкой
Руководство по масштабированию Localzet Server для обработки высоких нагрузок и больших объемов трафика.
Стратегии масштабирования
Вертикальное масштабирование
Увеличение производительности одного сервера.
Оптимальное количество процессов
// Автоматическое определение по количеству CPU
$server->count = cpu_count();
// Или явное указание
$server->count = 8; // Для 8-core сервера
Рекомендации:
- Для CPU-intensive задач:
count = CPU cores - Для I/O-intensive задач:
count = CPU cores × 2 - Максимум:
count = CPU cores × 4(больше уже неэффективно)
Настройка буферов
// Увеличение размеров буферов для высокой нагрузки
TcpConnection::$defaultMaxSendBufferSize = 4194304; // 4MB
TcpConnection::$defaultMaxPackageSize = 20971520; // 20MB
Горизонтальное масштабирование
Распределение нагрузки между несколькими серверами.
Архитектура с Load Balancer
┌─────────────┐
│ Load │
│ Balancer │
│ (Nginx/HA) │
└──────┬───────┘
│
┌──────────────────┼──────────────────┐
│ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│ Server 1│ │ Server 2│ │ Server 3│
│ :8080 │ │ :8080 │ │ :8080 │
└─────────┘ └─────────┘ └─────────┘
Настройка Nginx как Load Balancer
upstream localzet_backend {
least_conn; # Балансировка по количеству соединений
server 127.0.0.1:8080 weight=1 max_fails=3 fail_timeout=30s;
server 127.0.0.1:8081 weight=1 max_fails=3 fail_timeout=30s;
server 127.0.0.1:8082 weight=1 max_fails=3 fail_timeout=30s;
}
server {
listen 80;
location / {
proxy_pass http://localzet_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# WebSocket поддержка
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Оптимизация для высокой нагрузки
1. Настройка операционной системы
Linux оптимизации
# Увеличение лимитов
echo "* soft nofile 65535" >> /etc/security/limits.conf
echo "* hard nofile 65535" >> /etc/security/limits.conf
# Оптимизация TCP
cat >> /etc/sysctl.conf << EOF
# Максимальное количество соединений в очереди
net.core.somaxconn = 65535
# Переиспользование TIME_WAIT сокетов
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30
# Увеличение диапазона портов
net.ipv4.ip_local_port_range = 1024 65535
# Оптимизация буферов
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
# Отключение синхронизации для производительности
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_syn_retries = 2
net.ipv4.tcp_synack_retries = 2
EOF
sysctl -p
2. Использование SO_REUSEPORT
$server = new Server('http://0.0.0.0:8080');
$server->reusePort = true; // Распределение на уровне ядра
$server->count = cpu_count();
Преимущества:
- Распределение соединений на уровне ядра Linux
- Исключение конкуренции между процессами
- Улучшенная производительность при высокой нагрузке
3. Выбор оптимального Event Loop
Приоритет выбора:
- libuv (ext-uv) - максимальная производительность
- libev (ext-ev) - высокая производительность
- libevent (ext-event) - стабильная производительность
- stream_select - универсальный fallback
Установка расширений:
# libuv
pecl install uv
# libev
pecl install ev
# libevent
pecl install event
4. Оптимизация памяти
Управление кешами
// Настройка размера кеша запросов
// В Http.php уже реализовано автоматическое управление
// Для пользовательских протоколов:
static $cache = [];
const MAX_CACHE_SIZE = 512;
if (count($cache) > MAX_CACHE_SIZE) {
// LRU удаление
unset($cache[key($cache)]);
}
Минимизация аллокаций
// Переиспользование объектов
static $pool = [];
function getConnection() {
if (empty($pool)) {
return new Connection();
}
return array_pop($pool);
}
function releaseConnection($conn) {
$conn->reset();
$pool[] = $conn;
}
Мониторинг и метрики
Встроенная статистика
// Статистика соединений
$stats = ConnectionInterface::$statistics;
// [
// 'connection_count' => 1234,
// 'total_request' => 56789,
// 'throw_exception' => 5,
// 'send_fail' => 2
// ]
// Получение статуса серверов
$servers = Server::getAllServers();
foreach ($servers as $server) {
echo "Server: {$server->name}\n";
echo "Connections: " . count($server->connections) . "\n";
}
Кастомный мониторинг
// Сбор метрик
class Metrics {
private static array $metrics = [];
public static function increment(string $metric, int $value = 1): void {
self::$metrics[$metric] = (self::$metrics[$metric] ?? 0) + $value;
}
public static function get(string $metric): int {
return self::$metrics[$metric] ?? 0;
}
public static function getAll(): array {
return self::$metrics;
}
}
// Использование
$server->onMessage = function($conn, $req) {
Metrics::increment('requests.total');
Metrics::increment('requests.by_path.' . $req->path());
// Обработка...
};
Экспорт метрик
// Endpoint для Prometheus
$server->onMessage = function($conn, $req) {
if ($req->path() === '/metrics') {
$metrics = Metrics::getAll();
$prometheus = '';
foreach ($metrics as $name => $value) {
$prometheus .= "$name $value\n";
}
$conn->send($prometheus);
}
};
Обработка пиковых нагрузок
Rate Limiting
class RateLimiter {
private static array $requests = [];
private const MAX_REQUESTS = 100;
private const WINDOW = 1; // секунда
public static function check(string $key): bool {
$now = time();
$window = floor($now / self::WINDOW);
$windowKey = "$key:$window";
if (!isset(self::$requests[$windowKey])) {
self::$requests[$windowKey] = 0;
}
if (++self::$requests[$windowKey] > self::MAX_REQUESTS) {
return false; // Превышен лимит
}
return true;
}
public static function cleanup(): void {
$now = time();
$currentWindow = floor($now / self::WINDOW);
foreach (self::$requests as $key => $value) {
$window = (int)explode(':', $key)[1];
if ($window < $currentWindow - 10) {
unset(self::$requests[$key]);
}
}
}
}
// Очистка каждые 60 секунд
Timer::repeat(60.0, [RateLimiter::class, 'cleanup']);
// Использование
$server->onMessage = function($conn, $req) {
$ip = $conn->getRemoteIp();
if (!RateLimiter::check($ip)) {
$conn->close(new Response(429, [], 'Too Many Requests'));
return;
}
// Обработка запроса...
};
Connection Pooling
class ConnectionPool {
private array $pool = [];
private int $maxSize;
private string $address;
public function __construct(string $address, int $maxSize = 100) {
$this->address = $address;
$this->maxSize = $maxSize;
}
public function get(): AsyncTcpConnection {
if (!empty($this->pool)) {
return array_pop($this->pool);
}
$conn = new AsyncTcpConnection($this->address);
$conn->connect();
return $conn;
}
public function release(AsyncTcpConnection $conn): void {
if (count($this->pool) < $this->maxSize &&
$conn->getStatus() === AsyncTcpConnection::STATUS_ESTABLISHED) {
$this->pool[] = $conn;
} else {
$conn->close();
}
}
}
Распределенное развертывание
Redis для общих данных
use Redis;
class SharedState {
private static Redis $redis;
public static function init(): void {
self::$redis = new Redis();
self::$redis->connect('127.0.0.1', 6379);
}
public static function increment(string $key, int $value = 1): int {
return self::$redis->incrBy($key, $value);
}
public static function set(string $key, mixed $value, int $ttl = 0): bool {
return self::$redis->setex($key, $ttl, serialize($value));
}
public static function get(string $key): mixed {
$data = self::$redis->get($key);
return $data ? unserialize($data) : null;
}
}
// Инициализация при старте сервера
$server->onServerStart = function($server) {
SharedState::init();
};
Pub/Sub для межсерверной коммуникации
class MessageBus {
private static Redis $redis;
public static function publish(string $channel, mixed $message): void {
self::$redis->publish($channel, serialize($message));
}
public static function subscribe(string $channel, callable $callback): void {
$pubsub = self::$redis->pubSubLoop(['subscribe' => [$channel]]);
foreach ($pubsub as $message) {
if ($message->kind === 'message') {
$data = unserialize($message->payload);
$callback($data);
}
}
}
}
// Отправка сообщения на все серверы
MessageBus::publish('notifications', [
'type' => 'user_online',
'user_id' => 123
]);
Профилирование и отладка
Профилирование производительности
class Profiler {
private static array $timings = [];
public static function start(string $name): void {
self::$timings[$name] = [
'start' => microtime(true),
'memory' => memory_get_usage()
];
}
public static function end(string $name): array {
if (!isset(self::$timings[$name])) {
return [];
}
$timing = self::$timings[$name];
unset(self::$timings[$name]);
return [
'time' => microtime(true) - $timing['start'],
'memory' => memory_get_usage() - $timing['memory']
];
}
}
// Использование
$server->onMessage = function($conn, $req) {
Profiler::start('request_processing');
// Обработка запроса
$result = processRequest($req);
$stats = Profiler::end('request_processing');
if ($stats['time'] > 1.0) {
Server::log("Slow request: {$stats['time']}s");
}
$conn->send($result);
};
Рекомендации
Для высоких нагрузок
- Используйте
SO_REUSEPORTдля лучшего распределения - Установите оптимальный Event Loop (libuv/libev)
- Настройте операционную систему для высокой нагрузки
- Мониторьте метрики и настраивайте алерты
- Используйте горизонтальное масштабирование при необходимости
Для низкой латентности
- Минимизируйте обработку в
onMessage - Используйте асинхронные операции для I/O
- Оптимизируйте протоколы прикладного уровня
- Кешируйте часто используемые данные
- Используйте быстрый Event Loop

