Как хранить сессии в Redis для нескольких серверов PHP (2016)
Коротко: Установите phpredis расширение или predis/predis, в php.ini укажите session.save_handler = redis и session.save_path = "tcp://redis-server:6379". Все PHP-серверы пишут сессии в один Redis — load balancer может отправлять запросы на любой сервер. Также перенесите файловый кэш на Redis.
Проблема: сессии на нескольких серверах
Без Redis (файловые сессии):
┌─────────────────┐
Запрос 1 ────────► │ Server 1 │ ← Сессия создана здесь
└─────────────────┘
Запрос 2 ─────────► Сессия НЕ НАЙДЕНА
(попал на Server 2) └─────────────────┘
│ Server 2 │ ← Файла сессии нет!
└─────────────────┘
С Redis:
┌─────────────────┐
Запрос 1 ────────► │ Server 1 │ ──► Redis ─ сессия сохранена
└─────────────────┘
Запрос 2 ────────► │ Server 2 │ ──► Redis ─ сессия найдена ✓
└─────────────────┘
Вариант 1: php.ini (нативный Redis handler)
# Установить phpredis расширение (быстрее Predis)
sudo apt-get install php7.0-redis
# Или:
sudo pecl install redis
echo "extension=redis.so" | sudo tee /etc/php/7.0/mods-available/redis.ini
sudo phpenmod redis
php -m | grep redis # Должно отображаться: redis
; /etc/php/7.0/fpm/php.ini
; Или /etc/php/7.0/fpm/conf.d/99-redis-sessions.ini
[Session]
session.save_handler = redis
session.save_path = "tcp://10.0.0.100:6379"
; Если Redis с паролем:
; session.save_path = "tcp://10.0.0.100:6379?auth=yourpassword"
; Если Redis Sentinel:
; session.save_path = "tcp://10.0.0.100:26379?sentinel=mymaster"
session.gc_maxlifetime = 7200 ; Сессия живёт 2 часа
session.cookie_secure = 1 ; HTTPS only
session.cookie_httponly = 1 ; Не доступна из JS
session.cookie_samesite = Strict ; CSRF защита (PHP 7.3+)
sudo service php7.0-fpm restart
Вариант 2: Laravel Session Driver (удобнее для Laravel)
# .env
SESSION_DRIVER=redis
SESSION_LIFETIME=120 # Минуты
REDIS_HOST=10.0.0.100
REDIS_PASSWORD=null
REDIS_PORT=6379
// config/database.php
'redis' => [
'client' => 'predis', // или 'phpredis'
'default' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => 0,
],
'session' => [
// Отдельная Redis database для сессий
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => 1, // DB 1 для сессий, DB 0 для кэша
],
],
// config/session.php
'connection' => 'session', // Использовать Redis connection 'session'
Nginx: Load Balancing
# /etc/nginx/nginx.conf (на load balancer)
upstream php_servers {
least_conn; # Round-robin замените на least_conn для лучшего баланса
server 10.0.0.1:80 weight=1; # PHP Server 1
server 10.0.0.2:80 weight=1; # PHP Server 2
server 10.0.0.3:80 weight=1; # PHP Server 3
keepalive 32; # Сохранять keep-alive соединения
}
server {
listen 80;
server_name myapp.ru;
location / {
proxy_pass http://php_servers;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# НЕ используйте ip_hash (sticky sessions) — это противоречит цели
# Сессии в Redis = любой сервер может обработать любой запрос
}
}
Redis конфигурация для сессий
# /etc/redis/redis.conf на Redis сервере
# Привязка к приватной сети
bind 10.0.0.100
# Пароль (обязательно если Redis открыт по сети)
requirepass yourStrongPassword
# Persistence: RDB для восстановления сессий после рестарта
save 900 1 # Сохранить если 1 изменение за 900 секунд
save 300 10
# Максимальный размер памяти
maxmemory 512mb
# Политика вытеснения для сессий: volatile-lru (вытеснять с TTL)
# НЕ allkeys-lru (вытеснит все ключи включая важные сессии)
maxmemory-policy volatile-lru
# Мониторинг
slowlog-log-slower-than 10000 # Логировать запросы > 10ms
slowlog-max-len 128
Проверка что сессии в Redis
# Авторизоваться в приложении, затем:
redis-cli -h 10.0.0.100
> AUTH yourStrongPassword
> KEYS "PHPREDIS_SESSION:*"
# или для Laravel:
> KEYS "laravel:session*"
# Посмотреть содержимое сессии:
> GET PHPREDIS_SESSION:abc123...
# Вывод: сериализованные данные сессии
# Мониторинг в реальном времени:
> MONITOR
# Покажет все команды — убедитесь что сессии читаются/пишутся
Что делать если Redis недоступен
<?php
// Graceful degradation: если Redis упал — fallback на файловые сессии
class ResilientSessionHandler implements SessionHandlerInterface {
private $primary; // Redis handler
private $fallback; // File handler
public function read($sessionId): string {
try {
return $this->primary->read($sessionId);
} catch (RedisException $e) {
error_log('Redis session unavailable, using file fallback: ' . $e->getMessage());
return $this->fallback->read($sessionId);
}
}
public function write($sessionId, $sessionData): bool {
try {
return $this->primary->write($sessionId, $sessionData);
} catch (RedisException $e) {
return $this->fallback->write($sessionId, $sessionData);
}
}
// ... остальные методы аналогично
}
Результаты после Redis-сессий
| Метрика | File сессии (1 сервер) | Redis сессии (3 сервера) |
|---|---|---|
| Пиковая нагрузка | 400 req/s | 1,200 req/s |
| Потеря сессий при деплое | Да (inode перегрузка) | Нет |
| Откат при сбое | Ручной | Автоматический (load balancer) |
| Время сессионного запроса | ~5ms (disk I/O) | ~0.3ms (Redis) |
Redis сессии — необходимое условие для горизонтального масштабирования PHP. Без этого load balancer работает, но пользователи теряют авторизацию при каждом втором запросе.