← Назад к главному
# redis.rs - Модуль Redis клиента ## 📋 Назначение модуля `RedisCore` - обёртка вокруг redis-rs для управления подключениями и командами к Redis. **Основная логика:** 1. **Инициализация** - создание клиента Redis 2. **PING** - проверка подключения к серверу 3. **DELETE BY PREFIX** - удаление ключей по префиксу 4. **GET CONNECTION** - получение мультиплексированного подключения **Используется в:** - `main.rs` - проверка Redis PING/PONG, очистка Redis - `monitor.rs` - запись тиков и мета-данных - `src/live/meta.rs` - чтение/запись мета-данных - `src/live/logger.rs` - запись live_log - `src/live/entry_calculator.rs` - чтение/запись EntryCalculation --- ## 🏗️ Структура модуля ### Структура `RedisCore` **Строки:** 5-7 ```rust pub struct RedisCore { pub client: redis::Client, } ``` **Назначение:** Управление подключением к Redis **Поля:** | Поле | Тип | Описание | |------|-----|----------| | `client` | `redis::Client` | Клиент Redis (базовое подключение) | --- ## 🔄 Основные функции ### Функция `RedisCore::new()` **Строки:** 11-15 **Сигнатура:** ```rust pub fn new(redis_url: &str) -> Result ``` **Параметры:** - `redis_url` - URL подключения к Redis (например, `redis://127.0.0.1:6379/`) **Возвращает:** - `Ok(RedisCore)` - клиент создан успешно - `Err` - ошибка создания клиента **Алгоритм:** ```rust let client = redis::Client::open(redis_url) .context("Failed to create Redis client")?; Ok(RedisCore { client }) ``` **Что делает:** - Создаёт клиент Redis по указанному URL - Возвращает структуру `RedisCore` **Пример:** ```rust let redis = RedisCore::new("redis://127.0.0.1:6379/")?; ``` **Пример ошибки:** ``` Failed to create Redis client: Connection refused ``` --- ### Функция `RedisCore::ping()` **Строки:** 18-32 **Сигнатура:** ```rust pub async fn ping(&self) -> Result<()> ``` **Возвращает:** - `Ok(())` - Redis отвечает PONG - `Err` - ошибка подключения или неверный ответ **Алгоритм:** --- #### Этап 1: Получение подключения **Строки:** 19-20 ```rust let mut conn = self.get_conn().await .context("❌ ОШИБКА: Redis сервер не запущен или недоступен!")?; ``` **Что делает:** - Получает мультиплексированное подключение к Redis - При ошибке выводит сообщение "Redis сервер не запущен или недоступен" **Пример ошибки:** ``` ❌ ОШИБКА: Redis сервер не запущен или недоступен! Connection refused ``` --- #### Этап 2: Отправка PING **Строки:** 22-25 ```rust let pong: String = redis::cmd("PING") .query_async(&mut conn) .await .context("❌ ОШИБКА: Redis не отвечает на PING!")?; ``` **Что делает:** - Отправляет команду `PING` в Redis - Ожидает ответ `PONG` - При ошибке выводит сообщение "Redis не отвечает на PING" **Пример ошибки:** ``` ❌ ОШИБКА: Redis не отвечает на PING! Timeout ``` --- #### Этап 3: Проверка ответа **Строки:** 27-29 ```rust if pong != "PONG" { anyhow::bail!("❌ ОШИБКА: Redis вернул неверный ответ: {}", pong); } ``` **Что делает:** - Проверяет что ответ равен "PONG" - Если ответ другой → ошибка **Пример ошибки:** ``` ❌ ОШИБКА: Redis вернул неверный ответ: PANG ``` --- #### Этап 4: Возврат успеха **Строка:** 31 ```rust Ok(()) ``` --- **Пример использования:** ```rust match redis.ping().await { Ok(_) => println!("✅ Redis PING/PONG - сервер работает!"), Err(e) => eprintln!("❌ {}", e), } ``` **Пример успешного ответа:** ``` ✅ Redis PING/PONG - сервер работает! ``` --- ### Функция `RedisCore::delete_by_prefix()` **Строки:** 35-73 **Сигнатура:** ```rust pub async fn delete_by_prefix(&self, prefix: &str) -> Result ``` **Параметры:** - `prefix` - префикс ключей для удаления (например, `"hb:*"`) **Возвращает:** - `Ok(usize)` - количество удалённых ключей - `Err` - ошибка SCAN или DEL **Алгоритм:** --- #### Этап 1: Получение подключения **Строки:** 36-37 ```rust let mut conn = self.get_conn().await .context("❌ ОШИБКА: Не удалось подключиться к Redis для удаления!")?; ``` **Что делает:** - Получает мультиплексированное подключение к Redis - При ошибке выводит сообщение --- #### Этап 2: SCAN по ключам **Строки:** 40-60 ```rust // Используем SCAN для безопасного поиска ключей let mut keys: Vec = Vec::new(); let mut cursor: usize = 0; loop { let (next_cursor, batch): (usize, Vec) = redis::cmd("SCAN") .arg(cursor) .arg("MATCH") .arg(prefix) .arg("COUNT") .arg(100) .query_async(&mut conn) .await .context("❌ ОШИБКА: Не удалось выполнить SCAN!")?; keys.extend(batch); cursor = next_cursor; if cursor == 0 { break; } } ``` **Что делает:** - Использует `SCAN` для итерации по ключам (безопасная альтернатива KEYS) - `SCAN cursor MATCH prefix COUNT 100` - сканирует по 100 ключей за раз - Накапливает ключи в векторе - Повторяет пока `cursor != 0` **Почему SCAN вместо KEYS:** - `KEYS` блокирует Redis на всё время выполнения - `SCAN` неблокирующий и безопасный для продакшена **Пример команды:** ``` SCAN 0 MATCH hb:* COUNT 100 → возвращает (cursor, batch) SCAN next_cursor MATCH hb:* COUNT 100 → продолжаем пока cursor != 0 ``` --- #### Этап 3: Удаление ключей **Строки:** 62-72 ```rust if !keys.is_empty() { let deleted: usize = redis::cmd("DEL") .arg(keys.as_slice()) .query_async(&mut conn) .await .context("❌ ОШИБКА: Не удалось выполнить DEL!")?; Ok(deleted) } else { Ok(0) } ``` **Что делает:** - Если есть ключи → удаляет их командой `DEL` - Если ключей нет → возвращает 0 **Пример команды:** ``` DEL hb:ticks:MEMES_USDT hb:last:MEMES_USDT hb:meta:MEMES_USDT → возвращает 3 (удалено 3 ключа) ``` --- **Пример использования:** ```rust match redis.delete_by_prefix("hb:*").await { Ok(count) => { if count > 0 { println!("🧹 Redis очищен: удалено {} старых ключей", count); } else { println!("✅ Redis чист - нет старых данных"); } } Err(e) => { eprintln!("⚠️ Очистка Redis не удалась: {:?}", e); } } ``` **Пример успешного ответа:** ``` 🧹 Redis очищен: удалено 42 старых ключей ``` **Пример когда ключей нет:** ``` ✅ Redis чист - нет старых данных ``` --- ### Функция `RedisCore::get_conn()` **Строки:** 76-80 **Сигнатура:** ```rust pub async fn get_conn(&self) -> Result ``` **Возвращает:** - `Ok(MultiplexedConnection)` - мультиплексированное подключение - `Err` - ошибка получения подключения **Алгоритм:** ```rust let conn = self.client.get_multiplexed_async_connection().await .context("Failed to get Redis connection")?; Ok(conn) ``` **Что делает:** - Получает мультиплексированное асинхронное подключение - Мультиплексированное подключение позволяет выполнять несколько команд параллельно **Почему MultiplexedConnection:** - Поддерживает несколько команд параллельно - Пул подключений внутри - Безопасно использовать в async контексте --- ### Функция `get_redis_conn()` **Строки:** 84-86 **Сигнатура:** ```rust pub async fn get_redis_conn(redis: &RedisCore) -> Result ``` **Параметры:** - `redis` - ссылка на `RedisCore` **Возвращает:** - `Ok(MultiplexedConnection)` - мультиплексированное подключение - `Err` - ошибка получения подключения **Алгоритм:** ```rust redis.get_conn().await ``` **Что делает:** - Helper-функция для вызова `RedisCore::get_conn()` - Удобна для импорта через `use crate::redis::get_redis_conn` **Пример использования:** ```rust use crate::redis::get_redis_conn; let conn = get_redis_conn(&redis).await?; ``` --- ## 📊 Redis ключи | Ключ | Тип | Модуль | Описание | |------|-----|---------|----------| | `hb:meta:{symbol}` | String | `live/meta.rs` | Мета-данные контракта | | `hb:entry_calc:{symbol}` | String | `live/entry_calculator.rs` | Расчёт входа | | `hb:live_log:{symbol}` | List | `live/logger.rs` | Лог позиции (максимум 200 записей) | | `hb:ticks:{symbol}` | List | `monitor.rs` | Список тиков (максимум 1000) | | `hb:last:{symbol}` | String | `monitor.rs` | Последний тик | --- ## ⚠️ Критические моменты ### 1. SCAN вместо KEYS **Строки:** 43-60 ```rust let (next_cursor, batch): (usize, Vec) = redis::cmd("SCAN") .arg(cursor) .arg("MATCH") .arg(prefix) .arg("COUNT") .arg(100) .query_async(&mut conn) .await .context("❌ ОШИБКА: Не удалось выполнить SCAN!")?; ``` **Почему SCAN вместо KEYS:** - `KEYS hb:*` - блокирует Redis пока ищет все ключи - `SCAN 0 MATCH hb:*` - неблокирующий, возвращает по 100 ключей за раз - Безопасно использовать в продакшене с большим количеством ключей --- ### 2. MultiplexedConnection **Строка:** 77 ```rust let conn = self.client.get_multiplexed_async_connection().await ``` **Почему MultiplexedConnection:** - Поддерживает несколько команд параллельно - Пул подключений внутри - Оптимизировано для async/await --- ### 3. Проверка PONG **Строки:** 27-29 ```rust if pong != "PONG" { anyhow::bail!("❌ ОШИБКА: Redis вернул неверный ответ: {}", pong); } ``` **Почему это важно:** - PING должен вернуть именно "PONG" - Любой другой ответ означает проблему - Защита от некорректных ответов --- ### 4. batch возвращает Vec **Строка:** 44 ```rust let (next_cursor, batch): (usize, Vec) = redis::cmd("SCAN") ``` **Что это значит:** - SCAN возвращает `(cursor, batch)` - курсор и партию ключей - `batch` имеет тип `Vec` - вектор строк (имена ключей) - Накапливаем все партии в `keys.extend(batch)` --- ## 📚 Связанные файлы | Файл | Связь | |------|-------| | `src/main.rs` | Использует RedisCore::ping(), RedisCore::delete_by_prefix() | | `src/monitor.rs` | Использует get_redis_conn() для записи тиков | | `src/live/meta.rs` | Использует get_redis_conn() для чтения/записи мета-данных | | `src/live/logger.rs` | Использует get_redis_conn() для записи live_log | | `src/live/entry_calculator.rs` | Использует get_redis_conn() для чтения/записи EntryCalculation | --- ## 🎯 Резюме **Что делает redis.rs:** 1. ✅ Создаёт клиент Redis по URL 2. ✅ Проверяет подключение через PING/PONG 3. ✅ Удаляет ключи по префиксу (через SCAN) 4. ✅ Получает мультиплексированное подключение **RedisCore методы:** - `new(redis_url)` - создание клиента - `ping()` - проверка подключения (PING/PONG) - `delete_by_prefix(prefix)` - удаление ключей по префиксу - `get_conn()` - получение подключения **Helper функция:** - `get_redis_conn(redis)` - удобная обёртка над `redis.get_conn()` **Redis ключи бота:** - `hb:*` - префикс всех ключей бота - `hb:meta:{symbol}` - мета-данные - `hb:entry_calc:{symbol}` - расчёт входа - `hb:live_log:{symbol}` - лог позиции - `hb:ticks:{symbol}` - список тиков - `hb:last:{symbol}` - последний тик **Критические моменты:** - 🔥 SCAN вместо KEYS (неблокирующий) - 🔥 MultiplexedConnection (мультиплексированное подключение) - 🔥 Проверка PONG (валидация ответа) --- **Дата создания:** 2026-02-22 **Автор:** Claude Code Assistant **Версия:** 1.0