# Стратегия кэширования и Redis XC_VM использует двухуровневую стратегию кэширования: - **Файловый кэш (igbinary)** — основной уровень, используемый как потоковыми, так и административными путями - **Redis/KeyDB** — дополнительный высокопроизводительный уровень для определения состояния соединения и расширенных операций Путь потоковой передачи считывается исключительно из файлового кэша (никаких запросов к базе данных). Путь администратора считывается из базы данных с дополнительным кратковременным кэшем. --- ## Интерфейс кэширования Файл: `src/Core/Cache/CacheInterface.php` ```php get($key, $maxAge = null) set($key, $data, $ttl = 0) delete($key) has($key, $maxAge = null) flush() ``` - `$ttl = 0` означает кэширование навсегда (до ручного удаления или промывки). - `$maxAge` проверяет время изменения файла на свежесть (только для файлового кэша). --- ## Файловый кэш Файл: `src/Core/Cache/FileCache.php` Реализация кэша по умолчанию. Данные, сериализованные в двоичном формате, хранятся в виде плоских файлов. ```php $cache = new FileCache(CACHE_TMP_PATH); $cache->set('my_key', $data, 3600); $data = $cache->get('my_key', 120); // only if < 2 min old ``` Статический удобный API (обратная совместимость): ```php FileCache::setCache($key, $data) FileCache::getCache($key, $maxAge = null) ``` Характеристики: - Сериализация: igbinary (если доступно) или PHP выполнить резервную сериализацию. - Блокировка: `LOCK_EX` при записи для предотвращения повреждения. - Расположение файла: `{basePath}/{key}` (нет подкаталогов для основных ключей). - Восстановление поврежденных файлов: обнаруживает поврежденные данные, автоматически удаляет поврежденные файлы. --- ## Заново спрятать Файл: `src/Core/Cache/RedisCache.php` Дополнительная высокопроизводительная реализация. ```php $redis = new RedisCache('127.0.0.1', 6379, $password, 'prefix:'); $redis->set($key, $data, 600); // 10-minute TTL via SETEX $redis->getConnection(); // raw phpredis for sorted sets, pipelines ``` - Отложенное подключение: подключается при первой операции. - Встроенная поддержка TTL через Redis `SETEX`. - Используется в основном для `ConnectionTracker` (отсортированных наборов для текущего состояния соединения). --- ## Redis Управление подключениями Файл: `src/Infrastructure/Redis/RedisManager.php` Жизненный цикл синглтона: ```php RedisManager::instance() // get active Redis or null RedisManager::ensureConnected() // connect if not already RedisManager::isConnected() // health check RedisManager::closeInstance() // disconnect ``` Проверка работоспособности выдает сигнал Redis каждые 30 секунд (отменено). При сбое автоматически восстанавливается соединение. При сбое соединения возвращается значение null (постепенное ухудшение). Конфигурация: |Установка|Источник|По умолчанию| | --- | --- | --- | | `hostname` | `config.ini` |—| | `port` |жестко запрограммированный| `6379` | | `password` | `settings.redis_password` |—| | `read_timeout` |жестко запрограммированный| `2.0s` | | `tcp_keepalive` |жестко запрограммированный| `60s` | ### Сохраняющиеся при отключении в режиме ожидания (долгоживущие демоны) Кратковременные запросы (PHP-FPM stream/admin) открывают новое соединение для каждого процесса и на них не влияют тайм-ауты простоя. Демоны-долгожители — цикл `watchdog`, `fanout_sync` — вместо этого удерживайте соединение **один** через синглтон для их весь срок службы, при котором возможны два режима сбоя на загруженном сервере или на межсерверном сервере (LB → ГЛАВНАЯ) ссылка: - **Сервер простаивает - закрывается.** Redis закрывает любой клиент, который простаивает дольше своего `timeout` (`300s` в комплекте `bin/redis/redis.conf`). затем phpredis прозрачно откроется снова. сокет в следующей команде **без повторного воспроизведения аутентификации**, поэтому более поздняя команда отвечает `NOAUTH` — или просто возвращает `false`. - **Устранен пробел в проверке работоспособности.** `instance()` пингуется только каждые 30 секунд, так что между пингует, что сброшенное соединение еще не замечено. Охранники на месте: - `instance()` обрабатывает любой ответ, не связанный с`PONG` пингом (беззвучное повторное подключение / `NOAUTH` состояние) как отключенное соединение и принудительно выполняет полное, **повторная аутентификация** повторное подключение через `\XC_VM::redis_connect()` — это не просто повторная попытка на уровне сокета. - Вызывайте сайты, которые командами конвейера проверяют объект конвейера. Например `ConnectionTracker::getCapacity()` проверяет, что `$redis->multi()` вернул `\Redis` (сломанный сокет возвращает `false` и вызывает `zCard()` для этого bool был бы фатальным вне пути повторного подключения) и выдает, чтобы его цикл повторных попыток снова подключился. The server-side alternative (`timeout 0`) is deliberately **not** used — the вместо этого клиент становится устойчивым, и `tcp-keepalive` по-прежнему получает доступ к мертвым одноранговым узлам. --- ## Заполнение кэша Файлы кэша генерируются двумя заданиями cron: ### Облегченный кэш (CacheCronJob) Запускает каждый цикл cron. Восстанавливает быстро меняющиеся данные (~1 секунда): - `settings` — настройки панели - `servers` — список серверов - `bouquets` — пакеты каналов - `categories` — категории потоков - Блокирующие списки: `blocked_isp`, `blocked_ua`, `blocked_ips`, `blocked_servers` - `allowed_ips`, `output_formats`, `hmac_keys`, `rtmp_ips` ### Большой объем кэша (CacheEngineCronJob) Перестраивает потоковые, линейные и последовательные данные. Регулируется до одного раза в 5 минут с помощью маркера `heavy_cache_built`: - `STREAMS_TMP_PATH/stream_{id}` — метаданные отдельного потока - `LINES_TMP_PATH/line_i_{user_id}` — данные учетной записи пользователя - `LINES_TMP_PATH/line_c_{username_password}` — имя пользователя → поиск идентификатора пользователя - `LINES_TMP_PATH/line_t_{access_token}` — токен → поиск идентификатора пользователя - `SERIES_TMP_PATH/series_{id}` — метаданные серии Режим обнаружения изменений (если включено `cache_changes`): сравнивает временную метку базы данных `updated` с файлом `mtime`, восстанавливает только измененные элементы. Режим полной перестройки: восстанавливает все записи. Регулируется параметром `cache_thread_count`. ### Готовность кэша После каждой полной сборки кэша записывается файл `cache_complete`. Путь потоковой передачи проверяет наличие этого файла и завершает работу с ошибкой, если он отсутствует. ### Безопасность холодного кэширования Потоковый загрузчик (`LegacyInitializer::initStreaming()`) считывает `servers`, блок-листы и `proxy_servers` из файлового кэша. Перед первой сборкой (fresh boot, cleared tmp) those files do not exist and `CacheReader::get()` возвращает `null`, поэтому для каждого такого глобального массива по умолчанию используется пустой массив. Холодный кэш следовательно, **не удается закрыть** — запрос не находит серверов и показывает "нет в эфире". — вместо предупреждения `foreach(null)` или `in_array($x, null)` со смертельным исходом (PHP 8) вниз по течению. Действительно поврежденный кэш *сборка* все еще отображается отдельно с помощью `FileCache` предупреждение о сбое записи, поэтому это значение по умолчанию маскирует только временный окно холодного пуска, а не настоящий сбой. --- ## Соглашения о ключах кэширования ### Системные ключи (CACHE_TMP_PATH) |Ключ|Содержание| | --- | --- | | `settings` |массив настроек панели| | `servers` |`array[server_id]` → конфигурация сервера| | `bouquets` |`array[bouquet_id]` → bouquet определение| | `categories` |`array[category_id]` → данные категории| | `bouquet_map` |`array[stream_id]` → `array[bouquet_id]`| | `category_map` |`array[bouquet_id]` → `array[category_id]`| | `permissions_{group_id}` |набор групповых разрешений| | `cache_complete` |`time()` временная метка последней полной сборки| ### Ключи потока (STREAMS_TMP_PATH) |Ключ|Содержание| | --- | --- | | `stream_{id}` |информация о потоке + букеты + состояние каждого сервера| | `channels_categories` |`array[stream_id]` → `array[category_id]`| ### Линейные ключи (LINES_TMP_PATH) |Ключ|Содержание| | --- | --- | | `line_i_{user_id}` |полная запись о пользователе| | `line_c_{username_password}` |user_id (поиск учетных данных)| | `line_t_{access_token}` |user_id (поиск токена)| ### Ключи серии (SERIES_TMP_PATH) |Ключ|Содержание| | --- | --- | | `series_{id}` |метаданные серии| | `series_map` |`array[stream_id]` → идентификатор серии| | `episodes_{series_id}` |`array[season_num]` → список эпизодов| --- ## Шаблоны аннулирования |Спусковой крючок|Затронутые ключи|Механизм| | --- | --- | --- | |Администратор редактирует поток|`stream_{id}`, `bouquet_map`|сигнал → следующий `cron:cache_engine`| |Администратор редактирует строку|`line_i_*`, `line_c_*`, `line_t_*`|следующий `cron:cache_engine`| |Настройки изменены|`settings`, категории, блок-листы|`SettingsManager::clearCache()` + хрон| |Обновлен список серверов|`servers`, `bouquet_map`|крон| |Начало потока (FFprobe)| `{md5(source)}` |5-минутный TTL с помощью проверки mtime файла| |Кнопка сброса администратора|все файлы в `CACHE_TMP_PATH`| `rm -rf` | --- ## Потоковая передача против пути администратора ### Путь потоковой передачи (`www/stream/*`) - `cached: true` по умолчанию. - Считывает данные исключительно из файлового кэша (никаких запросов к базе данных). - Raw igbinary deserialization: `igbinary_unserialize(file_get_contents(...))`. - Если `cache_complete` отсутствует: завершите работу с ошибкой. ### Путь администратора (`Public/Controllers/Admin/*`) - `cached: false` по умолчанию. - Считывает данные из базы данных непосредственно через доменные службы. - Необязательный кратковременный кэш (пример из `BouquetService::getAll()`): ```php $rCache = FileCache::getCache('bouquets', 60); // only if < 60s old if (!empty($rCache)) { return $rCache; } // miss: query database and write cache FileCache::setCache('bouquets', $rOutput); ``` --- ## Расположение файла кэша ```text /home/xc_vm/tmp/cache/ ├── settings ├── servers ├── bouquets ├── categories ├── bouquet_map ├── category_map ├── cache_complete ├── heavy_cache_built ├── streams/ │ ├── stream_{id} │ └── channels_categories ├── lines/ │ ├── line_i_{user_id} │ ├── line_c_{username_password} │ └── line_t_{access_token} └── series/ ├── series_{id} ├── series_map └── episodes_{series_id} ``` --- ## Связанные файлы |Файл|Цель| | --- | --- | | `src/Core/Cache/CacheInterface.php` |контракт на кэширование| | `src/Core/Cache/FileCache.php` |реализация кэша на основе файлов| | `src/Core/Cache/RedisCache.php` |Redis реализация кэширования| | `src/Infrastructure/Redis/RedisManager.php` |Redis одноэлементное соединение| | `src/Infrastructure/Cache/CacheReader.php` |устаревший мост для чтения кэша| | `src/Cli/CronJobs/CacheCronJob.php` |облегченная генерация кэша| | `src/Cli/CronJobs/CacheEngineCronJob.php` |генерация большого объема кэша (потоки, строки, серии)| | `src/Domain/Bouquet/BouquetService.php` |пример кэширования пути администратора| | `src/Domain/Stream/ConnectionTracker.php` |Redis отсортированные наборы для определения состояния соединения|