Интеграция Redis в PHP-проекты на платформе Docker
Основные сценарии и их реализация
Как создать работающую связку PHP и Redis в Docker?
Наиболее распространённый подход - использовать официальный образ PHP с установкой расширения phpredis и отдельный контейнер Redis, объединённые через docker-compose. Это обеспечивает минимальное время отклика и надёжную передачу данных.
Шаг 1. Создание Dockerfile для PHP
FROM php:8.2-fpm-alpine
RUN docker-php-ext-install pdo pdo_mysql redis
COPY . /var/www/html
Docker php redis (docker php redis)
Шаг 2. Файл docker-compose.yml
version: '3.8'
services:
php:
build: .
ports:
- "9000:9000"
volumes:
- ./app:/var/www/html
depends_on:
- redis
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
volumes:
redis_data:
Шаг 3. Проверка подключения из PHP
$redis = new Redis();
$redis->connect('redis', 6379); // имя сервиса в compose
$redis->set('test', 'Hello from Docker!');
echo $redis->get('test');
Типичная ошибка: Connection refused при обращении к Redis через localhost. Внутри контейнера PHP нужно использовать имя сервиса (redis) или IP-адрес контейнера. Если контейнеры не в одной сети, они не увидят друг друга. Решение - использовать общую сеть в docker-compose (по умолчанию она создаётся). Также проверьте, что порт 6379 открыт внутри контейнера Redis.
Ещё одна проблема - не установлено расширение redis в PHP. Ошибка "Class 'Redis' not found". Убедитесь, что в Dockerfile используется docker-php-ext-install redis (доступно в официальных образах PHP).
Как подключиться к Redis через библиотеку predis без установки расширения?
Если расширение phpredis недоступно (например, при использовании shared-хостинга или ограниченной сборки PHP), можно воспользоваться чистым PHP-клиентом predis. Он устанавливается через Composer.
Dockerfile без расширения redis:
FROM php:8.2-fpm-alpine
RUN docker-php-ext-install pdo pdo_mysql
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html
COPY composer.json .
RUN composer install --no-dev
COPY . .
composer.json должен содержать "predis/predis": "^2.0".
Пример использования в PHP:
require 'vendor/autoload.php';
$client = new Predis\Client([
'scheme' => 'tcp',
'host' => 'redis',
'port' => 6379,
]);
$client->set('foo', 'bar');
echo $client->get('foo');
Основная проблема predis - скорость работы ниже, чем у phpredis, так как это pure-PHP решение. Для высоконагруженных проектов предпочтительнее расширение C. Также predis потребляет больше памяти. Ошибка подключения может быть связана с тем, что Composer не установлен внутри контейнера - тогда используйте многоэтапную сборку или копирование бинарника composer из другого образа.
Как настроить хранение PHP-сессий в Redis внутри Docker?
Redis идеально подходит для сессий благодаря высокой скорости чтения/записи и возможности TTL. Для этого нужно изменить конфигурацию PHP и убедиться, что расширение redis доступно.
Шаг 1. Установите расширение redis, как в основном варианте.
Шаг 2. В Dockerfile добавьте копирование конфигурационного файла php.ini или укажите настройки через переменные окружения.
COPY custom.ini /usr/local/etc/php/conf.d/custom.ini
custom.ini:
session.save_handler = redis
session.save_path = "tcp://redis:6379?auth=mysecretpassword"
Если пароль не требуется, опустите часть ?auth=....
Шаг 3. Убедитесь, что Redis контейнер работает и доступен по имени redis.
Проверка: в PHP-скрипте вызовите session_start() и проверьте, что данные сохраняются между запросами.
Частая ошибка: сессии не сохраняются, если Redis требует аутентификации, а пароль не передан в save_path. Также проверьте, что контейнер Redis запущен с опцией requirepass или --requirepass, если вы используете пароль. В docker-compose можно задать пароль через переменную окружения REDIS_PASSWORD (для официального образа).
Как настроить Redis в качестве кеша для Laravel в Docker?
Laravel поддерживает Redis из коробки. Достаточно правильно настроить файл .env и сервисы Docker.
Шаг 1. Docker-окружение аналогично основному варианту: контейнер PHP с расширением redis и контейнер Redis.
Шаг 2. В файле .env укажите:
REDIS_HOST=redis
REDIS_PASSWORD=null
REDIS_PORT=6379
CACHE_DRIVER=redis
SESSION_DRIVER=redis
Шаг 3. При необходимости используйте несколько баз данных (по умолчанию db0 для кеша, db1 для сессий). Можно настроить в config/database.php.
Пример: кеширование результата запроса:
$users = Cache::remember('users.active', 3600, function () {
return DB::table('users')->where('active', 1)->get();
});
Ошибка: Predis\ClientException или Connection refused, если имя хоста не разрешается. Убедитесь, что сервис Redis называется redis в docker-compose и контейнеры принадлежат одной сети. Также проверьте, что в .env не указан REDIS_PASSWORD, если пароль не задан.
Как обеспечить сохранение данных Redis после перезапуска контейнера?
По умолчанию Redis хранит все данные в памяти, но может периодически сохранять их на диск с помощью RDB или AOF. В Docker для этого нужно смонтировать volume в каталог /data.
docker-compose.yml (фрагмент):
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- ./redis-data:/data
command: ["redis-server", "--appendonly", "yes"]
Опция --appendonly yes включает AOF (Append Only File) - каждое изменение записывается в лог-файл. Альтернативно можно настроить RDB (снапшоты) через --save 900 1.
Конфигурация через файл: создайте redis.conf:
appendonly yes
appendfsync everysec
save 900 1
save 300 10
save 60 10000
и укажите в compose: command: ["redis-server", "/usr/local/etc/redis/redis.conf"] + volume с конфигом.
Проблема: после пересоздания контейнера данные не восстанавливаются, если volume не примонтирован или права доступа на каталог не позволяют Redis записывать. Решение - проверить владельца (chown 999:999, где 999 - UID пользователя redis в контейнере).
Как защитить Redis паролем и изолировать сеть Docker?
Redis по умолчанию не требует аутентификации. Для продакшена необходимо задать пароль и ограничить доступ к порту.
Вариант 1: Установка пароля через аргументы командной строки:
redis:
image: redis:7-alpine
command: ["redis-server", "--requirepass", "SuperSecretPassword"]
Вариант 2: Через конфигурационный файл redis.conf:
requirepass SuperSecretPassword
Тогда в PHP-коде при подключении нужно указать пароль:
$redis = new Redis();
$redis->connect('redis', 6379);
$redis->auth('SuperSecretPassword');
Для изоляции сети используйте отдельную сеть в docker-compose, чтобы только необходимые контейнеры имели доступ к Redis.
Пример сети:
networks:
internal:
driver: bridge
services:
php:
networks:
- internal
redis:
networks:
- internal
ports:
- "127.0.0.1:6379:6379" # только localhost
Ошибка: при использовании auth() в predis синтаксис отличается - $client->auth('password'). Если пароль не задан, попытка аутентификации приведёт к ошибке.
Расширенные примеры и нестандартные сценарии
Очередь задач с помощью списков (BRPOP/LPUSH)
Redis списки отлично подходят для реализации очередей. Один процесс (producer) добавляет задачи в конец списка, другой (worker) ждёт новые элементы.
// producer.php
$redis = new Redis();
$redis->connect('redis', 6379);
$redis->lPush('queue:email', 'user1@example.com');
$redis->lPush('queue:email', 'user2@example.com');
// worker.php
$redis = new Redis();
$redis->connect('redis', 6379);
while (true) {
$result = $redis->brPop('queue:email', 0); // блокируется до появления элемента
echo "Отправляем письмо: " . $result[1] . "\n";
}
Отправляем письмо: user2@example.com Отправляем письмо: user1@example.com
Транзакции Redis (MULTI/EXEC)
Группа команд выполняется атомарно. Пример - атомарный инкремент с проверкой.
$redis = new Redis();
$redis->connect('redis', 6379);
$redis->watch('counter');
$current = $redis->get('counter');
$redis->multi();
$redis->set('counter', $current + 1);
$redis->exec();
Если другой клиент изменит counter во время выполнения, транзакция будет отменена.
Публикация и подписка (Pub/Sub)
Redis поддерживает асинхронный обмен сообщениями через каналы.
// subscriber.php
$redis = new Redis();
$redis->connect('redis', 6379);
$redis->subscribe(['channel:notifications'], function ($instance, $channel, $message) {
echo "Получено сообщение: $message из канала $channel\n";
});
// publisher.php
$redis = new Redis();
$redis->connect('redis', 6379);
$redis->publish('channel:notifications', 'Привет, подписчики!');
(в subscriber.php) Получено сообщение: Привет, подписчики! из канала channel:notifications
Выполнение Lua-скриптов на стороне сервера
Скрипты выполняются атомарно и снижают нагрузку на сеть.
$script = <<<'LUA'
local current = redis.call('GET', KEYS[1])
if not current then
redis.call('SET', KEYS[1], ARGV[1])
return ARGV[1]
else
return current
end
LUA;
$redis->eval($script, ['mykey', 'default_value'], 1);
Результат: если ключ не существует, будет установлено значение из аргумента, иначе вернётся текущее.
Мониторинг состояния Redis через PHP
Метод INFO возвращает множество статистик.
$redis = new Redis();
$redis->connect('redis', 6379);
$info = $redis->info();
echo "Версия Redis: " . $info['redis_version'] . "\n";
echo "Количество ключей: " . $info['db0']['keys'] . "\n";
echo "Использовано памяти: " . $info['used_memory_human'] . "\n";
Версия Redis: 7.0.12 Количество ключей: 142 Использовано памяти: 1.23M
Настройка phpredis с опциями соединения
Можно задать таймауты, повторные попытки и serialiser.
$redis = new Redis();
$redis->connect('redis', 6379, 2.5); // таймаут 2.5 сек
$redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP);
$redis->setOption(Redis::OPT_PREFIX, 'myapp:');
$redis->set('user:1', ['name' => 'Иван', 'age' => 30]);
$data = $redis->get('user:1');
echo $data['name']; // Иван
Healthcheck для Redis в docker-compose
Чтобы убедиться, что Redis готов к работе до запуска PHP, добавьте healthcheck.
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 3
Тогда в сервисе PHP можно указать depends_on: redis: condition: service_healthy.