О насБлогКонтакты
DevOps28 июля 2016 г. 4 мин 21

Как хранить сессии в Redis для нескольких серверов PHP (2016)

AunimedaAunimeda
📋 Содержание

Как хранить сессии в 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 работает, но пользователи теряют авторизацию при каждом втором запросе.

Читайте также

Как настроить CI/CD для PHP-проекта в GitLab в 2016 годуaunimeda
DevOps

Как настроить CI/CD для PHP-проекта в GitLab в 2016 году

GitLab CI стал бесплатным и встроенным в GitLab CE в 2015. В 2016 мы перешли с ручных деплоев по FTP на автоматическую сборку и деплой при каждом push в master. Точная конфигурация .gitlab-ci.yml: тесты PHPUnit, статический анализ, деплой по SSH без downtime.

Как установить Let's Encrypt на nginx — первые дни после запуска (декабрь 2015)aunimeda
DevOps

Как установить Let's Encrypt на nginx — первые дни после запуска (декабрь 2015)

Let's Encrypt открылся для всех 3 декабря 2015 года. Бесплатные SSL-сертификаты с автопродлением — это изменило веб. В первые недели работы Certbot (тогда letsencrypt-auto) имел баги. Вот что работало и что нет в декабре 2015 на Ubuntu 14.04 + nginx.

Хабраэффект 2011: как пост на Хабре положил наш сервер и что мы с этим сделалиaunimeda
DevOps

Хабраэффект 2011: как пост на Хабре положил наш сервер и что мы с этим сделали

«Хабраэффект» — это когда публикация на Хабрахабр за 15 минут уничтожает сервер, который нормально работал месяцами. Мы через это прошли в 2011 году. Вот технический разбор того, что упало, почему упало и как мы перестроили стек чтобы это не повторилось.

Нужна IT-разработка для вашего бизнеса?

Разрабатываем сайты, мобильные приложения и AI-решения для бизнеса в России. Бесплатная консультация.

Получить консультацию Все статьи