Redis

Redis

Redis в приложении обычно появляется как «положить значение и забрать». Проблема начинается не с команд, а с соединения: оно одно, оно живёт неделями, и в резидентном воркере его нельзя дать двум запросам сразу. winter-redis закрывает именно это — соединениями управляет пул, а прикладной код видит стор с понятными методами.

Пакет flytachi/winter-redisПоверх ext-redis + CPoolSwoole опционально

Зачем это нужно

Проблема. Соединение с Redis — это один сокет с последовательным протоколом: команда → ответ → следующая команда. Пока по нему идёт один запрос, второй через него не пройдёт. В классическом PHP это не имело значения: процесс обслуживал один запрос и умирал. Резидентный воркер Winter обслуживает много запросов одновременно, каждый в своей корутине, — и одно общее соединение здесь означает, что две корутины пишут в него вперемежку. Ответ приходит на чужой запрос, в логе появляется «packets out of order», а под нагрузкой падает воркер целиком.

Открывать соединение на каждый запрос — другая крайность: рукопожатие, аутентификация и выбор базы на каждый чих, а тысяча одновременных запросов превращается в тысячу соединений и max number of clients reached на сервере.

Решение. Набор готовых соединений, из которого запрос берёт одно на время работы и возвращает по завершении. Соединения переиспользуются, их число ограничено сверху, а мёртвые заменяются — именно это делает winter-redis поверх CPool.

Если приложение маленькое, всё это может быть не нужно

Одно соединение на запрос через #[Bean(scope: Scope::Request)] — корректный и достаточный вариант для многих проектов, и он описан в Базовых подключениях. Пул нужен, когда становится заметна его цена: переоткрытие на каждом запросе и ничем не ограниченное число соединений.

Из чего состоит

Три слоя, каждым можно пользоваться отдельно.

Конфигурация

Класс, описывающий одну точку подключения: хост, порт, пароль, номер базы, таймауты, сериализатор. Одна база — один класс конфигурации; подробно — Конфигурация.

Пул

RedisPool держит по пулу на каждый класс конфигурации. Под Swoole соединение берётся при первом обращении в корутине и возвращается автоматически, когда она завершилась; без Swoole — одно самоподдерживающееся соединение на процесс. Прикладной код об этом различии не знает. Подробно — Пул соединений.

Стор

Именованный кусок базы: префикс ключей плюс те команды, которыми пользуются чаще всего. Это обычный объект — его внедряют, им пользуются, его тестируют. Подробно — Сторы, Хеши, Списки и Стримы.

Как это выглядит целиком

main/Configurations/MainRedisConfig.php
<?php

namespace Main\Configurations;

use Flytachi\Winter\Redis\Config\RedisConfig;

class MainRedisConfig extends RedisConfig
{
  public function setUp(): void
  {
      $this->host     = env('REDIS_HOST', 'localhost');
      $this->port     = (int) env('REDIS_PORT', 6379);
      $this->password = env('REDIS_PASS', '');
  }
}
main/Stores/SessionStore.php
<?php

namespace Main\Stores;

use Flytachi\Winter\Redis\Store\RedisStore;
use Main\Configurations\MainRedisConfig;

class SessionStore extends RedisStore
{
  protected string $redisConfigClassName = MainRedisConfig::class;
  protected string $prefix               = 'session:';
}
php
use Flytachi\Winter\DI\Attribute\Autowired;
use Main\Stores\SessionStore;

class AuthService
{
  #[Autowired]
  private SessionStore $sessions;

  public function remember(int $userId, string $token): void
  {
      $this->sessions->set((string) $userId, $token, ttl: 3600);
  }

  public function forget(int $userId): void
  {
      $this->sessions->delete((string) $userId);
  }
}

Ни получения соединения, ни его возврата, ни select() — соединение берётся при первой команде и уходит обратно в пул, когда запрос закончился.

Установка

bash
composer require flytachi/winter-redis

Нужен PHP 8.4+ и расширение ext-redis. ext-swoole опционален: с ним работает настоящий пул, без него — одно соединение с теми же проверками живости и сроком жизни.

Дальше

  • Конфигурация — классы конфигурации, базы, сериализация, размеры пула
  • Сторы — префикс, значения, сроки жизни, raw()
  • Хеши — работа с полями и их сроками жизни
  • Списки — очереди, потолок длины, блокирующее чтение
  • Стримы — журнал событий и группы потребителей
  • Пул соединений — как устроен, как настроить, что делать при переполнении