2026-08-21 16:31:01 +03:00
# Стратегия кэширования и Redis
2026-08-20 22:37:44 +03:00
XC_VM использует двухуровневую стратегию кэширования:
2026-08-27 18:07:43 +03:00
- **Файловый кэш (igbinary)** — основной уровень, используемый как потоковыми, так и административными путями
- **Redis/KeyDB** — дополнительный высокопроизводительный уровень для определения состояния соединения и расширенных операций
2026-08-20 22:37:44 +03:00
Путь потоковой передачи считывается исключительно из файлового кэша (никаких запросов к базе данных).
Путь администратора считывается из базы данных с дополнительным кратковременным кэшем.
---
## Интерфейс кэширования
Файл: `src/Core/Cache/CacheInterface.php`
```php
get ( $key , $maxAge = null )
set ( $key , $data , $ttl = 0 )
delete ( $key )
has ( $key , $maxAge = null )
flush ()
```
2026-08-21 16:31:01 +03:00
- `$ttl = 0` означает кэширование навсегда (до ручного удаления или промывки).
2026-08-20 22:37:44 +03:00
- `$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 )
```
Характеристики:
2026-08-21 16:31:01 +03:00
- Сериализация: igbinary (если доступно) или PHP выполнить резервную сериализацию.
2026-08-20 22:37:44 +03:00
- Блокировка: `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
```
2026-08-21 16:31:01 +03:00
Проверка работоспособности выдает сигнал Redis каждые 30 секунд (отменено). При сбое автоматически восстанавливается соединение. При сбое соединения возвращается значение null (постепенное ухудшение).
2026-08-20 22:37:44 +03:00
Конфигурация:
|Установка|Источник|По умолчанию|
| --- | --- | --- |
| `hostname` | `config.ini` |—|
| `port` |жестко запрограммированный| `6379` |
| `password` | `settings.redis_password` |—|
| `read_timeout` |жестко запрограммированный| `2.0s` |
| `tcp_keepalive` |жестко запрограммированный| `60s` |
### Сохраняющиеся при отключении в режиме ожидания (долгоживущие демоны)
Кратковременные запросы (PHP-FPM stream/admin) открывают новое соединение для каждого процесса
и на них не влияют тайм-ауты простоя. Демоны-долгожители — цикл `watchdog` ,
2026-08-27 18:07:43 +03:00
`fanout_sync` — вместо этого удерживайте соединение **один** через синглтон для их
2026-08-20 22:37:44 +03:00
весь срок службы, при котором возможны два режима сбоя на загруженном сервере или на межсерверном сервере
(LB → ГЛАВНАЯ) ссылка:
2026-08-27 18:07:43 +03:00
- **Сервер простаивает - закрывается.** Redis закрывает любой клиент, который простаивает дольше своего `timeout` (`300s`
2026-08-20 22:37:44 +03:00
в комплекте `bin/redis/redis.conf` ). затем phpredis прозрачно откроется снова.
2026-08-27 18:07:43 +03:00
сокет в следующей команде **без повторного воспроизведения аутентификации** , поэтому более поздняя команда
2026-08-20 22:37:44 +03:00
отвечает `NOAUTH` — или просто возвращает `false` .
2026-08-27 18:07:43 +03:00
- **Устранен пробел в проверке работоспособности.** `instance()` пингуется только каждые 30 секунд, так что между
2026-08-20 22:37:44 +03:00
пингует, что сброшенное соединение еще не замечено.
Охранники на месте:
2026-08-21 16:31:01 +03:00
- `instance()` обрабатывает любой ответ, не связанный с`PONG` пингом (беззвучное повторное подключение / `NOAUTH`
2026-08-27 18:07:43 +03:00
состояние) как отключенное соединение и принудительно выполняет полное, **повторная аутентификация** повторное подключение
2026-08-21 16:31:01 +03:00
через `\XC_VM::redis_connect()` — это не просто повторная попытка на уровне сокета.
2026-08-20 22:37:44 +03:00
- Вызывайте сайты, которые командами конвейера проверяют объект конвейера. Например
`ConnectionTracker::getCapacity()` проверяет, что `$redis->multi()` вернул
`\Redis` (сломанный сокет возвращает `false` и вызывает `zCard()` для этого bool
был бы фатальным вне пути повторного подключения) и выдает, чтобы его цикл повторных попыток снова подключился.
2026-08-27 18:07:43 +03:00
The server-side alternative (`timeout 0` ) is deliberately **not** used — the
2026-08-20 22:37:44 +03:00
вместо этого клиент становится устойчивым, и `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)
2026-08-21 16:31:01 +03:00
Перестраивает потоковые, линейные и последовательные данные. Регулируется до одного раза в 5 минут с помощью маркера `heavy_cache_built` :
2026-08-20 22:37:44 +03:00
- `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}` — метаданные серии
2026-08-21 16:31:01 +03:00
Режим обнаружения изменений (если включено `cache_changes` ): сравнивает временную метку базы данных `updated` с файлом `mtime` , восстанавливает только измененные элементы.
2026-08-20 22:37:44 +03:00
Режим полной перестройки: восстанавливает все записи. Регулируется параметром `cache_thread_count` .
### Готовность кэша
2026-08-21 16:31:01 +03:00
После каждой полной сборки кэша записывается файл `cache_complete` . Путь потоковой передачи проверяет наличие этого файла и завершает работу с ошибкой, если он отсутствует.
2026-08-20 22:37:44 +03:00
### Безопасность холодного кэширования
2026-08-21 16:31:01 +03:00
Потоковый загрузчик (`LegacyInitializer::initStreaming()` ) считывает `servers` ,
2026-08-20 22:37:44 +03:00
блок-листы и `proxy_servers` из файлового кэша. Перед первой сборкой
2026-08-21 16:31:01 +03:00
(fresh boot, cleared tmp) those files do not exist and `CacheReader::get()`
2026-08-20 22:37:44 +03:00
возвращает `null` , поэтому для каждого такого глобального массива по умолчанию используется пустой массив. Холодный кэш
2026-08-27 18:07:43 +03:00
следовательно, **не удается закрыть** — запрос не находит серверов и показывает "нет в эфире". —
2026-08-21 16:31:01 +03:00
вместо предупреждения `foreach(null)` или `in_array($x, null)` со смертельным исходом (PHP 8)
2026-08-20 22:37:44 +03:00
вниз по течению. Действительно поврежденный кэш *сборка* все еще отображается отдельно с помощью
2026-08-21 16:31:01 +03:00
`FileCache` предупреждение о сбое записи, поэтому это значение по умолчанию маскирует только временный
2026-08-20 22:37:44 +03:00
окно холодного пуска, а не настоящий сбой.
---
## Соглашения о ключах кэширования
### Системные ключи (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` по умолчанию.
- Считывает данные исключительно из файлового кэша (никаких запросов к базе данных).
2026-08-21 16:31:01 +03:00
- Raw igbinary deserialization: `igbinary_unserialize(file_get_contents(...))` .
2026-08-20 22:37:44 +03:00
- Если `cache_complete` отсутствует: завершите работу с ошибкой.
### Путь администратора (`Public/Controllers/Admin/*`)
- `cached: false` по умолчанию.
- Считывает данные из базы данных непосредственно через доменные службы.
2026-08-21 16:31:01 +03:00
- Необязательный кратковременный кэш (пример из `BouquetService::getAll()` ):
2026-08-20 22:37:44 +03:00
```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` |реализация кэша на основе файлов|
2026-08-21 16:31:01 +03:00
| `src/Core/Cache/RedisCache.php` |Redis реализация кэширования|
| `src/Infrastructure/Redis/RedisManager.php` |Redis одноэлементное соединение|
2026-08-20 22:37:44 +03:00
| `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 отсортированные наборы для определения состояния соединения|