mirror of
https://github.com/Vateron-Media/XC_VM.git
synced 2026-10-05 04:02:31 +02:00
The line-by-line web translator reordered words inside `**bold**` spans and misplaced/dropped the markers, producing `**LB` or `****` (empty bold). Mask each `**...**` as ONE atomic sentinel: translate the inner text on its own, then store the whole balanced `**inner**` — the engine never sees the markers and cannot reorder or collapse them. Also harden the anthropic prompt to keep emphasis balanced. Auto-prune: after translating, delete generated docs/ru files whose docs/en source no longer exists (renamed/removed) and drop now-empty dirs, so the tree mirrors docs/en 1:1 (removes the stale development/modules.md and guides/geoip-and-device-detection.md). Bump PROMPT_VERSION to 6 to invalidate the contaminated cache and regenerate docs/ru (0 broken bold spans remaining, aside from pre-existing multi-line bold that spans a soft line break).
297 lines
16 KiB
Markdown
297 lines
16 KiB
Markdown
# Стратегия кэширования и 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 отсортированные наборы для определения состояния соединения|
|